merge main

This commit is contained in:
Red Adaya 2024-12-11 08:45:14 +08:00
commit 9b2a7de8f2
49 changed files with 682 additions and 462 deletions

View File

@ -14,7 +14,7 @@ const config: StorybookConfig = {
"./custom-addons/theme/register",
],
core: {},
core: { builder: "@storybook/builder-vite" },
framework: {
name: "@storybook/react-vite",
@ -58,6 +58,7 @@ const config: StorybookConfig = {
<link rel="stylesheet" href="./fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="./fontawesome/css/sharp-solid.min.css" />
<link rel="stylesheet" href="./fontawesome/css/sharp-regular.min.css" />
<link rel="stylesheet" href="./fontawesome/css/custom-icons.min.css" />
<style>
#storybook-docs {
[id^="anchor--"],

View File

@ -9,7 +9,6 @@ import "../frontend/app/reset.scss";
import "./global.css";
import { light, dark } from "./theme";
import { DocsContainer } from "@storybook/addon-docs";
import { addons } from "@storybook/preview-api";
import { DARK_MODE_EVENT_NAME } from "storybook-dark-mode";

View File

@ -18,7 +18,7 @@ Wave is an open-source terminal that can launch graphical widgets, controlled an
Wave isn't just another terminal emulator; it's a rethink on how terminals are built. For too long there has been a disconnect between the CLI and the web. If you want fast, keyboard-accessible, easy-to-write applications, you use the CLI, but if you want graphical interfaces, native widgets, copy/paste, scrolling, variable font sizes, then you'd have to turn to the web. Wave's goal is to bridge that gap.
![WaveTerm Screenshot](./assets/wave-screenshot.png)
![WaveTerm Screenshot](./assets/wave-screenshot.webp)
## Installation

View File

@ -120,6 +120,7 @@ tasks:
sources:
- "cmd/server/*.go"
- "pkg/**/*.go"
- "pkg/**/*.json"
generates:
- dist/bin/wavesrv.*

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 600 400" style="enable-background:new 0 0 600 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_00000123405442604648655120000000636425700907447225_);}
.st2{fill:#FFFFFF;}
.st3{fill:#58C142;}
.st4{fill:url(#SVGID_00000100361222533905006500000012948355425597525920_);}
.st5{fill:url(#SVGID_00000039098984540155201650000005728924474174226367_);}
.st6{fill:url(#SVGID_00000065066116668922763670000009055193333651929737_);}
.st7{fill:url(#SVGID_00000046310373342220590360000000833566212213317053_);}
.st8{fill:url(#SVGID_00000067914870925445215340000010520730682335284873_);}
.st9{fill:url(#SVGID_00000139972009352615321800000011748188400644384138_);}
.st10{fill:url(#SVGID_00000137813723102203423940000002281677718566419372_);}
.st11{fill:url(#SVGID_00000117658585713214366330000001759073570098750902_);}
.st12{fill:url(#SVGID_00000075146307645666362690000001517567647835044244_);}
.st13{fill:url(#SVGID_00000169553343881037582000000012883033354483876241_);}
</style>
<g>
<path d="M218.2,133.2c51,0,99.3,21.9,138.2,39.5c21.2,9.6,43.1,19.5,53.4,19.5c8,0,21.5,0,30.5-42.1l4.6-21.3l21.6,3.1l66.7,9.6
c11.2-27.9,18.8-61.6,22.9-101.2L426.4,21.7c-8.6,40.3-24.5,60.5-53.3,60.5c-37.4,0-113.8-59.1-191.6-59.1
C76.4,23.1,23.2,89.4,7.3,226.2l46,6.7c7.6-15.6,16.4-29.3,26.4-41.2C112.6,152.9,159.2,133.2,218.2,133.2z"/>
<path d="M409.8,215.6c-37.4,0-113.8-59.1-191.6-59.1c-105.1,0-158.4,66.3-174.3,203.1l129.6,18.7c8.7-41.8,24.5-60.5,53.3-60.5
c38.9,0,112.3,59.1,191.6,59.1c105.1,0,159.9-66.3,174.3-203.1l-129.6-18.7C454.4,195.4,438.6,215.6,409.8,215.6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 KiB

BIN
assets/wave-screenshot.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

View File

@ -34,6 +34,7 @@ var editCmd = &cobra.Command{
func init() {
viewCmd.Flags().BoolVarP(&viewMagnified, "magnified", "m", false, "open view in magnified mode")
rootCmd.AddCommand(viewCmd)
editCmd.Flags().BoolVarP(&viewMagnified, "magnified", "m", false, "open view in magnified mode")
rootCmd.AddCommand(editCmd)
}

177
docs/docs/ai-presets.mdx Normal file
View File

@ -0,0 +1,177 @@
---
sidebar_position: 3.6
id: "ai-presets"
title: "AI Presets"
---
<img
title="AI Presets Menu"
style={{ float: "right", margin: "0 0 10px 10px" }}
src="./img/ai-presets.png"
width="300"
/>
Wave's AI widget can be configured to work with various AI providers and models through presets. Presets allow you to define multiple AI configurations and easily switch between them using the dropdown menu in the AI widget.
## How AI Presets Work
AI presets are defined in `~/.config/waveterm/presets/ai.json`. You can easily edit this file using:
```bash
wsh editconfig presets/ai.json
```
Each preset defines a complete set of configuration values for the AI widget. When you select a preset from the dropdown menu, those configuration values are applied to the widget. If no preset is selected, the widget uses the default values from `settings.json`.
Here's a basic example using Claude:
```json
{
"ai@claude-sonnet": {
"display:name": "Claude 3 Sonnet",
"display:order": 1,
"ai:*": true,
"ai:apitype": "anthropic",
"ai:model": "claude-3-5-sonnet-latest",
"ai:apitoken": "<your anthropic API key>"
}
}
```
To make a preset your default, add this single line to your `settings.json`:
```json
{
"ai:preset": "ai@claude-sonnet"
}
```
:::info
You can quickly set your default preset using the `setconfig` command:
```bash
wsh setconfig ai:preset=ai@claude-sonnet
```
This is easier than editing settings.json directly!
:::
## Provider-Specific Configurations
### Anthropic (Claude)
To use Claude models, create a preset like this:
```json
{
"ai@claude-sonnet": {
"display:name": "Claude 3 Sonnet",
"display:order": 1,
"ai:*": true,
"ai:apitype": "anthropic",
"ai:model": "claude-3-5-sonnet-latest",
"ai:apitoken": "<your anthropic API key>"
}
}
```
### Local LLMs (Ollama)
To connect to a local Ollama instance:
```json
{
"ai@ollama-llama": {
"display:name": "Ollama - Llama2",
"display:order": 2,
"ai:*": true,
"ai:baseurl": "http://localhost:11434/v1",
"ai:name": "llama2",
"ai:model": "llama2",
"ai:apitoken": "ollama"
}
}
```
Note: The `ai:apitoken` is required but can be any value as Ollama ignores it. See [Ollama OpenAI compatibility docs](https://github.com/ollama/ollama/blob/main/docs/openai.md) for more details.
### Azure OpenAI
To connect to Azure AI services:
```json
{
"ai@azure-gpt4": {
"display:name": "Azure GPT-4",
"display:order": 3,
"ai:*": true,
"ai:apitype": "azure",
"ai:baseurl": "<your Azure AI base URL>",
"ai:model": "<your model deployment name>",
"ai:apitoken": "<your Azure API key>"
}
}
```
Note: Do not include query parameters or `api-version` in the `ai:baseurl`. The `ai:model` should be your model deployment name in Azure.
### Perplexity
To use Perplexity's models:
```json
{
"ai@perplexity-sonar": {
"display:name": "Perplexity Sonar",
"display:order": 4,
"ai:*": true,
"ai:apitype": "perplexity",
"ai:model": "llama-3.1-sonar-small-128k-online",
"ai:apitoken": "<your perplexity API key>"
}
}
```
## Multiple Presets Example
You can define multiple presets in your `ai.json` file:
```json
{
"ai@claude-sonnet": {
"display:name": "Claude 3 Sonnet",
"display:order": 1,
"ai:*": true,
"ai:apitype": "anthropic",
"ai:model": "claude-3-5-sonnet-latest",
"ai:apitoken": "<your anthropic API key>"
},
"ai@ollama-llama": {
"display:name": "Ollama - Llama2",
"display:order": 2,
"ai:*": true,
"ai:baseurl": "http://localhost:11434/v1",
"ai:name": "llama2",
"ai:model": "llama2",
"ai:apitoken": "ollama"
},
"ai@perplexity-sonar": {
"display:name": "Perplexity Sonar",
"display:order": 3,
"ai:*": true,
"ai:apitype": "perplexity",
"ai:model": "llama-3.1-sonar-small-128k-online",
"ai:apitoken": "<your perplexity API key>"
}
}
```
The `display:order` value determines the order in which presets appear in the dropdown menu.
Remember to set your default preset in `settings.json`:
```json
{
"ai:preset": "ai@claude-sonnet"
}
```

View File

@ -54,6 +54,7 @@ wsh editconfig
| autoupdate:intervalms | float64 | time in milliseconds to wait between update checks (requires app restart) |
| autoupdate:installonquit | bool | whether to automatically install updates on quit (requires app restart) |
| autoupdate:channel | string | the auto update channel "latest" (stable builds), or "beta" (updated more frequently) (requires app restart) |
| tab:preset | string | a "bg@" preset to automatically apply to new tabs. e.g. `bg@green`. should match the preset key |
| widget:showhelp | bool | whether to show help/tips widgets in right sidebar |
| window:transparent | bool | set to true to enable window transparency (cannot be combined with `window:blur`) (macOS and Windows only, requires app restart, see [note on Windows compatibility](https://www.electronjs.org/docs/latest/tutorial/window-customization#limitations)) |
| window:blur | bool | set to enable window background blurring (cannot be combined with `window:transparent`) (macOS and Windows only, requires app restart, see [note on Windows compatibility](https://www.electronjs.org/docs/latest/tutorial/window-customization#limitations)) |

View File

@ -16,7 +16,7 @@ The easiest way to access connections is to click the <i className="fa-sharp fa-
## What are wsh Shell Extensions?
`wsh` is a small program that helps manage waveterm regardless of which machine you are currently connected to. It is always included on your host machine, but you also have the option to install it when connecting to a remote machine. If it is installed on the remote machine, it is installed at `~/.waveterm/bin/wsh`. Then, when wave connects to your connection (and only when wave connects to your connection), `~/.waveterm/bin` is added to your `PATH` for that individual session. For more info on what `wsh` is capable of, see [wsh command](/wsh). And if you wish to view the source code of `wsh`, you can find it [here](https://github.com/wavetermdev/waveterm/tree/main/cmd/wsh).
`wsh` is a small program that helps manage waveterm regardless of which machine you are currently connected to. It is always included on your host machine, but you also have the option to install it when connecting to a remote machine. If it is installed on the remote machine, it is installed at `~/.waveterm/bin/wsh`. Then, when wave connects to your connection (and only when wave connects to your connection), `~/.waveterm/bin` is added to your `PATH` for that individual session. If this fails for some reason, Wave will attempt to run without wsh. You will see this indicated by a small **<code><i className="fa-link-slash fa-solid fa-sharp"/></code>** icon in the block header. For more info on what `wsh` is capable of, see [wsh command](/wsh). And if you wish to view the source code of `wsh`, you can find it [here](https://github.com/wavetermdev/waveterm/tree/main/cmd/wsh).
With `wsh` installed, you have the ability to view certain widgets from the remote machine as if it were your host. In addition, `wsh` can be used to influence the widgets across various machines. As a very simple example, you can close a widget on the host machine by using the `wsh` command in a terminal window on a remote machine. For more information on what you can accomplish with `wsh`, take a look [here](/wsh).

View File

@ -38,6 +38,8 @@ in the [default termthemes.json file](https://github.com/wavetermdev/waveterm/bl
If you add your own termthemes.json file in the config directory, you can also add your own custom terminal themes (just follow the same format).
You can set the key `tab:preset` in your [Wave Config File](/config) to apply a theme to all new tabs.
<div style={{ clear: "both" }} />
#### Font Size

View File

@ -126,7 +126,7 @@ Suppose I want a widget that will run speedtest-go when opened. Then, I can defi
"view": "term",
"controller": "cmd",
"cmd": "speedtest-go --unix",
"cmd:clearonstart"
"cmd:clearonstart": true
}
}
},
@ -144,13 +144,13 @@ Now suppose I wanted to run a TUI app, for instance, `dua`. Well, it turns out t
```json
<... other widgets go here ...>,
"dua" : {
"icon": "brands@linux",
"label": "dua",
"blockdef": {
"meta": {
"view": "term",
"controller": "cmd",
"cmd": "dua"
"icon": "brands@linux",
"label": "dua",
"blockdef": {
"meta": {
"view": "term",
"controller": "cmd",
"cmd": "dua"
}
}
},

View File

@ -6,84 +6,11 @@ title: "FAQ"
# FAQ
### How do I set up my own LLM?
### How do I configure Wave to use different AI models/providers?
Open your [config file](./config) in Wave using `wsh editconfig` (the config file is normally located
at `~/.config/waveterm/settings.json`).
Wave supports various AI providers including local LLMs (via Ollama), Azure OpenAI, Anthropic's Claude, and Perplexity. The recommended way to configure these is through AI presets, which let you set up and easily switch between different providers and models.
| Key Name | Type | Function |
| ------------ | ------ | ----------------------------------------------- |
| ai:baseurl | string | Set the AI Base Url (must be OpenAI compatible) |
| ai:apitoken | string | your AI api token |
| ai:name | string | string to display in the Wave AI block header |
| ai:model | string | model name to pass to API |
| ai:maxtokens | int | max tokens to pass to API |
| ai:timeoutms | int | timeout (in milliseconds) for AI calls |
Here's an example of pointing it to a local Ollama instance. Note that to get the text in the header of the AI block
to update, you'll need to set the "ai:name" key. For ollama, you'll also need to provide something for the
apitoken (even though it is ignored).
Here are the ollma open AI compatibility docs: https://github.com/ollama/ollama/blob/main/docs/openai.md
```json
{
"ai:*": true,
"ai:baseurl": "http://localhost:11434/v1",
"ai:name": "llama3.2",
"ai:model": "llama3.2",
"ai:apitoken": "ollama"
}
```
Note: we set the `ai:*` key to true to clear all the existing "ai" keys so this config is a clean slate.
To switch between multiple models, consider [adding AI Presets](./presets) instead.
### How can I connect to Azure AI?
Open your [config file](./config) in Wave using `wsh editconfig` (the config file is normally located
at `~/.config/waveterm/settings.json`).
You'll need to set your `ai:baseurl` to your Azure AI Base URL (do not include query parameters or `api-version`).
You'll also need to set `ai:apitype` to `azure`. You can then set the `ai:model`, and `ai:apitoken` appropriately
for your setup.
### How can I connect to Claude?
Open your [config file](./config) in Wave using `wsh editconfig`.
Set these keys:
```json
{
"ai:*": true,
"ai:apitype": "anthropic",
"ai:model": "claude-3-5-sonnet-latest",
"ai:apitoken": "<your anthropic API key>"
}
```
Note: we set the `ai:*` key to true to clear all the existing "ai" keys so this config is a clean slate.
### How can I connect to Perplexity?
Open your [config file](./config) in Wave using `wsh editconfig`.
Set these keys:
```json
{
"ai:*": true,
"ai:apitype": "perplexity",
"ai:model": "llama-3.1-sonar-small-128k-online",
"ai:apitoken": "<your perplexity API key>"
}
```
Note: we set the `ai:*` key to true to clear all the existing "ai" keys so this config is a clean slate.
To switch between models, consider [adding AI Presets](./presets) instead.
See our [AI Presets documentation](/ai-presets) for detailed setup instructions for each provider.
### How can I see the block numbers?

View File

@ -1,127 +1,94 @@
---
sidebar_position: 3
sidebar_position: 3.5
id: "presets"
title: "Presets"
---
Presets can be used to apply multiple setting overrides at once to either a tab or a block. They are currently supported in two scenarios: tab backgrounds and AI models.
# Presets
You can set presets either by placing them in `~/.config/waveterm/presets.json` or by placing them in a JSON file in the `~/.config/waveterm/presets/` directory. All presets will be aggregated regardless of which file they're placed in so you can use the `presets` directory to organize them as you see fit.
Wave's preset system allows you to save and apply multiple configuration settings at once. Presets can be used in two different scenarios:
- AI models: Configure different AI providers and models (see [AI Presets](/ai-presets))
- Tab backgrounds: Apply visual styles to your tabs
## Managing Presets
You can store presets in two locations:
- `~/.config/waveterm/presets.json`: Main presets file
- `~/.config/waveterm/presets/`: Directory for organizing presets into separate files
All presets are aggregated regardless of which file they're in, so you can use the `presets` directory to organize them (e.g., `presets/backgrounds.json`, `presets/ai.json`).
:::info
You can easily edit your presets using the built-in editor:
You can open up the main presets config file in Wave by running:
```
wsh editconfig presets.json
```bash
wsh editconfig presets.json # Edit main presets file
wsh editconfig presets/ai.json # Edit AI presets
```
:::
## File format
## File Format
Presets follow the following format:
Presets follow this format:
```json
{
...
"<preset-type>@<preset-key>": {
"display:name": "<Preset name>",
"display:order": "<number>", // optional
"<overridden-config-key-1>": "<overridden-config-value-1>"
...
}
}
```
A complete example of a preset for a tab background is the following:
```json
{
"bg@rainbow": {
"display:name": "Rainbow",
"display:order": 2.1,
"bg:*": true,
"bg": "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% )",
"bg:opacity": 0.3
"<preset-type>@<preset-key>": {
"display:name": "<Preset name>",
"display:order": "<number>", // optional
"<overridden-config-key-1>": "<overridden-config-value-1>"
...
}
}
```
A complete example of a preset for an AI model is the following:
The `preset-type` determines where the preset appears in Wave's interface:
```json
{
"ai@wave": {
"display:name": "Ollama - llama3.1",
"display:order": 0,
"ai:baseurl": "http://localhost:11434",
"ai:model": "llama3.1:latest"
}
}
```
- `ai`: Appears in the models dropdown in the "Wave AI" widget header (see [AI Presets](/ai-presets))
- `bg`: Appears in the "Backgrounds" submenu when right-clicking a tab
## Preset Types
### Common Keys
The type of the preset determines where it can be discovered in the app. Currently, the two types that will be discovered in the app are `bg` and `ai`.
`bg` will be served in the "Backgrounds" submenu of the Tab context menu (which can be found by right-clicking on a tab).
![screenshot showing the default options in the backgrounds submenu of the tab context menu](./img/backgrounds-menu.png)
`ai` will be served in the models dropdown in the block header of the "Wave AI" widget.
![screenshot showing the default options in the models dropdown in the block header of the "Wave AI" widget](./img/waveai-model-dropdown.png)
### Available configuration keys
The following configuration keys are available for use in presets:
#### Common keys
| Key Name | Type | Function |
| ------------- | ------ | ---------------------------------------------------------------------- |
| display:name | string | the name to use when displaying the preset in a menu (required) |
| display:order | float | the order in which the preset should be displayed in a menu (optional) |
| Key Name | Type | Function |
| ------------- | ------ | ----------------------------------------- |
| display:name | string | Name shown in the UI menu (required) |
| display:order | float | Controls the order in the menu (optional) |
:::info
Configs in a preset are applied in order to override the default config values, which will persist for the remainder of the tab or block's lifetime. Setting `bg:*` or `ai:*` to `"true"` will clear the values of any previously overridden Background or AI configurations, respectively, setting them back to their defaults. You almost always want to add these keys to your presets in order to create a clean slate and prevent previously set values from leaking in.
When a preset is applied, it overrides the default configuration values for that tab or block. Using `bg:*` or `ai:*` will clear any previously overridden values, setting them back to defaults. It's recommended to include these keys in your presets to ensure a clean slate.
:::
## AI Configurations
## AI Presets
| Key Name | Type | Function |
| ------------- | ------ | -------------------------------------------------------------------------------------------------- |
| ai:\* | bool | reset all existing ai keys |
| ai:preset | string | the default AI preset to use |
| ai:baseurl | string | Set the AI Base Url (must be OpenAI compatible) |
| ai:apitoken | string | your AI api token |
| ai:apitype | string | defaults to "open_ai", but can also set to "azure" (for special Azure AI handling), or "anthropic" |
| ai:name | string | string to display in the Wave AI block header |
| ai:model | string | model name to pass to API |
| ai:apiversion | string | for Azure AI only (when apitype is "azure", this will default to "2023-05-15") |
| ai:orgid | string | |
| ai:maxtokens | int | max tokens to pass to API |
| ai:timeoutms | int | timeout (in milliseconds) for AI calls |
For configuring AI providers and models, see our dedicated [AI Presets](/ai-presets) documentation. It covers setting up presets for:
<a name="background-configurations" />
- Local LLMs via Ollama
- Azure OpenAI
- Anthropic's Claude
- Perplexity
- And more
## Background Configurations
## Background Presets
Wave's background system harnesses the full power of CSS backgrounds, letting you create rich visual effects through the "background" attribute. You can apply solid colors, gradients (both linear and radial), images, and even blend multiple elements together. The system offers preset configurations while maintaining the flexibility of pure CSS, making it both powerful and easy to use.
Wave's background system harnesses the full power of CSS backgrounds, letting you create rich visual effects through the "background" attribute. You can apply solid colors, gradients (both linear and radial), images, and even blend multiple elements together.
### Configuration Keys
| Key Name | Type | Function |
| -------------------- | ------ | ------------------------------------------------------------------------------------------------------- |
| bg:\* | bool | reset all existing bg keys (recommended to prevent any existing background settings from carrying over) |
| bg:\* | bool | Reset all existing bg keys (recommended to prevent any existing background settings from carrying over) |
| bg | string | CSS `background` attribute for the tab (supports colors, gradients images, etc.) |
| bg:opacity | float | the opacity of the background (defaults to 0.5) |
| bg:blendmode | string | the [blend mode](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode) of the background |
| bg:bordercolor | string | the color of the border when a block is not active (rarely used) |
| bg:activebordercolor | string | the color of the border when a block is active |
| bg:opacity | float | The opacity of the background (defaults to 0.5) |
| bg:blendmode | string | The [blend mode](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode) of the background |
| bg:bordercolor | string | The color of the border when a block is not active (rarely used) |
| bg:activebordercolor | string | The color of the border when a block is active |
### Simple solid color with opacity:
### Examples
#### Simple solid color:
```json
{
@ -135,7 +102,7 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
}
```
### Complex gradient combining multiple effects:
#### Complex gradient:
```json
{
@ -149,24 +116,11 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
}
```
### Repeating pattern background:
#### Background image:
```json
{
"bg@pattern": {
"display:name": "Diamond Pattern",
"bg:*": true,
"bg": "url('/path/to/pattern.png') repeat",
"bg:opacity": 0.15
}
}
```
### Full-cover background image:
```json
{
"bg@cover": {
"bg@ocean": {
"display:name": "Ocean Scene",
"bg:*": true,
"bg": "url('/path/to/ocean.jpg') center/cover no-repeat",
@ -175,25 +129,23 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
}
```
Replace image URLs with your own assets. All examples use reduced opacity to work well with dark themes.
:::info
Background images can be specified using URLs or local file paths. While URLs are supported, it's recommended to download and serve images locally for better reliability. For local files, you must use absolute paths or paths starting with `~` (e.g. `~/Downloads/background.png`). The system will automatically rewrite local paths to ensure proper access. We support all common web image formats: PNG, JPEG/JPG, WebP, GIF, and SVG.
Background images support both URLs and local file paths. For better reliability, we recommend using local files. Local paths must be absolute or start with `~` (e.g., `~/Downloads/background.png`). We support common web formats: PNG, JPEG/JPG, WebP, GIF, and SVG.
:::
:::tip
You can use `wsh setbg --print` to help generate the JSON for your background presets. For example:
The `setbg` command can help generate background preset JSON:
```bash
# Preview the metadata for a gradient background
wsh setbg --print "#ff0000"
# Preview a solid color preset
wsh setbg --print "#ff0000"
{
"bg:*": true,
"bg": "#ff0000",
"bg:opacity": 0.5
}
# Preview a centered logo configuration
# Preview a centered image preset
wsh setbg --print --center --opacity 0.3 ~/logo.png
{
"bg:*": true,
@ -202,9 +154,5 @@ wsh setbg --print --center --opacity 0.3 ~/logo.png
}
```
Copy the output and use it as a starting point for your preset configuration, just add the required `display:name` field!
Just add the required `display:name` field to complete your preset!
:::
#### Unset a default value
To unset a default value in a preset, add an override that sets it to an empty string, like `""`.

View File

@ -6,6 +6,28 @@ sidebar_position: 200
# Release Notes
### v0.10.0 &mdash; Dec 10, 2024
Wave Terminal v0.10.0 introduces workspaces, making it easier to manage multiple work environments. We've added powerful new command execution capabilities with `wsh run`, allowing you to launch and control commands in dedicated blocks. This release also brings significant improvements to SSH with a new connections configuration system for managing your remote environments.
- **Workspaces**: Organize your work into separate environments, each with their own tabs, layouts, and settings
- **Command Blocks**: New `wsh run` command for launching terminal commands in dedicated blocks, with support for magnification, auto-closing, and execution control ([docs](/wsh#run))
- **Connections**: New configuration system for managing SSH connections, with support for wsh-free operation, per-connection themes, and more ([docs](/connections))
- Improved tab management with better switching behavior and context menus (many bug fixes)
- New tab features including pinned tabs and drag-and-drop improvements
- Create, rename, and delete files/directories directly in directory preview
- Attempt wsh-free connection as a fallback if wsh installation or execution fails
- New `-i` flag to add identity files with the `wsh ssh` command
- Added Perplexity API integration ([docs](/faq#perplexity))
- `wsh setbg` command for background handling ([docs](/wsh#setbg))
- Switched from Less to SCSS for styling
- [bugfix] Fixed tab flickering issues during tab switches
- [bugfix] Corrected WaveAI text area resize behavior
- [bugfix] Fixed concurrent block controller start issues
- [bugfix] Fixed Preview Blocks for uninitialized connections
- Upgraded Go toolchain to 1.23.4
- Other bug fixes, performance improvements, and dependency updates
### v0.9.3 &mdash; Nov 20, 2024
New minor release that introduces Wave's connected computing extensions. We've introduced new `wsh` commands that allow you to store variables and files, and access them across terminal sessions (on both local and remote machines).

BIN
docs/static/img/ai-presets.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View File

@ -23,7 +23,10 @@ export type WindowOpts = {
};
export const waveWindowMap = new Map<string, WaveBrowserWindow>(); // waveWindowId -> WaveBrowserWindow
export let focusedWaveWindow = null; // on blur we do not set this to null (but on destroy we do)
// on blur we do not set this to null (but on destroy we do), so this tracks the *last* focused window
// e.g. it persists when the app itself is not focused
export let focusedWaveWindow: WaveBrowserWindow = null;
let cachedClientId: string = null;
@ -55,10 +58,13 @@ type WindowActionQueueEntry =
workspaceId: string;
};
function showCloseConfirmDialog(workspace: Workspace): boolean {
return !workspace.name && !workspace.icon && (workspace.tabids?.length > 1 || workspace.pinnedtabids?.length > 1);
}
export class WaveBrowserWindow extends BaseWindow {
waveWindowId: string;
workspaceId: string;
waveReadyPromise: Promise<void>;
allLoadedTabViews: Map<string, WaveTabView>;
activeTabView: WaveTabView;
private canClose: boolean;
@ -207,12 +213,7 @@ export class WaveBrowserWindow extends BaseWindow {
setWasActive(true);
});
this.on("blur", () => {
if (this.isDestroyed()) {
return;
}
if (focusedWaveWindow == this) {
focusedWaveWindow = null;
}
// nothing for now
});
this.on("close", (e) => {
if (this.canClose) {
@ -232,14 +233,13 @@ export class WaveBrowserWindow extends BaseWindow {
console.log("numWindows > 1", numWindows);
const workspace = await WorkspaceService.GetWorkspace(this.workspaceId);
console.log("workspace", workspace);
if (!workspace.name && !workspace.icon && workspace.tabids.length > 1) {
if (showCloseConfirmDialog(workspace)) {
console.log("workspace has no name, icon, and multiple tabs", workspace);
const choice = dialog.showMessageBoxSync(this, {
type: "question",
buttons: ["Cancel", "Yes"],
buttons: ["Cancel", "Close Window"],
title: "Confirm",
message:
"Are you sure you want to close this window (all tabs and blocks will be deleted)?",
message: "Window has unsaved tabs, closing window will delete existing tabs.\n\nContinue?",
});
if (choice === 0) {
console.log("user cancelled close window", this.waveWindowId);
@ -303,16 +303,12 @@ export class WaveBrowserWindow extends BaseWindow {
const workspaceList = await WorkspaceService.ListWorkspaces();
if (!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid) {
const curWorkspace = await WorkspaceService.GetWorkspace(this.workspaceId);
if (
(curWorkspace.tabids?.length || curWorkspace.pinnedtabids?.length) &&
(!curWorkspace.name || !curWorkspace.icon)
) {
if (showCloseConfirmDialog(curWorkspace)) {
const choice = dialog.showMessageBoxSync(this, {
type: "question",
buttons: ["Cancel", "Open in New Window", "Yes"],
buttons: ["Cancel", "Open in New Window", "Switch Workspace"],
title: "Confirm",
message:
"This window has unsaved tabs, switching workspaces will delete the existing tabs. Would you like to continue?",
message: "Window has unsaved tabs, switching workspaces will delete existing tabs.\n\nContinue?",
});
if (choice === 0) {
console.log("user cancelled switch workspace", this.waveWindowId);
@ -471,6 +467,9 @@ export class WaveBrowserWindow extends BaseWindow {
private async processActionQueue() {
while (this.actionQueue.length > 0) {
try {
if (this.isDestroyed()) {
break;
}
const entry = this.actionQueue[0];
let tabId: string = null;
// have to use "===" here to get the typechecker to work :/
@ -689,6 +688,21 @@ ipcMain.on("delete-workspace", (event, workspaceId) => {
fireAndForget(async () => {
const ww = getWaveWindowByWebContentsId(event.sender.id);
console.log("delete-workspace", workspaceId, ww?.waveWindowId);
const workspaceList = await WorkspaceService.ListWorkspaces();
const workspaceHasWindow = !!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid;
const choice = dialog.showMessageBoxSync(this, {
type: "question",
buttons: ["Cancel", "Delete Workspace"],
title: "Confirm",
message: `Deleting workspace will also delete its contents.${workspaceHasWindow ? "\nWorkspace is open in a window, which will be closed." : ""}\n\nContinue?`,
});
if (choice === 0) {
console.log("user cancelled workspace delete", workspaceId, ww?.waveWindowId);
return;
}
await WorkspaceService.DeleteWorkspace(workspaceId);
console.log("delete-workspace done", workspaceId, ww?.waveWindowId);
if (ww?.workspaceId == workspaceId) {
@ -711,7 +725,6 @@ export async function createNewWaveWindow() {
const existingWindowData = (await ObjectService.GetObject("window:" + existingWindowId)) as WaveWindow;
if (existingWindowData != null) {
const win = await createBrowserWindow(existingWindowData, fullConfig, { unamePlatform });
await win.waveReadyPromise;
win.show();
recreatedWindow = true;
}
@ -722,7 +735,6 @@ export async function createNewWaveWindow() {
}
console.log("creating new window");
const newBrowserWindow = await createBrowserWindow(null, fullConfig, { unamePlatform });
await newBrowserWindow.waveReadyPromise;
newBrowserWindow.show();
}
@ -754,7 +766,6 @@ export async function relaunchBrowserWindows() {
wins.push(win);
}
for (const win of wins) {
await win.waveReadyPromise;
console.log("show window", win.waveWindowId);
win.show();
}

View File

@ -94,7 +94,6 @@ function handleWSEvent(evtMsg: WSEventType) {
}
const fullConfig = await services.FileService.GetFullConfig();
const newWin = await createBrowserWindow(windowData, fullConfig, { unamePlatform });
await newWin.waveReadyPromise;
newWin.show();
} else if (evtMsg.eventtype == "electron:closewindow") {
console.log("electron:closewindow", evtMsg.data);
@ -378,6 +377,9 @@ function saveImageFileWithNativeDialog(defaultFileName: string, mimeType: string
defaultFileName = "image";
}
const ww = focusedWaveWindow;
if (ww == null) {
return;
}
const mimeToExtension: { [key: string]: string } = {
"image/png": "png",
"image/jpeg": "jpg",

View File

@ -164,7 +164,9 @@ export class Updater {
type: "info",
message: "There are currently no updates available.",
};
dialog.showMessageBox(focusedWaveWindow, dialogOpts);
if (focusedWaveWindow) {
dialog.showMessageBox(focusedWaveWindow, dialogOpts);
}
}
// Only update the last check time if this is an automatic check. This ensures the interval remains consistent.

View File

@ -637,7 +637,7 @@ const ChangeConnectionBlockModal = React.memo(
const connStatusMap = new Map<string, ConnStatus>();
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
const connectionsConfig = fullConfig.connections;
let filterOutNowsh = util.useAtomValueSafe(viewModel.filterOutNowsh) || true;
let filterOutNowsh = util.useAtomValueSafe(viewModel.filterOutNowsh) ?? true;
let maxActiveConnNum = 1;
for (const conn of allConnStatus) {
@ -706,9 +706,6 @@ const ChangeConnectionBlockModal = React.memo(
}
const filteredList: Array<string> = [];
for (const conn of connList) {
if (conn === connSelected) {
createNew = false;
}
if (
conn.includes(connSelected) &&
connectionsConfig?.[conn]?.["display:hidden"] != true &&
@ -716,13 +713,13 @@ const ChangeConnectionBlockModal = React.memo(
// != false is necessary because of defaults
) {
filteredList.push(conn);
if (conn === connSelected) {
createNew = false;
}
}
}
const filteredWslList: Array<string> = [];
for (const conn of wslList) {
if (conn === connSelected) {
createNew = false;
}
if (
conn.includes(connSelected) &&
connectionsConfig?.[conn]?.["display:hidden"] != true &&
@ -730,6 +727,9 @@ const ChangeConnectionBlockModal = React.memo(
// != false is necessary because of defaults
) {
filteredWslList.push(conn);
if (conn === connSelected) {
createNew = false;
}
}
}
// priority handles special suggestions when necessary

View File

@ -146,6 +146,10 @@
}
}
&.bold {
font-weight: bold;
}
&:disabled {
cursor: default;
opacity: 0.5;

View File

@ -67,6 +67,7 @@ interface InputProps {
required?: boolean;
maxLength?: number;
autoFocus?: boolean;
autoSelect?: boolean;
disabled?: boolean;
isNumber?: boolean;
inputRef?: React.MutableRefObject<any>;
@ -88,6 +89,7 @@ const Input = memo(
required,
maxLength,
autoFocus,
autoSelect,
disabled,
isNumber,
manageFocus,
@ -114,6 +116,9 @@ const Input = memo(
};
const handleFocus = () => {
if (autoSelect) {
inputRef.current?.select();
}
manageFocus?.(true);
onFocus?.();
};

View File

@ -1,6 +1,8 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
@import url("../../../node_modules/highlight.js/scss/github-dark-dimmed.scss");
.markdown {
display: flex;
flex-direction: row;

View File

@ -2,6 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
.window-drag {
-webkit-app-region: drag;
-webkit-app-region: drag !important;
z-index: var(--zindex-window-drag);
}

View File

@ -5,7 +5,7 @@
position: absolute;
width: 130px;
height: calc(100% - 1px);
padding: 6px 0px 0px;
padding: 0 0 0 0;
box-sizing: border-box;
font-weight: bold;
color: var(--secondary-text-color);

View File

@ -28,17 +28,23 @@
display: flex;
flex-direction: row;
width: env(titlebar-area-width);
-webkit-app-region: drag;
* {
-webkit-app-region: no-drag;
}
.tabs-wrapper {
transition: var(--tabs-wrapper-transition);
height: 32px;
height: 26px;
}
.tab-bar {
margin-top: 6px;
position: relative; // Needed for absolute positioning of child tabs
display: flex;
flex-direction: row;
height: 33px;
height: 27px;
}
.pinned-tab-spacer {

View File

@ -99,6 +99,43 @@ const ConfigErrorIcon = ({ buttonRef }: { buttonRef: React.RefObject<HTMLElement
);
};
function strArrayIsEqual(a: string[], b: string[]) {
// null check
if (a == null && b == null) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
function setIsEqual(a: Set<string> | null, b: Set<string> | null): boolean {
if (a == null && b == null) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.size !== b.size) {
return false;
}
for (const item of a) {
if (!b.has(item)) {
return false;
}
}
return true;
}
const TabBar = memo(({ workspace }: TabBarProps) => {
const [tabIds, setTabIds] = useState([]);
const [pinnedTabIds, setPinnedTabIds] = useState<Set<string>>(new Set());
@ -149,22 +186,22 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
}, [tabIds]);
useEffect(() => {
if (workspace) {
// Compare current tabIds with new workspace.tabids
const newTabIds = new Set([...(workspace.pinnedtabids ?? []), ...(workspace.tabids ?? [])]);
const newPinnedTabIds = workspace.pinnedtabids ?? [];
if (!workspace) {
return;
}
// Compare current tabIds with new workspace.tabids
console.log("tabbar workspace", workspace);
const areEqual =
tabIds.length === newTabIds.size &&
tabIds.every((id) => newTabIds.has(id)) &&
newPinnedTabIds.length === pinnedTabIds.size;
const newTabIdsArr = [...(workspace.pinnedtabids ?? []), ...(workspace.tabids ?? [])];
const newPinnedTabSet = new Set(workspace.pinnedtabids ?? []);
if (!areEqual) {
const newPinnedTabIdSet = new Set(newPinnedTabIds);
const newTabIdList = newPinnedTabIds.concat([...newTabIds].filter((id) => !newPinnedTabIdSet.has(id))); // Corrects for any duplicates between the two lists
setTabIds(newTabIdList);
setPinnedTabIds(newPinnedTabIdSet);
}
const areEqual = strArrayIsEqual(tabIds, newTabIdsArr) && setIsEqual(pinnedTabIds, newPinnedTabSet);
if (!areEqual) {
console.log("newPinnedTabIds", newPinnedTabSet);
console.log("newTabIdList", newTabIdsArr);
setTabIds(newTabIdsArr);
setPinnedTabIds(newPinnedTabSet);
}
}, [workspace, tabIds, pinnedTabIds]);

View File

@ -143,14 +143,9 @@
.expandable-menu-item-group-title {
height: 29px;
padding: 0;
.left-icon {
font-size: 14px;
width: 16px;
}
}
.color-icon-selector {
.workspace-editor {
width: 100%;
.input {
margin: 5px 0 10px;
@ -223,7 +218,7 @@
display: flex;
align-items: center;
justify-content: center;
margin-top: 5px;
margin-top: 10px;
}
}

View File

@ -43,7 +43,7 @@ const colors = [
];
const icons = [
"circle",
"custom@wave-logo-solid",
"triangle",
"star",
"heart",
@ -93,7 +93,7 @@ const IconSelector = memo(({ icons, selectedIcon, onSelect, className }: IconSel
return (
<div className={clsx("icon-selector", className)}>
{icons.map((icon) => {
const iconClass = makeIconClass(icon, false);
const iconClass = makeIconClass(icon, true);
return (
<i
key={icon}
@ -106,7 +106,7 @@ const IconSelector = memo(({ icons, selectedIcon, onSelect, className }: IconSel
);
});
interface ColorAndIconSelectorProps {
interface WorkspaceEditorProps {
title: string;
icon: string;
color: string;
@ -116,7 +116,7 @@ interface ColorAndIconSelectorProps {
onIconChange: (newIcon: string) => void;
onDeleteWorkspace: () => void;
}
const ColorAndIconSelector = memo(
const WorkspaceEditor = memo(
({
title,
icon,
@ -126,28 +126,30 @@ const ColorAndIconSelector = memo(
onColorChange,
onIconChange,
onDeleteWorkspace,
}: ColorAndIconSelectorProps) => {
}: WorkspaceEditorProps) => {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (focusInput && inputRef.current) {
inputRef.current.focus();
inputRef.current.select();
}
}, [focusInput]);
return (
<div className="color-icon-selector">
<div className="workspace-editor">
<Input
ref={inputRef}
className={clsx("vertical-padding-3", { error: title === "" })}
onChange={onTitleChange}
value={title}
autoFocus
autoSelect
/>
<ColorSelector selectedColor={color} colors={colors} onSelect={onColorChange} />
<IconSelector selectedIcon={icon} icons={icons} onSelect={onIconChange} />
<div className="delete-ws-btn-wrapper">
<Button className="ghost red font-size-12" onClick={onDeleteWorkspace}>
<Button className="ghost red font-size-12 bold" onClick={onDeleteWorkspace}>
Delete workspace
</Button>
</div>
@ -221,6 +223,7 @@ const WorkspaceSwitcher = forwardRef<HTMLDivElement, {}>(({}, ref) => {
setTimeout(() => {
fireAndForget(updateWorkspaceList);
}, 10);
setEditingWorkspace(activeWorkspace.oid);
};
return (
@ -335,7 +338,7 @@ const WorkspaceSwitcherItem = ({
>
<ExpandableMenuItemLeftElement>
<i
className={clsx("left-icon", makeIconClass(workspace.icon, false))}
className={clsx("left-icon", makeIconClass(workspace.icon, true))}
style={{ color: workspace.color }}
/>
</ExpandableMenuItemLeftElement>
@ -349,7 +352,7 @@ const WorkspaceSwitcherItem = ({
</div>
</ExpandableMenuItemGroupTitle>
<ExpandableMenuItem>
<ColorAndIconSelector
<WorkspaceEditor
title={workspace.name}
icon={workspace.icon}
color={workspace.color}

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
// Used for syntax highlighting in markdown
@import url("../../node_modules/highlight.js/styles/github-dark-dimmed.min.css");
:root {
--main-text-color: #f7f7f7;

View File

@ -293,7 +293,8 @@ function DirectoryTable({
newPath = path.replace(fileName, newName);
console.log(`replacing ${fileName} with ${newName}: ${path}`);
fireAndForget(async () => {
await FileService.Rename(globalStore.get(model.connection), path, newPath);
const connection = await globalStore.get(model.connection);
await FileService.Rename(connection, path, newPath);
model.refreshCallback();
});
}
@ -804,7 +805,8 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
onSave: (newName: string) => {
console.log(`newFile: ${newName}`);
fireAndForget(async () => {
await FileService.TouchFile(globalStore.get(model.connection), `${dirPath}/${newName}`);
const connection = await globalStore.get(model.connection);
await FileService.TouchFile(connection, `${dirPath}/${newName}`);
model.refreshCallback();
});
setEntryManagerProps(undefined);
@ -817,7 +819,8 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
onSave: (newName: string) => {
console.log(`newDirectory: ${newName}`);
fireAndForget(async () => {
await FileService.Mkdir(globalStore.get(model.connection), `${dirPath}/${newName}`);
const connection = await globalStore.get(model.connection);
await FileService.Mkdir(connection, `${dirPath}/${newName}`);
model.refreshCallback();
});
setEntryManagerProps(undefined);

View File

@ -128,7 +128,7 @@ export class PreviewModel implements ViewModel {
normFilePath: Atom<Promise<string>>;
loadableStatFilePath: Atom<Loadable<string>>;
loadableFileInfo: Atom<Loadable<FileInfo>>;
connection: Atom<string>;
connection: Atom<Promise<string>>;
statFile: Atom<Promise<FileInfo>>;
fullFile: Atom<Promise<FullFile>>;
fileMimeType: Atom<Promise<string>>;
@ -136,6 +136,7 @@ export class PreviewModel implements ViewModel {
fileContentSaved: PrimitiveAtom<string | null>;
fileContent: WritableAtom<Promise<string>, [string], void>;
newFileContent: PrimitiveAtom<string | null>;
connectionError: PrimitiveAtom<string>;
openFileModal: PrimitiveAtom<boolean>;
openFileError: PrimitiveAtom<string>;
@ -167,6 +168,7 @@ export class PreviewModel implements ViewModel {
this.markdownShowToc = atom(false);
this.filterOutNowsh = atom(true);
this.monacoRef = createRef();
this.connectionError = atom("");
this.viewIcon = atom((get) => {
const blockData = get(this.blockAtom);
if (blockData?.meta?.icon) {
@ -359,15 +361,22 @@ export class PreviewModel implements ViewModel {
return fileInfo.dir + "/" + fileInfo.name;
});
this.loadableStatFilePath = loadable(this.statFilePath);
this.connection = atom<string>((get) => {
return get(this.blockAtom)?.meta?.connection;
this.connection = atom<Promise<string>>(async (get) => {
const connName = get(this.blockAtom)?.meta?.connection;
try {
await RpcApi.ConnEnsureCommand(TabRpcClient, connName, { timeout: 60000 });
globalStore.set(this.connectionError, "");
} catch (e) {
globalStore.set(this.connectionError, e as string);
}
return connName;
});
this.statFile = atom<Promise<FileInfo>>(async (get) => {
const fileName = get(this.metaFilePath);
if (fileName == null) {
return null;
}
const conn = get(this.connection) ?? "";
const conn = (await get(this.connection)) ?? "";
const statFile = await services.FileService.StatFile(conn, fileName);
return statFile;
});
@ -384,7 +393,7 @@ export class PreviewModel implements ViewModel {
if (fileName == null) {
return null;
}
const conn = get(this.connection) ?? "";
const conn = (await get(this.connection)) ?? "";
const file = await services.FileService.ReadFile(conn, fileName);
return file;
});
@ -434,10 +443,14 @@ export class PreviewModel implements ViewModel {
const mimeType = await getFn(this.fileMimeType);
const fileInfo = await getFn(this.statFile);
const fileName = await getFn(this.statFilePath);
const connErr = getFn(this.connectionError);
const editMode = getFn(this.editMode);
const parentFileInfo = await this.getParentInfo(fileInfo);
console.log(parentFileInfo);
if (connErr != "") {
return { errorStr: `Connection Error: ${connErr}` };
}
if (parentFileInfo?.notfound ?? false) {
return { errorStr: `Parent Directory Not Found: ${fileInfo.path}` };
}
@ -505,7 +518,7 @@ export class PreviewModel implements ViewModel {
}
async getParentInfo(fileInfo: FileInfo): Promise<FileInfo | undefined> {
const conn = globalStore.get(this.connection);
const conn = await globalStore.get(this.connection);
try {
const parentFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.path, ".."], {
route: makeConnRoute(conn),
@ -526,7 +539,7 @@ export class PreviewModel implements ViewModel {
this.updateOpenFileModalAndError(false);
return true;
}
const conn = globalStore.get(this.connection);
const conn = await globalStore.get(this.connection);
try {
const newFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.path, ".."], {
route: makeConnRoute(conn),
@ -586,7 +599,7 @@ export class PreviewModel implements ViewModel {
console.log("not saving file, newFileContent is null");
return;
}
const conn = globalStore.get(this.connection) ?? "";
const conn = (await globalStore.get(this.connection)) ?? "";
try {
await services.FileService.SaveFile(conn, filePath, stringToBase64(newFileContent));
globalStore.set(this.fileContent, newFileContent);
@ -609,7 +622,7 @@ export class PreviewModel implements ViewModel {
this.updateOpenFileModalAndError(false);
return true;
}
const conn = globalStore.get(this.connection);
const conn = await globalStore.get(this.connection);
try {
const newFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.dir, filePath], {
route: makeConnRoute(conn),

View File

@ -103,6 +103,11 @@ function makeIconClass(icon: string, fw: boolean, opts?: { spin?: boolean; defau
icon = icon.replace(/^brands@/, "");
return clsx(`fa fa-brands fa-${icon}`, fw ? "fa-fw" : null, opts?.spin ? "fa-spin" : null);
}
if (icon.match(/^custom@[a-z0-9-]+$/)) {
// strip off the "custom@" prefix if it exists
icon = icon.replace(/^custom@/, "");
return clsx(`fa fa-kit fa-${icon}`, fw ? "fa-fw" : null, opts?.spin ? "fa-spin" : null);
}
if (opts?.defaultIcon != null) {
return makeIconClass(opts.defaultIcon, fw, { spin: opts?.spin });
}

View File

@ -109,21 +109,27 @@ async function reinitWave() {
}
function reloadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || ws.tabids == null) {
if (ws == null || (!ws.tabids?.length && !ws.pinnedtabids?.length)) {
return;
}
ws.tabids.forEach((tabid) => {
ws?.tabids.forEach((tabid) => {
WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid));
});
ws?.pinnedtabids?.forEach((tabid) => {
WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid));
});
}
function loadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || ws.tabids == null) {
if (ws == null || (!ws.tabids?.length && !ws.pinnedtabids?.length)) {
return;
}
ws.tabids.forEach((tabid) => {
WOS.getObjectValue<Tab>(WOS.makeORef("tab", tabid));
});
ws.pinnedtabids.forEach((tabid) => {
WOS.getObjectValue<Tab>(WOS.makeORef("tab", tabid));
});
}
async function initWave(initOpts: WaveInitOpts) {

View File

@ -10,6 +10,7 @@
<link rel="stylesheet" href="/fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="/fontawesome/css/sharp-solid.min.css" />
<link rel="stylesheet" href="/fontawesome/css/sharp-regular.min.css" />
<link rel="stylesheet" href="/fontawesome/css/custom-icons.min.css" />
<script type="module" src="frontend/wave.ts"></script>
</head>
<body class="init">

View File

@ -7,7 +7,7 @@
"productName": "Wave",
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
"license": "Apache-2.0",
"version": "0.10.0-beta.2",
"version": "0.10.0-beta.4",
"homepage": "https://waveterm.dev",
"build": {
"appId": "dev.commandline.waveterm"
@ -30,14 +30,15 @@
"@chromatic-com/storybook": "^3.2.2",
"@eslint/js": "^9.16.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@storybook/addon-essentials": "^8.4.6",
"@storybook/addon-interactions": "^8.4.6",
"@storybook/addon-links": "^8.4.6",
"@storybook/blocks": "^8.4.6",
"@storybook/react": "^8.4.6",
"@storybook/react-vite": "^8.4.6",
"@storybook/test": "^8.4.6",
"@storybook/theming": "^8.4.6",
"@storybook/addon-essentials": "^8.4.7",
"@storybook/addon-interactions": "^8.4.7",
"@storybook/addon-links": "^8.4.7",
"@storybook/blocks": "^8.4.7",
"@storybook/builder-vite": "^8.4.7",
"@storybook/react": "^8.4.7",
"@storybook/react-vite": "^8.4.7",
"@storybook/test": "^8.4.7",
"@storybook/theming": "^8.4.7",
"@types/color": "^4.2.0",
"@types/css-tree": "^2",
"@types/debug": "^4",
@ -68,7 +69,7 @@
"rollup-plugin-flow": "^1.1.1",
"sass": "^1.82.0",
"semver": "^7.6.3",
"storybook": "^8.4.6",
"storybook": "^8.4.7",
"storybook-dark-mode": "^4.0.2",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",

View File

@ -733,6 +733,10 @@ func GetConnectionsFromInternalConfig() []string {
var internalNames []string
config := wconfig.ReadFullConfig()
for internalName := range config.Connections {
if strings.HasPrefix(internalName, "wsl://") {
// don't add wsl conns to this list
continue
}
internalNames = append(internalNames, internalName)
}
return internalNames

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package workspaceservice
import (

View File

@ -96,5 +96,13 @@
"bg": "linear-gradient(120deg,hsla(350, 65%, 57%, 1),hsla(30,60%,60%, .75), hsla(208,69%,50%,.15), hsl(230,60%,40%)),radial-gradient(at top right,hsla(300,60%,70%,0.3),transparent),radial-gradient(at top left,hsla(330,100%,70%,.20),transparent),radial-gradient(at top right,hsla(190,100%,40%,.20),transparent),radial-gradient(at bottom left,hsla(323,54%,50%,.5),transparent),radial-gradient(at bottom left,hsla(144,54%,50%,.25),transparent)",
"bg:blendmode": "overlay",
"bg:text": "rgb(200, 200, 200)"
},
"bg@cosmic-tide": {
"display:name": "Cosmic Tide",
"display:order": 3.6,
"bg:activebordercolor": "#ff55aa",
"bg:*": true,
"bg": "linear-gradient(135deg, #00d9d9, #ff55aa, #1e1e2f, #2f3b57, #ff99ff)",
"bg:opacity": 0.6
}
}

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wcore
import (

View File

@ -54,7 +54,7 @@ func EnsureInitialData() error {
return nil
}
log.Println("client has no windows, creating starter workspace")
starterWs, err := CreateWorkspace(ctx, "Starter workspace", "circle", "#58C142", true)
starterWs, err := CreateWorkspace(ctx, "Starter workspace", "custom@wave-logo-solid", "#58C142", true)
if err != nil {
return fmt.Errorf("error creating starter workspace: %w", err)
}

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wcore
import (

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wcore
import (

View File

@ -0,0 +1 @@
@charset "utf-8";.fak.fa-wave-logo-solid,.fa-kit.fa-wave-logo-solid{--fa:"";--fa--fa:""}.fak,.fa-kit{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:var(--fa-display,inline-block);font-variant:normal;text-rendering:auto;font-family:Font Awesome Kit;font-style:normal;font-weight:400;line-height:1}.fak:before,.fa-kit:before{content:var(--fa)}@font-face{font-family:Font Awesome Kit;font-style:normal;font-display:block;src:url(../webfonts/custom-icons.woff2)format("woff2"),url(../webfonts/custom-icons.ttf)format("truetype")}

File diff suppressed because one or more lines are too long

Binary file not shown.

317
yarn.lock
View File

@ -5167,9 +5167,9 @@ __metadata:
languageName: node
linkType: hard
"@storybook/addon-actions@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-actions@npm:8.4.6"
"@storybook/addon-actions@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-actions@npm:8.4.7"
dependencies:
"@storybook/global": "npm:^5.0.0"
"@types/uuid": "npm:^9.0.1"
@ -5177,164 +5177,164 @@ __metadata:
polished: "npm:^4.2.2"
uuid: "npm:^9.0.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/80b2feceacb4ebe7f2be06b2fe3f49ded5ee08ca8bd036ff47a65d45d8796d29081ccadd0526984c8022bcfa24348e0ad4ce3f37cee4a60a928bae372bfc8afe
storybook: ^8.4.7
checksum: 10c0/411be60f358101291cbd4ff8e5ddbac58fa0583c95338b82b410dc030a73632b654eaf7004b421c7e309cf0bfa709c4f93728b943e1b59dcfff5a249686501c1
languageName: node
linkType: hard
"@storybook/addon-backgrounds@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-backgrounds@npm:8.4.6"
"@storybook/addon-backgrounds@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-backgrounds@npm:8.4.7"
dependencies:
"@storybook/global": "npm:^5.0.0"
memoizerific: "npm:^1.11.3"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/2125d6905bf44194adf79e92698753d5e4ff75fac1ffbba1fc95ae705ba9ac8dc6ca9249c9a862aa05ea207d916d23142faefa759bb9ce21c6e16f0e329d28d2
storybook: ^8.4.7
checksum: 10c0/d22c4acd1d99f616865dde11c70b444a0aac7fe7623904479a29a0142b504f284ddc2407eacfd1203c3b0856e5497e7902eb86e287516364c7735b90e224bbcb
languageName: node
linkType: hard
"@storybook/addon-controls@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-controls@npm:8.4.6"
"@storybook/addon-controls@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-controls@npm:8.4.7"
dependencies:
"@storybook/global": "npm:^5.0.0"
dequal: "npm:^2.0.2"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/f5f0ab2de8de80c8c3726de81802042cc29a6f2ec50de3b8bd463286c9056e87800e4ea9b350c6a41ce4c4175a11cb7d3d490da5cfc20bbf2a2e3549f77a82a7
storybook: ^8.4.7
checksum: 10c0/900c71d172e9f75a1c39a87de1d411890fcea012586be02e3293c705c500a3a62a2bdecb10c11ba9c9f6117706dfbc34aaa40d2ca8e8a9d7b8a6a739d6a73e0c
languageName: node
linkType: hard
"@storybook/addon-docs@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-docs@npm:8.4.6"
"@storybook/addon-docs@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-docs@npm:8.4.7"
dependencies:
"@mdx-js/react": "npm:^3.0.0"
"@storybook/blocks": "npm:8.4.6"
"@storybook/csf-plugin": "npm:8.4.6"
"@storybook/react-dom-shim": "npm:8.4.6"
"@storybook/blocks": "npm:8.4.7"
"@storybook/csf-plugin": "npm:8.4.7"
"@storybook/react-dom-shim": "npm:8.4.7"
react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0"
react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/ae53bf71048fe7476862ae733f0f765a22d0d1da32457f7ca7e3bdd23bb1cd452c56bc4e1f586cf978599c3f5acb835caeb569ff394eaec09d3259382f4954be
storybook: ^8.4.7
checksum: 10c0/0eb1854ddb6dbef1b32f89746944ee7a16db986403fe0a3712f43d39faa6335e0bce4ac21a8c20d09955ae73cccd1962f3b45037ab1144f61c1317d686e8695f
languageName: node
linkType: hard
"@storybook/addon-essentials@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/addon-essentials@npm:8.4.6"
"@storybook/addon-essentials@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/addon-essentials@npm:8.4.7"
dependencies:
"@storybook/addon-actions": "npm:8.4.6"
"@storybook/addon-backgrounds": "npm:8.4.6"
"@storybook/addon-controls": "npm:8.4.6"
"@storybook/addon-docs": "npm:8.4.6"
"@storybook/addon-highlight": "npm:8.4.6"
"@storybook/addon-measure": "npm:8.4.6"
"@storybook/addon-outline": "npm:8.4.6"
"@storybook/addon-toolbars": "npm:8.4.6"
"@storybook/addon-viewport": "npm:8.4.6"
"@storybook/addon-actions": "npm:8.4.7"
"@storybook/addon-backgrounds": "npm:8.4.7"
"@storybook/addon-controls": "npm:8.4.7"
"@storybook/addon-docs": "npm:8.4.7"
"@storybook/addon-highlight": "npm:8.4.7"
"@storybook/addon-measure": "npm:8.4.7"
"@storybook/addon-outline": "npm:8.4.7"
"@storybook/addon-toolbars": "npm:8.4.7"
"@storybook/addon-viewport": "npm:8.4.7"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/b8fb83e018fcb1e8cad04b371af5f8ce9933e3a500a78a889715ecfe4efd9faa52acce2d0f97fb04fe9ae0898e661112816c052bfe9b5f01189938b122055a44
storybook: ^8.4.7
checksum: 10c0/82ddd8424dfd5bf0ef44cee6a320f8395c63678bc0d4566307b2c68bd83c39f6bd447fb421681e3ab581c35c9d991207b01bebf20269c083931f581bb4651d6d
languageName: node
linkType: hard
"@storybook/addon-highlight@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-highlight@npm:8.4.6"
"@storybook/addon-highlight@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-highlight@npm:8.4.7"
dependencies:
"@storybook/global": "npm:^5.0.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/67a23a5e3b8f7740c7101e8fa886f3f9c6c61b6db3cb3430d2c805231f7ad170d2d926c12e7c9bfc4af327c5abac5b4155f4c0d70ea423b04704fe3def845acc
storybook: ^8.4.7
checksum: 10c0/2256b880d1f83c86c64287988bd4f4b76a8e1990f2a2a080a322994a9a8e553013fc21b7503c218ec394a880c1b72b131975e6eeadec6accb7eb35d3cb85a6ce
languageName: node
linkType: hard
"@storybook/addon-interactions@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/addon-interactions@npm:8.4.6"
"@storybook/addon-interactions@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/addon-interactions@npm:8.4.7"
dependencies:
"@storybook/global": "npm:^5.0.0"
"@storybook/instrumenter": "npm:8.4.6"
"@storybook/test": "npm:8.4.6"
"@storybook/instrumenter": "npm:8.4.7"
"@storybook/test": "npm:8.4.7"
polished: "npm:^4.2.2"
ts-dedent: "npm:^2.2.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/42e4bc2df354dba10217385687ac20fb355f4e1a2a7390812081d6b387151b67bca868211794e531c1e112dc4ce50c70dffa55c8f4338b0bd860d59363d58d5b
storybook: ^8.4.7
checksum: 10c0/5c35d2f33122f053568a746c36eb99eb1764ee990146ea374b0fc01defd3f0b33674d2758c027c760fe2966f8683193e8c414089c07e1136ffc562e3346ce479
languageName: node
linkType: hard
"@storybook/addon-links@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/addon-links@npm:8.4.6"
"@storybook/addon-links@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/addon-links@npm:8.4.7"
dependencies:
"@storybook/csf": "npm:^0.1.11"
"@storybook/global": "npm:^5.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6
storybook: ^8.4.7
peerDependenciesMeta:
react:
optional: true
checksum: 10c0/9360122d9c5370706a583526fb72efd0901d7e64c7467bfb4d832712cc41928d4fcfa397a53cfa17a1ae3875b8ef92ce6a10fb0bf0ce00149dc0d0eb1d66e27b
checksum: 10c0/475d3231ac6c6531cfa5d01e8816b90cbf51e993c1575fa7bf541540bf76af52d7f1087e929b87d771ce41ae4fd7762df1e25c9d8543200630f8618d85b16520
languageName: node
linkType: hard
"@storybook/addon-measure@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-measure@npm:8.4.6"
"@storybook/addon-measure@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-measure@npm:8.4.7"
dependencies:
"@storybook/global": "npm:^5.0.0"
tiny-invariant: "npm:^1.3.1"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/fd05b49fdb102a991fc696a0f75fde08d372b692778340ab2abc2c73fbd31a07dfa27a7a9d775dda7baaa9bd8a18972ed0bd86e9ce27948afb0305778f7b5a95
storybook: ^8.4.7
checksum: 10c0/a9e87c91cbcade2d0059cdc471e8ba479ad6d9dee0c2558c3b706e37d58b4cb3d986924ea0ff623aa791300ee2a8d2429e8fb3ef32eeec9d49861f8677815ac2
languageName: node
linkType: hard
"@storybook/addon-outline@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-outline@npm:8.4.6"
"@storybook/addon-outline@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-outline@npm:8.4.7"
dependencies:
"@storybook/global": "npm:^5.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/62600a9f4164a8d91118d37cd7be4f4dd871e849a156ba7728f463bc2cfc5a8a233df09055dd5e5733a042fde7a63b08616cb3c61b26c363c1e2d4ce20d92584
storybook: ^8.4.7
checksum: 10c0/13e8579ad1e9c8e338a66935331764351d9681e177469c7be72bc8383d6ab0441a783b2089ac3a730979d9a97c347800a47769b1f1ab5b4dfd7fc31f29e1709f
languageName: node
linkType: hard
"@storybook/addon-toolbars@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-toolbars@npm:8.4.6"
"@storybook/addon-toolbars@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-toolbars@npm:8.4.7"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/6525e71aaa3870ae97d407b662323022ade98859f89975110f5fb4a1d3f34b6c918d47fcc8a6a271f4a77acfcaadc963a846a83ebc6c748b37df50422ad60e7e
storybook: ^8.4.7
checksum: 10c0/1c315d5ad07291f35ad780ef69fbd6570a582c008ab911cf14bff84061546b9ea1373d1127213844652d73a47c3011d28c1ad08d465fc120969c133dabfe7638
languageName: node
linkType: hard
"@storybook/addon-viewport@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-viewport@npm:8.4.6"
"@storybook/addon-viewport@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/addon-viewport@npm:8.4.7"
dependencies:
memoizerific: "npm:^1.11.3"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/824438cc44a45f90748ac5f20ac148a36d975a94fa89504a583e0e1188de8c574e042ad3cd537bc16ddb30d4e44e90f5a63263239b13419aec5334e2ece18cd0
storybook: ^8.4.7
checksum: 10c0/4dec3b59be1f3b99d3c9eaab695a7e346d975b772f6691f8286005d78a13a204c5680c6c8733ae83060c7639b56efed9f3580cee7413834ac6595b56345183ef
languageName: node
linkType: hard
"@storybook/blocks@npm:8.4.6, @storybook/blocks@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/blocks@npm:8.4.6"
"@storybook/blocks@npm:8.4.7, @storybook/blocks@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/blocks@npm:8.4.7"
dependencies:
"@storybook/csf": "npm:^0.1.11"
"@storybook/icons": "npm:^1.2.12"
@ -5342,36 +5342,36 @@ __metadata:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6
storybook: ^8.4.7
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
checksum: 10c0/36d79c3aeb3d27f4ba966d62302e13fc17fd7b450dbfbcf538adfc6df3cfecb13c92f9d2542871fa747a77d7c770e413b358623049135355fb01454d6eb52d9a
checksum: 10c0/1cb87811f9c7bad087dca752fb0d6483c237cb5776abea59cb555d8fce9ca14f4d5487725f5d8679a49f7e3f38bbe84189703498a31f2a9aa306f9fb3c8e65c8
languageName: node
linkType: hard
"@storybook/builder-vite@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/builder-vite@npm:8.4.6"
"@storybook/builder-vite@npm:8.4.7, @storybook/builder-vite@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/builder-vite@npm:8.4.7"
dependencies:
"@storybook/csf-plugin": "npm:8.4.6"
"@storybook/csf-plugin": "npm:8.4.7"
browser-assert: "npm:^1.2.1"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.6
storybook: ^8.4.7
vite: ^4.0.0 || ^5.0.0 || ^6.0.0
checksum: 10c0/36998ffea04023a9f634ebbafe0d1ab3bd3e7c7fec8e8e6c4caef3ce0c94ce01fa44f332f40d0053edb788548f95096baf8561cd35c23fe3c9bcfd872f74f631
checksum: 10c0/138651b9042356972580a121eb3116c745f9fbc8b188ae0a5e543070dc54fcb6c1f14d35bc0cd1294ee763993a5f0e3a30cbe92508e74d183fed04a4d4125591
languageName: node
linkType: hard
"@storybook/components@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/components@npm:8.4.6"
"@storybook/components@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/components@npm:8.4.7"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/1622b2f12b6d18e5c495a623deb2930888b3e8b173a271cbe42a7cbd6e14e80b736c57792ea97d5269dff0e6c0db40385d3ea80ab6e46d4cb6e104aee6cac6bc
checksum: 10c0/7c1eb12fe2310a306f3c2f77a499c3a0caeb4694d4af8dde418f3b2d2ac8a3549b3f56cdc4629b9c15d79177c72e8668dd781a71bf257948f799b0e9cba201fa
languageName: node
linkType: hard
@ -5393,9 +5393,9 @@ __metadata:
languageName: node
linkType: hard
"@storybook/core@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/core@npm:8.4.6"
"@storybook/core@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/core@npm:8.4.7"
dependencies:
"@storybook/csf": "npm:^0.1.11"
better-opn: "npm:^3.0.2"
@ -5413,18 +5413,18 @@ __metadata:
peerDependenciesMeta:
prettier:
optional: true
checksum: 10c0/1e30268eec18458dd78ed4b97fb12ac47b2c3cb41ffcbe9e9f5934b3f0c83b0bfcb0c0d508926344779383cc5260f992dcd534ffffab3f05425c7cee8c90687c
checksum: 10c0/0943ea7cd092739834ae4347cb46c66aa1c238ee9494af60345364f11568ee60d6290875a593808cd7aeb79715ae27365c2448e6ae5c644e316cd194af184755
languageName: node
linkType: hard
"@storybook/csf-plugin@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/csf-plugin@npm:8.4.6"
"@storybook/csf-plugin@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/csf-plugin@npm:8.4.7"
dependencies:
unplugin: "npm:^1.3.1"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/d771f36ee768c6ff62ecd930c6ff64a4ba45bdbb7f7fb41e5f4ffd02204e3f54b17ed091049b265a6d371922bf599bfe749eb9deabfcd7e2b4fb5a5444655241
storybook: ^8.4.7
checksum: 10c0/da38e2422e474e323e237e569b3dd678af77d975a4a08fa36108e66c9228858e510246628e18b013bd859a4e674c1a3d0072952a71dac0d7058e03e7c3417b3f
languageName: node
linkType: hard
@ -5454,24 +5454,24 @@ __metadata:
languageName: node
linkType: hard
"@storybook/instrumenter@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/instrumenter@npm:8.4.6"
"@storybook/instrumenter@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/instrumenter@npm:8.4.7"
dependencies:
"@storybook/global": "npm:^5.0.0"
"@vitest/utils": "npm:^2.1.1"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/602017872236124dc9dfa77d6bc2c5987d540063f15c7ace83bf91060d9343fdbe113a61cba44e17cae2247aeeb69875ebf45ff66ce9c28d364d2d3638eb3ec8
storybook: ^8.4.7
checksum: 10c0/bc0865fed7f3c8242cd97978257e3d48f1880ad01e9794cc45122c4bcc7cf4a498c6ff8deebffcc70332b4a096e98b00e695ac152e40d0ef2c23160009c86f5d
languageName: node
linkType: hard
"@storybook/manager-api@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/manager-api@npm:8.4.6"
"@storybook/manager-api@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/manager-api@npm:8.4.7"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/5921ec72df0be765bd398aa906186c9b121a8b3415a7e1a10014a8d17c44aec386b59de3d240017bfc925be00c40a4da8d26991b5fa39023f23ba8efe1b0d58e
checksum: 10c0/a3aeed441a2cca1a8fac73336a853b389a00a1e7dbbbbcd54492a90f2f12f86e976235fd1272f27a606532fb7e0f82dec3f7ecd1f2b87b03ffa74b667830152a
languageName: node
linkType: hard
@ -5484,34 +5484,34 @@ __metadata:
languageName: node
linkType: hard
"@storybook/preview-api@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/preview-api@npm:8.4.6"
"@storybook/preview-api@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/preview-api@npm:8.4.7"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/63967f4813c75e410634bff20189b5a670a061cfeeaa601ec07f0de82e2b4955af292836030d5a8432c3c7e48968285e121ed2bb55d2b5c70d17dbb4ada3c051
checksum: 10c0/86e8dd8e46b20a4cab99655ded093a76ae5a2b2b9ab03af57292022c8143d76e0f76a137f8768b8f6847fd1b522abf3dee8504f0ba5ff16b5779120d3875967c
languageName: node
linkType: hard
"@storybook/react-dom-shim@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/react-dom-shim@npm:8.4.6"
"@storybook/react-dom-shim@npm:8.4.7":
version: 8.4.7
resolution: "@storybook/react-dom-shim@npm:8.4.7"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6
checksum: 10c0/b97c6faa3adc3efe1b7b6f5e38476e040c0a988b14db68e368d704c68f3f4d4bf7866b36607c118a0483242921b34944b5f5f72614d9852476476f6ead462e5c
storybook: ^8.4.7
checksum: 10c0/5db1306c844a36264587836860d17f3fd44e5981a2417e66ccb0699d2b05364736f29df2ebc605ae19a7f7b9b9d6a19845771c3052b167ce27702e20337cd334
languageName: node
linkType: hard
"@storybook/react-vite@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/react-vite@npm:8.4.6"
"@storybook/react-vite@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/react-vite@npm:8.4.7"
dependencies:
"@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.4.2"
"@rollup/pluginutils": "npm:^5.0.2"
"@storybook/builder-vite": "npm:8.4.6"
"@storybook/react": "npm:8.4.6"
"@storybook/builder-vite": "npm:8.4.7"
"@storybook/react": "npm:8.4.7"
find-up: "npm:^5.0.0"
magic-string: "npm:^0.30.0"
react-docgen: "npm:^7.0.0"
@ -5520,61 +5520,61 @@ __metadata:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6
storybook: ^8.4.7
vite: ^4.0.0 || ^5.0.0 || ^6.0.0
checksum: 10c0/9f81a19461dbbf11932a13f8fb611dbcd95fbfa695ee5536daf7e078bf0feb5ddda2738606073826131e3fee710e230dce9042e3f7f985203392376aa8407643
checksum: 10c0/105d967cc7aa9168b60723b6325d1d011836a2d6a5b9a4e45a13a64ccae26b7794fb3eb70042ab4b4af2af705078e34250c834bfe305c090b3dd22cf67301978
languageName: node
linkType: hard
"@storybook/react@npm:8.4.6, @storybook/react@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/react@npm:8.4.6"
"@storybook/react@npm:8.4.7, @storybook/react@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/react@npm:8.4.7"
dependencies:
"@storybook/components": "npm:8.4.6"
"@storybook/components": "npm:8.4.7"
"@storybook/global": "npm:^5.0.0"
"@storybook/manager-api": "npm:8.4.6"
"@storybook/preview-api": "npm:8.4.6"
"@storybook/react-dom-shim": "npm:8.4.6"
"@storybook/theming": "npm:8.4.6"
"@storybook/manager-api": "npm:8.4.7"
"@storybook/preview-api": "npm:8.4.7"
"@storybook/react-dom-shim": "npm:8.4.7"
"@storybook/theming": "npm:8.4.7"
peerDependencies:
"@storybook/test": 8.4.6
"@storybook/test": 8.4.7
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6
storybook: ^8.4.7
typescript: ">= 4.2.x"
peerDependenciesMeta:
"@storybook/test":
optional: true
typescript:
optional: true
checksum: 10c0/1441f8ab3be91757647c6b1a05eb1ef0d78a454ffd14b01a14fdde00e92a8be8fc7c8408c4670b46bc20a5a04995514f0890e98ed6ee35c362ff36141da02f02
checksum: 10c0/9ca588446171491458e9adb5f9cf69b17517feddb4edd876da495843a45fa48a9c9272d4823090546e24a78dd7a93f1dcedef96257054383eb5678bfae6ccc09
languageName: node
linkType: hard
"@storybook/test@npm:8.4.6, @storybook/test@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/test@npm:8.4.6"
"@storybook/test@npm:8.4.7, @storybook/test@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/test@npm:8.4.7"
dependencies:
"@storybook/csf": "npm:^0.1.11"
"@storybook/global": "npm:^5.0.0"
"@storybook/instrumenter": "npm:8.4.6"
"@storybook/instrumenter": "npm:8.4.7"
"@testing-library/dom": "npm:10.4.0"
"@testing-library/jest-dom": "npm:6.5.0"
"@testing-library/user-event": "npm:14.5.2"
"@vitest/expect": "npm:2.0.5"
"@vitest/spy": "npm:2.0.5"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/fbf7c2ac7773a7fe18145876eb67491ce90b000ba5f8e364a319569e56d56e706fdd1c7ef24d3ab2ffa3dfcdb92377d8050c8ffbd457d2d8b613aba2a4845a04
storybook: ^8.4.7
checksum: 10c0/4b100eacdca6d016a08358b1bf4c17f36450dffc9005557e0184814e546e71d200afccfb652fd2d45404fbd15e75f61fb4b93d869694249769ca919a0a2111f1
languageName: node
linkType: hard
"@storybook/theming@npm:8.4.6, @storybook/theming@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/theming@npm:8.4.6"
"@storybook/theming@npm:8.4.7, @storybook/theming@npm:^8.4.7":
version: 8.4.7
resolution: "@storybook/theming@npm:8.4.7"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/7d9c8e5ef2c1d974cd5258301350a2345890326e7be7a5ed6bdd0db70fd1648c0bbb8ee1d905f8e66fa57b75c47aefe7ec9772ec0bfb9691d127dcc19286e4c9
checksum: 10c0/20a4975478063cea616ce6ab6b1e9ec181af1424280678ed74dc5afc15b828c043e843696a1643601331c4fd266169ec4bcc5bb43fd2f1f3c01c0e21443a658a
languageName: node
linkType: hard
@ -20174,11 +20174,11 @@ __metadata:
languageName: node
linkType: hard
"storybook@npm:^8.4.6":
version: 8.4.6
resolution: "storybook@npm:8.4.6"
"storybook@npm:^8.4.7":
version: 8.4.7
resolution: "storybook@npm:8.4.7"
dependencies:
"@storybook/core": "npm:8.4.6"
"@storybook/core": "npm:8.4.7"
peerDependencies:
prettier: ^2 || ^3
peerDependenciesMeta:
@ -20188,7 +20188,7 @@ __metadata:
getstorybook: ./bin/index.cjs
sb: ./bin/index.cjs
storybook: ./bin/index.cjs
checksum: 10c0/e15249718c1efab3d3d05f3152df28fc8f7e2e988bf7414cd4abf2adfb5d6c3b802f05dad5be0521c30d0ba43e55abf516e6f874b0671e0d1e84a7096cb47d3d
checksum: 10c0/795b79950b88b41ee0158fe2e2583a8ce97ff843c054f91e3c55310967b9e5c4e4d72814773380b543c33bd6d57ce6b5f377ce93ce73962e803b250a751be37c
languageName: node
linkType: hard
@ -21954,14 +21954,15 @@ __metadata:
"@observablehq/plot": "npm:^0.6.16"
"@react-hook/resize-observer": "npm:^2.0.2"
"@rollup/plugin-node-resolve": "npm:^15.3.0"
"@storybook/addon-essentials": "npm:^8.4.6"
"@storybook/addon-interactions": "npm:^8.4.6"
"@storybook/addon-links": "npm:^8.4.6"
"@storybook/blocks": "npm:^8.4.6"
"@storybook/react": "npm:^8.4.6"
"@storybook/react-vite": "npm:^8.4.6"
"@storybook/test": "npm:^8.4.6"
"@storybook/theming": "npm:^8.4.6"
"@storybook/addon-essentials": "npm:^8.4.7"
"@storybook/addon-interactions": "npm:^8.4.7"
"@storybook/addon-links": "npm:^8.4.7"
"@storybook/blocks": "npm:^8.4.7"
"@storybook/builder-vite": "npm:^8.4.7"
"@storybook/react": "npm:^8.4.7"
"@storybook/react-vite": "npm:^8.4.7"
"@storybook/test": "npm:^8.4.7"
"@storybook/theming": "npm:^8.4.7"
"@table-nav/core": "npm:^0.0.7"
"@table-nav/react": "npm:^0.0.7"
"@tanstack/react-table": "npm:^8.20.5"
@ -22039,7 +22040,7 @@ __metadata:
sharp: "npm:^0.33.5"
shell-quote: "npm:^1.8.2"
sprintf-js: "npm:^1.1.3"
storybook: "npm:^8.4.6"
storybook: "npm:^8.4.7"
storybook-dark-mode: "npm:^4.0.2"
throttle-debounce: "npm:^5.0.2"
tinycolor2: "npm:^1.6.0"