mirror of
https://github.com/wavetermdev/waveterm
synced 2026-04-21 14:37:16 +00:00
v0.13 Release Notes, Docs Updates, Onboarding Updates (#2642)
This commit is contained in:
parent
323db7fa04
commit
ea68aec9f4
29 changed files with 555 additions and 141 deletions
19
ROADMAP.md
19
ROADMAP.md
|
|
@ -13,6 +13,10 @@ Wave Terminal's AI assistant is already powerful and continues to evolve. Here's
|
|||
### AI Provider Support
|
||||
|
||||
- ✅ OpenAI (including gpt-5 and gpt-5-mini models)
|
||||
- ✅ Google Gemini (v0.13)
|
||||
- ✅ OpenRouter and custom OpenAI-compatible endpoints (v0.13)
|
||||
- ✅ Azure OpenAI (modern and legacy APIs) (v0.13)
|
||||
- ✅ Local AI models via Ollama, LM Studio, vLLM, and other OpenAI-compatible servers (v0.13)
|
||||
|
||||
### Context & Input
|
||||
|
||||
|
|
@ -32,33 +36,28 @@ Wave Terminal's AI assistant is already powerful and continues to evolve. Here's
|
|||
|
||||
### AI Configuration & Flexibility
|
||||
|
||||
- 🔷 BYOK (Bring Your Own Key) - Use your own API keys for any supported provider
|
||||
- ✅ BYOK (Bring Your Own Key) - Use your own API keys for any supported provider (v0.13)
|
||||
- ✅ Local AI agents - Run AI models locally on your machine (v0.13)
|
||||
- 🔧 Enhanced provider configuration options
|
||||
- 🔷 Context (add markdown files to give persistent system context)
|
||||
|
||||
### Expanded Provider Support
|
||||
|
||||
Top priorities are Claude (for better coding support), and the OpenAI Completions API which will allow us to interface with
|
||||
many more local/open models.
|
||||
|
||||
- 🔷 Anthropic Claude - Full integration with extended thinking and tool use
|
||||
- 🔷 OpenAI Completions API - Support for older model formats
|
||||
- 🤞 Google Gemini - Complete integration
|
||||
- 🤞 Local AI agents - Run AI models locally on your machine
|
||||
|
||||
### Advanced AI Tools
|
||||
|
||||
#### File Operations
|
||||
|
||||
- 🔧 AI file writing with intelligent diff previews
|
||||
- 🔧 Rollback support for AI-made changes
|
||||
- ✅ AI file writing with intelligent diff previews
|
||||
- ✅ Rollback support for AI-made changes
|
||||
- 🔷 Multi-file editing workflows
|
||||
- 🔷 Safe file modification patterns
|
||||
|
||||
#### Terminal Command Execution
|
||||
|
||||
- 🔧 Execute commands directly from AI
|
||||
- 🔧 Intelligent terminal state detection
|
||||
- ✅ Intelligent terminal state detection
|
||||
- 🔧 Command result capture and parsing
|
||||
|
||||
### Remote & Advanced Capabilities
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/wavetermdev/waveterm/pkg/panichandler"
|
||||
"github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
|
||||
"github.com/wavetermdev/waveterm/pkg/remote/fileshare/wshfs"
|
||||
"github.com/wavetermdev/waveterm/pkg/secretstore"
|
||||
"github.com/wavetermdev/waveterm/pkg/service"
|
||||
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
||||
"github.com/wavetermdev/waveterm/pkg/telemetry/telemetrydata"
|
||||
|
|
@ -224,18 +225,25 @@ func updateTelemetryCounts(lastCounts telemetrydata.TEventProps) telemetrydata.T
|
|||
customWidgets := fullConfig.CountCustomWidgets()
|
||||
customAIPresets := fullConfig.CountCustomAIPresets()
|
||||
customSettings := wconfig.CountCustomSettings()
|
||||
customAIModes := fullConfig.CountCustomAIModes()
|
||||
|
||||
props.UserSet = &telemetrydata.TEventUserProps{
|
||||
SettingsCustomWidgets: customWidgets,
|
||||
SettingsCustomAIPresets: customAIPresets,
|
||||
SettingsCustomSettings: customSettings,
|
||||
SettingsCustomAIModes: customAIModes,
|
||||
}
|
||||
|
||||
secretsCount, err := secretstore.CountSecrets()
|
||||
if err == nil {
|
||||
props.UserSet.SettingsSecretsCount = secretsCount
|
||||
}
|
||||
|
||||
if utilfn.CompareAsMarshaledJson(props, lastCounts) {
|
||||
return lastCounts
|
||||
}
|
||||
tevent := telemetrydata.MakeTEvent("app:counts", props)
|
||||
err := telemetry.RecordTEvent(ctx, tevent)
|
||||
err = telemetry.RecordTEvent(ctx, tevent)
|
||||
if err != nil {
|
||||
log.Printf("error recording counts tevent: %v\n", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,33 @@ sidebar_position: 200
|
|||
|
||||
# Release Notes
|
||||
|
||||
### v0.13.0 — Dec 8, 2025
|
||||
|
||||
**Wave v0.13 Brings Local AI Support, BYOK, and Unified Configuration**
|
||||
|
||||
Wave v0.13 is a major release that opens up Wave AI to local models, third-party providers, and bring-your-own-key (BYOK) configurations. This release also includes a completely redesigned configuration system and several terminal improvements.
|
||||
|
||||
**Local AI & BYOK Support:**
|
||||
- **OpenAI-Compatible API** - Wave now supports any provider or local server using the `/v1/chat/completions` endpoint, enabling use of Ollama, LM Studio, vLLM, OpenRouter, and countless other local and hosted models
|
||||
- **Google Gemini Integration** - Native support for Google's Gemini models with a dedicated API adapter
|
||||
- **Provider Presets** - Simplified configuration with built-in presets for OpenAI, OpenRouter, Google, Azure, and custom endpoints
|
||||
- **Multiple AI Modes** - Easily switch between different models and providers with a unified interface
|
||||
- See the new [Wave AI Modes documentation](https://docs.waveterm.dev/waveai-modes) for configuration examples and setup guides
|
||||
|
||||
**Unified Configuration Widget:**
|
||||
- **New Config Interface** - Replaced the basic JSON editor with a dedicated configuration widget accessible from the sidebar
|
||||
- **Better Organization** - Browse and edit different configuration types (general settings, AI modes, secrets) with improved validation and error handling
|
||||
- **Integrated Secrets Management** - Access Wave's secret store directly from the config widget for secure credential management
|
||||
|
||||
**Terminal Improvements:**
|
||||
- **Bracketed Paste Mode** - Now enabled by default to improve multi-line paste behavior and compatibility with tools like Claude Code
|
||||
- **Windows Paste Fix** - Ctrl+V now works as a standard paste accelerator on Windows
|
||||
- **SSH Password Management** - Store SSH connection passwords in Wave's secret store to avoid re-typing credentials
|
||||
|
||||
**Other Changes:**
|
||||
- Package updates and dependency upgrades
|
||||
- Various bug fixes and stability improvements
|
||||
|
||||
### v0.12.5 — Nov 24, 2025
|
||||
|
||||
Quick patch release to fix paste behavior on Linux (prevent raw HTML from getting pasted to the terminal).
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { VersionBadge } from "@site/src/components/versionbadge";
|
|||
|
||||
# Secrets
|
||||
|
||||
<VersionBadge version="v0.13" />
|
||||
<VersionBadge version="v0.13" noLeftMargin={true} />
|
||||
|
||||
Wave Terminal provides a secure way to store sensitive information like passwords, API keys, and tokens. Secrets are stored encrypted in your system's native keychain (macOS Keychain, Windows Credential Manager, or Linux Secret Service), ensuring your sensitive data remains protected.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ id: "waveai-modes"
|
|||
title: "Wave AI (Local Models + BYOK)"
|
||||
---
|
||||
|
||||
import { VersionBadge } from "@site/src/components/versionbadge";
|
||||
|
||||
<VersionBadge version="v0.13" noLeftMargin={true}/>
|
||||
|
||||
Wave AI supports custom AI modes that allow you to use local models, custom API endpoints, and alternative AI providers. This gives you complete control over which models and providers you use with Wave's AI features.
|
||||
|
||||
## Configuration Overview
|
||||
|
|
@ -15,7 +19,7 @@ AI modes are configured in `~/.config/waveterm/waveai.json`.
|
|||
2. Select "Settings" from the menu
|
||||
3. Choose "Wave AI Modes" from the settings sidebar
|
||||
|
||||
**Or edit from the command line:**
|
||||
**Or launch from the command line:**
|
||||
```bash
|
||||
wsh editconfig waveai.json
|
||||
```
|
||||
|
|
@ -43,83 +47,51 @@ Wave AI supports the following API types:
|
|||
- **`openai-responses`**: Uses the `/v1/responses` endpoint (modern API for GPT-5+ models)
|
||||
- **`google-gemini`**: Google's Gemini API format (automatically set when using `ai:provider: "google"`, not typically used directly)
|
||||
|
||||
## Configuration Structure
|
||||
## Global Wave AI Settings
|
||||
|
||||
### Minimal Configuration (with Provider)
|
||||
You can configure global Wave AI behavior in your Wave Terminal settings (separate from the mode configurations in `waveai.json`).
|
||||
|
||||
```json
|
||||
{
|
||||
"mode-key": {
|
||||
"display:name": "Qwen (OpenRouter)",
|
||||
"ai:provider": "openrouter",
|
||||
"ai:model": "qwen/qwen-2.5-coder-32b-instruct"
|
||||
}
|
||||
}
|
||||
```
|
||||
### Setting a Default AI Mode
|
||||
|
||||
### Full Configuration (all fields)
|
||||
After configuring a local model or custom mode, you can make it the default by setting `waveai:defaultmode` in your Wave Terminal settings.
|
||||
|
||||
```json
|
||||
{
|
||||
"mode-key": {
|
||||
"display:name": "Display Name",
|
||||
"display:order": 1,
|
||||
"display:icon": "icon-name",
|
||||
"display:description": "Full description",
|
||||
"ai:provider": "custom",
|
||||
"ai:apitype": "openai-chat",
|
||||
"ai:model": "model-name",
|
||||
"ai:thinkinglevel": "medium",
|
||||
"ai:endpoint": "http://localhost:11434/v1/chat/completions",
|
||||
"ai:azureapiversion": "v1",
|
||||
"ai:apitoken": "your-token",
|
||||
"ai:apitokensecretname": "PROVIDER_KEY",
|
||||
"ai:azureresourcename": "your-resource",
|
||||
"ai:azuredeployment": "your-deployment",
|
||||
"ai:capabilities": ["tools", "images", "pdfs"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Reference
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `display:name` | Yes | Name shown in the AI mode selector |
|
||||
| `display:order` | No | Sort order in the selector (lower numbers first) |
|
||||
| `display:icon` | No | Icon identifier for the mode |
|
||||
| `display:description` | No | Full description of the mode |
|
||||
| `ai:provider` | No | Provider preset: `openai`, `openrouter`, `google`, `azure`, `azure-legacy`, `custom` |
|
||||
| `ai:apitype` | No | API type: `openai-chat`, `openai-responses`, or `google-gemini` (defaults to `openai-chat` if not specified) |
|
||||
| `ai:model` | No | Model identifier (required for most providers) |
|
||||
| `ai:thinkinglevel` | No | Thinking level: `low`, `medium`, or `high` |
|
||||
| `ai:endpoint` | No | *Full* API endpoint URL (auto-set by provider when available) |
|
||||
| `ai:azureapiversion` | No | Azure API version (for `azure-legacy` provider, defaults to `2025-04-01-preview`) |
|
||||
| `ai:apitoken` | No | API key/token (not recommended - use secrets instead) |
|
||||
| `ai:apitokensecretname` | No | Name of secret containing API token (auto-set by provider) |
|
||||
| `ai:azureresourcename` | No | Azure resource name (for Azure providers) |
|
||||
| `ai:azuredeployment` | No | Azure deployment name (for `azure-legacy` provider) |
|
||||
| `ai:capabilities` | No | Array of supported capabilities: `"tools"`, `"images"`, `"pdfs"` |
|
||||
| `waveai:cloud` | No | Internal - for Wave Cloud AI configuration only |
|
||||
| `waveai:premium` | No | Internal - for Wave Cloud AI configuration only |
|
||||
|
||||
### AI Capabilities
|
||||
|
||||
The `ai:capabilities` field specifies what features the AI mode supports:
|
||||
|
||||
- **`tools`** - Enables AI tool usage for file reading/writing, shell integration, and widget interaction
|
||||
- **`images`** - Allows image attachments in chat (model can view uploaded images)
|
||||
- **`pdfs`** - Allows PDF file attachments in chat (model can read PDF content)
|
||||
|
||||
**Provider-specific behavior:**
|
||||
- **OpenAI and Google providers**: Capabilities are automatically configured based on the model. You don't need to specify them.
|
||||
- **OpenRouter, Azure, Azure-Legacy, and Custom providers**: You must manually specify capabilities based on your model's features.
|
||||
|
||||
:::warning
|
||||
If you don't include `"tools"` in the `ai:capabilities` array, the AI model will not be able to interact with your Wave terminal widgets, read/write files, or execute commands. Most AI modes should include `"tools"` for the best Wave experience.
|
||||
:::important
|
||||
Use the **mode key** (the key in your `waveai.json` configuration), not the display name. For example, use `"ollama-llama"` (the key), not `"Ollama - Llama 3.3"` (the display name).
|
||||
:::
|
||||
|
||||
Most models support `tools` and can benefit from it. Vision-capable models should include `images`. Not all models support PDFs, so only include `pdfs` if your model can process them.
|
||||
**Using the settings command:**
|
||||
```bash
|
||||
wsh setconfig waveai:defaultmode="ollama-llama"
|
||||
```
|
||||
|
||||
**Or edit settings.json directly:**
|
||||
1. Click the settings (gear) icon in the widget bar
|
||||
2. Select "Settings" from the menu
|
||||
3. Add the `waveai:defaultmode` key to your settings.json:
|
||||
```json
|
||||
"waveai:defaultmode": "ollama-llama"
|
||||
```
|
||||
|
||||
This will make the specified mode the default selection when opening Wave AI features.
|
||||
|
||||
### Hiding Wave Cloud Modes
|
||||
|
||||
If you prefer to use only your local or custom models and want to hide Wave's cloud AI modes from the mode dropdown, set `waveai:showcloudmodes` to `false`:
|
||||
|
||||
**Using the settings command:**
|
||||
```bash
|
||||
wsh setconfig waveai:showcloudmodes=false
|
||||
```
|
||||
|
||||
**Or edit settings.json directly:**
|
||||
1. Click the settings (gear) icon in the widget bar
|
||||
2. Select "Settings" from the menu
|
||||
3. Add the `waveai:showcloudmodes` key to your settings.json:
|
||||
```json
|
||||
"waveai:showcloudmodes": false
|
||||
```
|
||||
|
||||
This will hide Wave's built-in cloud AI modes, showing only your custom configured modes.
|
||||
|
||||
## Local Model Examples
|
||||
|
||||
|
|
@ -132,7 +104,7 @@ Most models support `tools` and can benefit from it. Vision-capable models shoul
|
|||
"ollama-llama": {
|
||||
"display:name": "Ollama - Llama 3.3",
|
||||
"display:order": 1,
|
||||
"display:icon": "llama",
|
||||
"display:icon": "microchip",
|
||||
"display:description": "Local Llama 3.3 70B model via Ollama",
|
||||
"ai:apitype": "openai-chat",
|
||||
"ai:model": "llama3.3:70b",
|
||||
|
|
@ -420,3 +392,81 @@ If you get "model not found" errors:
|
|||
- Use `openai-chat` for Ollama, LM Studio, custom endpoints, and most cloud providers
|
||||
- Use `openai-responses` for newer OpenAI models (GPT-5+) or when your provider specifically requires it
|
||||
- Provider presets automatically set the correct API type when needed
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### Minimal Configuration (with Provider)
|
||||
|
||||
```json
|
||||
{
|
||||
"mode-key": {
|
||||
"display:name": "Qwen (OpenRouter)",
|
||||
"ai:provider": "openrouter",
|
||||
"ai:model": "qwen/qwen-2.5-coder-32b-instruct"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Full Configuration (all fields)
|
||||
|
||||
```json
|
||||
{
|
||||
"mode-key": {
|
||||
"display:name": "Display Name",
|
||||
"display:order": 1,
|
||||
"display:icon": "icon-name",
|
||||
"display:description": "Full description",
|
||||
"ai:provider": "custom",
|
||||
"ai:apitype": "openai-chat",
|
||||
"ai:model": "model-name",
|
||||
"ai:thinkinglevel": "medium",
|
||||
"ai:endpoint": "http://localhost:11434/v1/chat/completions",
|
||||
"ai:azureapiversion": "v1",
|
||||
"ai:apitoken": "your-token",
|
||||
"ai:apitokensecretname": "PROVIDER_KEY",
|
||||
"ai:azureresourcename": "your-resource",
|
||||
"ai:azuredeployment": "your-deployment",
|
||||
"ai:capabilities": ["tools", "images", "pdfs"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Reference
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `display:name` | Yes | Name shown in the AI mode selector |
|
||||
| `display:order` | No | Sort order in the selector (lower numbers first) |
|
||||
| `display:icon` | No | Icon identifier for the mode (can use any [FontAwesome icon](https://fontawesome.com/search), use the name without the "fa-" prefix). Default is "sparkles" |
|
||||
| `display:description` | No | Full description of the mode |
|
||||
| `ai:provider` | No | Provider preset: `openai`, `openrouter`, `google`, `azure`, `azure-legacy`, `custom` |
|
||||
| `ai:apitype` | No | API type: `openai-chat`, `openai-responses`, or `google-gemini` (defaults to `openai-chat` if not specified) |
|
||||
| `ai:model` | No | Model identifier (required for most providers) |
|
||||
| `ai:thinkinglevel` | No | Thinking level: `low`, `medium`, or `high` |
|
||||
| `ai:endpoint` | No | *Full* API endpoint URL (auto-set by provider when available) |
|
||||
| `ai:azureapiversion` | No | Azure API version (for `azure-legacy` provider, defaults to `2025-04-01-preview`) |
|
||||
| `ai:apitoken` | No | API key/token (not recommended - use secrets instead) |
|
||||
| `ai:apitokensecretname` | No | Name of secret containing API token (auto-set by provider) |
|
||||
| `ai:azureresourcename` | No | Azure resource name (for Azure providers) |
|
||||
| `ai:azuredeployment` | No | Azure deployment name (for `azure-legacy` provider) |
|
||||
| `ai:capabilities` | No | Array of supported capabilities: `"tools"`, `"images"`, `"pdfs"` |
|
||||
| `waveai:cloud` | No | Internal - for Wave Cloud AI configuration only |
|
||||
| `waveai:premium` | No | Internal - for Wave Cloud AI configuration only |
|
||||
|
||||
### AI Capabilities
|
||||
|
||||
The `ai:capabilities` field specifies what features the AI mode supports:
|
||||
|
||||
- **`tools`** - Enables AI tool usage for file reading/writing, shell integration, and widget interaction
|
||||
- **`images`** - Allows image attachments in chat (model can view uploaded images)
|
||||
- **`pdfs`** - Allows PDF file attachments in chat (model can read PDF content)
|
||||
|
||||
**Provider-specific behavior:**
|
||||
- **OpenAI and Google providers**: Capabilities are automatically configured based on the model. You don't need to specify them.
|
||||
- **OpenRouter, Azure, Azure-Legacy, and Custom providers**: You must manually specify capabilities based on your model's features.
|
||||
|
||||
:::warning
|
||||
If you don't include `"tools"` in the `ai:capabilities` array, the AI model will not be able to interact with your Wave terminal widgets, read/write files, or execute commands. Most AI modes should include `"tools"` for the best Wave experience.
|
||||
:::
|
||||
|
||||
Most models support `tools` and can benefit from it. Vision-capable models should include `images`. Not all models support PDFs, so only include `pdfs` if your model can process them.
|
||||
|
|
|
|||
|
|
@ -74,15 +74,32 @@ Supports text files, images, PDFs, and directories. Use `-n` for new chat, `-s`
|
|||
File system operations require explicit approval. You control all file access.
|
||||
:::
|
||||
|
||||
## Local Models & BYOK
|
||||
|
||||
Wave AI supports using your own AI models and API keys:
|
||||
|
||||
- **Local Models**: Run AI models locally with [Ollama](https://ollama.ai), [LM Studio](https://lmstudio.ai), [vLLM](https://docs.vllm.ai), and other OpenAI-compatible servers
|
||||
- **BYOK (Bring Your Own Key)**: Use your own API keys with OpenAI, OpenRouter, Google AI (Gemini), Azure OpenAI, and other cloud providers
|
||||
- **Multiple Modes**: Configure and switch between multiple AI providers and models
|
||||
- **Privacy**: Keep your data local or use your preferred cloud provider
|
||||
|
||||
See the [**Local Models & BYOK guide**](./waveai-modes.mdx) for complete configuration instructions, examples, and troubleshooting.
|
||||
|
||||
## Privacy
|
||||
|
||||
**Default Wave AI Service:**
|
||||
- Messages are proxied through the Wave Cloud AI service (powered by OpenAI's APIs). Please refer to OpenAI's privacy policy for details on how they handle your data.
|
||||
- Wave does not store your chats, attachments, or use them for training
|
||||
- Usage counters included in anonymous telemetry
|
||||
- File access requires explicit approval
|
||||
|
||||
**Local Models & BYOK:**
|
||||
- When using local models, your chat data never leaves your machine
|
||||
- When using BYOK with cloud providers, requests are sent directly to your chosen provider
|
||||
- Refer to your provider's privacy policy for details on how they handle your data
|
||||
|
||||
:::info Under Active Development
|
||||
Wave AI is in active beta with included AI credits while we refine the experience. BYOK will be available once we've stabilized core features and gathered feedback on what works best. Share feedback in our [Discord](https://discord.gg/XfvZ334gwU).
|
||||
Wave AI is in active beta with included AI credits while we refine the experience. Share feedback in our [Discord](https://discord.gg/XfvZ334gwU).
|
||||
|
||||
**Coming Soon:**
|
||||
- **Remote File Access**: Read files on SSH-connected systems
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.version-badge.no-left-margin {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .version-badge {
|
||||
background-color: var(--ifm-color-primary-dark);
|
||||
color: var(--ifm-background-color);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ import "./versionbadge.css";
|
|||
|
||||
interface VersionBadgeProps {
|
||||
version: string;
|
||||
noLeftMargin?: boolean;
|
||||
}
|
||||
|
||||
export function VersionBadge({ version }: VersionBadgeProps) {
|
||||
return <span className="version-badge">{version}</span>;
|
||||
export function VersionBadge({ version, noLeftMargin }: VersionBadgeProps) {
|
||||
return <span className={`version-badge${noLeftMargin ? " no-left-margin" : ""}`}>{version}</span>;
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { atoms, getSettingsKeyAtom } from "@/app/store/global";
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { cn, fireAndForget, makeIconClass } from "@/util/util";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { memo, useRef, useState } from "react";
|
||||
|
|
@ -175,6 +177,16 @@ export const AIModeDropdown = memo(({ compatibilityMode = false }: AIModeDropdow
|
|||
|
||||
const handleConfigureClick = () => {
|
||||
fireAndForget(async () => {
|
||||
RpcApi.RecordTEventCommand(
|
||||
TabRpcClient,
|
||||
{
|
||||
event: "action:other",
|
||||
props: {
|
||||
"action:type": "waveai:configuremodes:contextmenu",
|
||||
},
|
||||
},
|
||||
{ noresponse: true }
|
||||
);
|
||||
await model.openWaveAIConfig();
|
||||
setIsOpen(false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { AIPanelHeader } from "./aipanelheader";
|
|||
import { AIPanelInput } from "./aipanelinput";
|
||||
import { AIPanelMessages } from "./aipanelmessages";
|
||||
import { AIRateLimitStrip } from "./airatelimitstrip";
|
||||
import { BYOKAnnouncement } from "./byokannouncement";
|
||||
import { TelemetryRequiredMessage } from "./telemetryrequired";
|
||||
import { WaveAIModel } from "./waveai-model";
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ const AIWelcomeMessage = memo(() => {
|
|||
return (
|
||||
<div className="text-secondary py-8">
|
||||
<div className="text-center">
|
||||
<i className="fa fa-sparkles text-4xl text-accent mb-4 block"></i>
|
||||
<i className="fa fa-sparkles text-4xl text-accent mb-2 block"></i>
|
||||
<p className="text-lg font-bold text-primary">Welcome to Wave AI</p>
|
||||
</div>
|
||||
<div className="mt-4 text-left max-w-md mx-auto">
|
||||
|
|
@ -154,6 +155,7 @@ const AIWelcomeMessage = memo(() => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BYOKAnnouncement />
|
||||
<div className="mt-4 text-center text-[12px] text-muted">
|
||||
BETA: Free to use. Daily limits keep our costs in check.
|
||||
</div>
|
||||
|
|
|
|||
73
frontend/app/aipanel/byokannouncement.tsx
Normal file
73
frontend/app/aipanel/byokannouncement.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { RpcApi } from "@/app/store/wshclientapi";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import { WaveAIModel } from "./waveai-model";
|
||||
|
||||
const BYOKAnnouncement = () => {
|
||||
const model = WaveAIModel.getInstance();
|
||||
|
||||
const handleOpenConfig = async () => {
|
||||
RpcApi.RecordTEventCommand(
|
||||
TabRpcClient,
|
||||
{
|
||||
event: "action:other",
|
||||
props: {
|
||||
"action:type": "waveai:configuremodes:panel",
|
||||
},
|
||||
},
|
||||
{ noresponse: true }
|
||||
);
|
||||
await model.openWaveAIConfig();
|
||||
};
|
||||
|
||||
const handleViewDocs = () => {
|
||||
RpcApi.RecordTEventCommand(
|
||||
TabRpcClient,
|
||||
{
|
||||
event: "action:other",
|
||||
props: {
|
||||
"action:type": "waveai:viewdocs:panel",
|
||||
},
|
||||
},
|
||||
{ noresponse: true }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-blue-900/20 border border-blue-500 rounded-lg p-4 mt-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<i className="fa fa-key text-blue-400 text-lg mt-0.5"></i>
|
||||
<div className="text-left flex-1">
|
||||
<div className="text-blue-400 font-medium mb-1">New: BYOK & Local AI Support</div>
|
||||
<div className="text-secondary text-sm mb-3">
|
||||
Wave AI now supports bring-your-own-key (BYOK) with OpenAI, Google Gemini, Azure, and
|
||||
OpenRouter, plus local models via Ollama, LM Studio, and other OpenAI-compatible providers.
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={handleOpenConfig}
|
||||
className="bg-blue-500/80 hover:bg-blue-500 text-secondary hover:text-primary px-3 py-1.5 rounded-md text-sm font-medium cursor-pointer transition-colors"
|
||||
>
|
||||
Configure AI Modes
|
||||
</button>
|
||||
<a
|
||||
href="https://docs.waveterm.dev/waveai-modes"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={handleViewDocs}
|
||||
className="text-secondary hover:text-primary text-sm cursor-pointer transition-colors flex items-center gap-1"
|
||||
>
|
||||
View Docs <i className="fa fa-external-link text-xs"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
BYOKAnnouncement.displayName = "BYOKAnnouncement";
|
||||
|
||||
export { BYOKAnnouncement };
|
||||
|
|
@ -35,6 +35,10 @@ body {
|
|||
|
||||
a.plain-link {
|
||||
color: var(--secondary-text-color);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useRef } from "react";
|
||||
import { cn } from "@/util/util";
|
||||
import "./toggle.scss";
|
||||
|
||||
interface ToggleProps {
|
||||
|
|
@ -9,9 +10,10 @@ interface ToggleProps {
|
|||
onChange: (value: boolean) => void;
|
||||
label?: string;
|
||||
id?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Toggle = ({ checked, onChange, label, id }: ToggleProps) => {
|
||||
const Toggle = ({ checked, onChange, label, id, className }: ToggleProps) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleChange = (e: any) => {
|
||||
|
|
@ -29,7 +31,7 @@ const Toggle = ({ checked, onChange, label, id }: ToggleProps) => {
|
|||
const inputId = id || `toggle-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
return (
|
||||
<div className="check-toggle-wrapper">
|
||||
<div className={cn("check-toggle-wrapper", className)}>
|
||||
<label htmlFor={inputId} className="checkbox-toggle">
|
||||
<input id={inputId} type="checkbox" checked={checked} onChange={handleChange} ref={inputRef} />
|
||||
<span className="slider" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export const CurrentOnboardingVersion = "v0.12.5";
|
||||
export const CurrentOnboardingVersion = "v0.13.0";
|
||||
|
|
|
|||
|
|
@ -126,6 +126,14 @@ const WaveAIPage = ({ onNext, onSkip }: { onNext: () => void; onSkip: () => void
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3 w-full">
|
||||
<i className="fa fa-key text-accent text-lg mt-1 flex-shrink-0" />
|
||||
<p>
|
||||
Bring your own API keys or run local models with Ollama, LM Studio, and other
|
||||
OpenAI-compatible providers
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<EmojiButton emoji="🔥" isClicked={fireClicked} onClick={handleFireClick} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { debounce } from "throttle-debounce";
|
||||
|
||||
const UpgradeOnboardingModal_v0_12_0 = () => {
|
||||
const UpgradeOnboardingMinor = () => {
|
||||
const modalRef = useRef<HTMLDivElement | null>(null);
|
||||
const [pageName, setPageName] = useState<"welcome" | "features">("welcome");
|
||||
const [isCompact, setIsCompact] = useState<boolean>(window.innerHeight < 800);
|
||||
|
|
@ -119,30 +119,36 @@ const UpgradeOnboardingModal_v0_12_0 = () => {
|
|||
if (pageName === "welcome") {
|
||||
pageComp = (
|
||||
<div className="flex flex-col h-full">
|
||||
<header className="flex flex-col gap-2 border-b-0 p-0 mt-1 mb-6 w-full unselectable flex-shrink-0">
|
||||
<header className="flex flex-col gap-2 border-b-0 p-0 mt-1 mb-4 w-full unselectable flex-shrink-0">
|
||||
<div className="flex justify-center">
|
||||
<Logo />
|
||||
</div>
|
||||
<div className="text-center text-[25px] font-normal text-foreground">Welcome to Wave v0.12!</div>
|
||||
<div className="text-center text-[25px] font-normal text-foreground">Welcome to Wave v0.13!</div>
|
||||
</header>
|
||||
<OverlayScrollbarsComponent
|
||||
className="flex-1 overflow-y-auto min-h-0"
|
||||
options={{ scrollbars: { autoHide: "never" } }}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-3 w-full mb-2 unselectable">
|
||||
<div className="flex flex-col items-center gap-4 max-w-md text-center">
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<div className="flex h-[52px] px-3 items-center rounded-lg bg-hover text-accent text-[24px]">
|
||||
<i className="fa fa-sparkles" />
|
||||
<span className="font-bold ml-2 font-mono">Wave AI</span>
|
||||
</div>
|
||||
<div className="text-secondary leading-relaxed max-w-[420px]">
|
||||
<div className="text-secondary leading-relaxed max-w-[600px]">
|
||||
<p className="mb-4">
|
||||
Wave AI is your new terminal assistant with full context. It can read your terminal
|
||||
output, analyze widgets, access files, and help you solve problems faster.
|
||||
Wave AI is your terminal assistant with full context. It can read your terminal
|
||||
output, analyze widgets, read and write files, and help you solve
|
||||
problems faster.
|
||||
</p>
|
||||
<p className="p-3 border border-border rounded-md bg-hover/30">
|
||||
Wave AI is in active beta with included AI credits while we refine the experience.
|
||||
We're actively improving it and would love your feedback in{" "}
|
||||
<p className="mb-4">
|
||||
<span className="font-semibold text-foreground">New in v0.13:</span> Wave AI now
|
||||
supports local models and bring-your-own-key! Use Ollama, LM Studio, vLLM,
|
||||
OpenRouter, or any OpenAI-compatible provider.
|
||||
</p>
|
||||
<p className="py-3 px-2 border border-border rounded-md bg-hover/30">
|
||||
Wave AI is in beta with included AI credits while we refine the experience. We're
|
||||
actively improving it and would love your feedback in{" "}
|
||||
<a target="_blank" href="https://discord.gg/XfvZ334gwU" className="hover:underline">
|
||||
Discord
|
||||
</a>
|
||||
|
|
@ -151,9 +157,9 @@ const UpgradeOnboardingModal_v0_12_0 = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-md border-t border-border my-2"></div>
|
||||
<div className="w-full max-w-[550px] border-t border-border my-2"></div>
|
||||
|
||||
<div className="flex flex-col items-center gap-3 text-center max-w-[440px]">
|
||||
<div className="flex flex-col items-center gap-3 text-center max-w-[550px]">
|
||||
<div className="text-foreground text-base">Thanks for being an early Wave adopter! ⭐</div>
|
||||
<div className="text-secondary text-sm">
|
||||
A GitHub star shows your support for Wave (and open-source) and helps us reach more
|
||||
|
|
@ -186,7 +192,7 @@ const UpgradeOnboardingModal_v0_12_0 = () => {
|
|||
}
|
||||
|
||||
const paddingClass = isCompact ? "!py-3 !px-[30px]" : "!p-[30px]";
|
||||
const widthClass = pageName === "features" ? "w-[800px]" : "w-[560px]";
|
||||
const widthClass = pageName === "features" ? "w-[800px]" : "w-[600px]";
|
||||
|
||||
return (
|
||||
<FlexiModal className={`${widthClass} rounded-[10px] ${paddingClass} relative overflow-hidden`} ref={modalRef}>
|
||||
|
|
@ -196,6 +202,6 @@ const UpgradeOnboardingModal_v0_12_0 = () => {
|
|||
);
|
||||
};
|
||||
|
||||
UpgradeOnboardingModal_v0_12_0.displayName = "UpgradeOnboardingModal_v0_12_0";
|
||||
UpgradeOnboardingMinor.displayName = "UpgradeOnboardingMinor";
|
||||
|
||||
export { UpgradeOnboardingModal_v0_12_0 };
|
||||
export { UpgradeOnboardingMinor };
|
||||
|
|
@ -17,6 +17,7 @@ import { debounce } from "throttle-debounce";
|
|||
import { UpgradeOnboardingModal_v0_12_1_Content } from "./onboarding-upgrade-v0121";
|
||||
import { UpgradeOnboardingModal_v0_12_2_Content } from "./onboarding-upgrade-v0122";
|
||||
import { UpgradeOnboardingModal_v0_12_3_Content } from "./onboarding-upgrade-v0123";
|
||||
import { UpgradeOnboardingModal_v0_13_0_Content } from "./onboarding-upgrade-v0130";
|
||||
|
||||
interface VersionConfig {
|
||||
version: string;
|
||||
|
|
@ -41,6 +42,12 @@ const versions: VersionConfig[] = [
|
|||
version: "v0.12.5",
|
||||
content: () => <UpgradeOnboardingModal_v0_12_3_Content />,
|
||||
prevText: "Prev (v0.12.2)",
|
||||
nextText: "Next (v0.13.0)",
|
||||
},
|
||||
{
|
||||
version: "v0.13.0",
|
||||
content: () => <UpgradeOnboardingModal_v0_13_0_Content />,
|
||||
prevText: "Prev (v0.12.5)",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
93
frontend/app/onboarding/onboarding-upgrade-v0130.tsx
Normal file
93
frontend/app/onboarding/onboarding-upgrade-v0130.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
const UpgradeOnboardingModal_v0_13_0_Content = () => {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-6 w-full mb-4 unselectable">
|
||||
<div className="text-secondary leading-relaxed">
|
||||
<p className="mb-0">
|
||||
Wave v0.13 brings local AI support, bring-your-own-key (BYOK), a redesigned configuration system,
|
||||
and improved terminal functionality.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-start gap-4">
|
||||
<div className="flex-shrink-0">
|
||||
<i className="text-[24px] text-accent fa-solid fa-sparkles"></i>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-2 flex-1">
|
||||
<div className="text-foreground text-base font-semibold leading-[18px]">Local AI & BYOK</div>
|
||||
<div className="text-secondary leading-5">
|
||||
<ul className="list-disc list-outside space-y-1 pl-5">
|
||||
<li>
|
||||
<strong>OpenAI-Compatible API</strong> - Connect to Ollama, LM Studio, vLLM, OpenRouter,
|
||||
and other local or hosted models
|
||||
</li>
|
||||
<li>
|
||||
<strong>Google Gemini</strong> - Native support for Gemini models
|
||||
</li>
|
||||
<li>
|
||||
<strong>Provider Presets</strong> - Built-in configs for OpenAI, OpenRouter, Google,
|
||||
Azure, and custom endpoints
|
||||
</li>
|
||||
<li>
|
||||
<strong>Multiple AI Modes</strong> - Easily switch between models and providers
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-start gap-4">
|
||||
<div className="flex-shrink-0">
|
||||
<i className="text-[24px] text-accent fa-solid fa-sliders"></i>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-2 flex-1">
|
||||
<div className="text-foreground text-base font-semibold leading-[18px]">Configuration Widget</div>
|
||||
<div className="text-secondary leading-5">
|
||||
<ul className="list-disc list-outside space-y-1 pl-5">
|
||||
<li>
|
||||
<strong>New Config Interface</strong> - Dedicated widget accessible from the sidebar
|
||||
</li>
|
||||
<li>
|
||||
<strong>Better Organization</strong> - Browse and edit settings with improved validation
|
||||
and error handling
|
||||
</li>
|
||||
<li>
|
||||
<strong>Integrated Secrets</strong> - Manage API keys and credentials from the config
|
||||
widget
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-start gap-4">
|
||||
<div className="flex-shrink-0">
|
||||
<i className="text-[24px] text-accent fa-solid fa-terminal"></i>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-2 flex-1">
|
||||
<div className="text-foreground text-base font-semibold leading-[18px]">Terminal Updates</div>
|
||||
<div className="text-secondary leading-5">
|
||||
<ul className="list-disc list-outside space-y-1 pl-5">
|
||||
<li>
|
||||
<strong>Bracketed Paste Mode</strong> - Enabled by default for better multi-line paste
|
||||
behavior
|
||||
</li>
|
||||
<li>
|
||||
<strong>Windows Paste Fix</strong> - Ctrl+V now works as standard paste on Windows
|
||||
</li>
|
||||
<li>
|
||||
<strong>SSH Password Storage</strong> - Store SSH passwords in Wave's secret store
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
UpgradeOnboardingModal_v0_13_0_Content.displayName = "UpgradeOnboardingModal_v0_13_0_Content";
|
||||
|
||||
export { UpgradeOnboardingModal_v0_13_0_Content };
|
||||
|
|
@ -7,7 +7,7 @@ import { useAtomValue } from "jotai";
|
|||
import { useEffect, useRef } from "react";
|
||||
import * as semver from "semver";
|
||||
import { CurrentOnboardingVersion } from "./onboarding-common";
|
||||
import { UpgradeOnboardingModal_v0_12_0 } from "./onboarding-upgrade-v0120";
|
||||
import { UpgradeOnboardingMinor } from "./onboarding-upgrade-minor";
|
||||
import { UpgradeOnboardingPatch } from "./onboarding-upgrade-patch";
|
||||
|
||||
const UpgradeOnboardingModal = () => {
|
||||
|
|
@ -34,7 +34,7 @@ const UpgradeOnboardingModal = () => {
|
|||
return <UpgradeOnboardingPatch />;
|
||||
}
|
||||
|
||||
return <UpgradeOnboardingModal_v0_12_0 />;
|
||||
return <UpgradeOnboardingMinor />;
|
||||
};
|
||||
|
||||
UpgradeOnboardingModal.displayName = "UpgradeOnboardingModal";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import Logo from "@/app/asset/logo.svg";
|
||||
import { Button } from "@/app/element/button";
|
||||
import { Toggle } from "@/app/element/toggle";
|
||||
import { FlexiModal } from "@/app/modals/modal";
|
||||
import { disableGlobalKeybindings, enableGlobalKeybindings, globalRefocus } from "@/app/store/keymodel";
|
||||
import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model";
|
||||
|
|
@ -53,7 +52,7 @@ const InitPage = ({ isCompact }: { isCompact: boolean }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const label = telemetryEnabled ? "Telemetry Enabled" : "Telemetry Disabled";
|
||||
const label = telemetryEnabled ? "Enabled" : "Disabled";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
|
|
@ -118,13 +117,9 @@ const InitPage = ({ isCompact }: { isCompact: boolean }) => {
|
|||
<i className="text-[32px] text-white/50 fa-solid fa-chart-line"></i>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1 flex-1">
|
||||
<div className="text-foreground text-base leading-[18px]">Telemetry</div>
|
||||
<div className="text-secondary leading-5">
|
||||
We collect minimal anonymous{" "}
|
||||
<a target="_blank" href="https://docs.waveterm.dev/telemetry" rel={"noopener"}>
|
||||
telemetry data
|
||||
</a>{" "}
|
||||
to help us understand how people are using Wave (
|
||||
Anonymous usage data helps us improve features you use.
|
||||
<br />
|
||||
<a
|
||||
className="plain-link"
|
||||
target="_blank"
|
||||
|
|
@ -133,9 +128,16 @@ const InitPage = ({ isCompact }: { isCompact: boolean }) => {
|
|||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
).
|
||||
</div>
|
||||
<Toggle checked={telemetryEnabled} onChange={setTelemetry} label={label} />
|
||||
<label className="flex items-center gap-2 cursor-pointer text-secondary">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={telemetryEnabled}
|
||||
onChange={(e) => setTelemetry(e.target.checked)}
|
||||
className="cursor-pointer accent-gray-500"
|
||||
/>
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -468,6 +468,16 @@ export class WaveConfigViewModel implements ViewModel {
|
|||
|
||||
try {
|
||||
await RpcApi.SetSecretsCommand(TabRpcClient, { [selectedSecret]: secretValue });
|
||||
RpcApi.RecordTEventCommand(
|
||||
TabRpcClient,
|
||||
{
|
||||
event: "action:other",
|
||||
props: {
|
||||
"action:type": "waveconfig:savesecret",
|
||||
},
|
||||
},
|
||||
{ noresponse: true }
|
||||
);
|
||||
this.closeSecretView();
|
||||
} catch (error) {
|
||||
globalStore.set(this.errorMessageAtom, `Failed to save secret: ${error.message}`);
|
||||
|
|
@ -539,6 +549,16 @@ export class WaveConfigViewModel implements ViewModel {
|
|||
|
||||
try {
|
||||
await RpcApi.SetSecretsCommand(TabRpcClient, { [name]: value });
|
||||
RpcApi.RecordTEventCommand(
|
||||
TabRpcClient,
|
||||
{
|
||||
event: "action:other",
|
||||
props: {
|
||||
"action:type": "waveconfig:savesecret",
|
||||
},
|
||||
},
|
||||
{ noresponse: true }
|
||||
);
|
||||
globalStore.set(this.isAddingNewAtom, false);
|
||||
globalStore.set(this.newSecretNameAtom, "");
|
||||
globalStore.set(this.newSecretValueAtom, "");
|
||||
|
|
|
|||
3
frontend/types/gotypes.d.ts
vendored
3
frontend/types/gotypes.d.ts
vendored
|
|
@ -1223,6 +1223,7 @@ declare global {
|
|||
"app:firstday"?: boolean;
|
||||
"app:firstlaunch"?: boolean;
|
||||
"action:initiator"?: "keyboard" | "mouse";
|
||||
"action:type"?: string;
|
||||
"debug:panictype"?: string;
|
||||
"block:view"?: string;
|
||||
"ai:backendtype"?: string;
|
||||
|
|
@ -1268,6 +1269,8 @@ declare global {
|
|||
"waveai:widgetaccess"?: boolean;
|
||||
"waveai:thinkinglevel"?: string;
|
||||
"waveai:mode"?: string;
|
||||
"waveai:provider"?: string;
|
||||
"waveai:islocal"?: boolean;
|
||||
"waveai:feedback"?: "good" | "bad";
|
||||
"waveai:action"?: string;
|
||||
$set?: TEventUserProps;
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "waveterm",
|
||||
"version": "0.12.5",
|
||||
"version": "0.13.0-beta.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "waveterm",
|
||||
"version": "0.12.5",
|
||||
"version": "0.13.0-beta.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import (
|
|||
)
|
||||
|
||||
const DefaultAIEndpoint = "https://cfapi.waveterm.dev/api/waveai"
|
||||
const DefaultOpenAIEndpoint = "https://api.openai.com/v1"
|
||||
const DefaultOpenRouterEndpoint = "https://openrouter.ai/api/v1"
|
||||
const WaveAIEndpointEnvName = "WAVETERM_WAVEAI_ENDPOINT"
|
||||
const DefaultAnthropicModel = "claude-sonnet-4-5"
|
||||
const DefaultOpenAIModel = "gpt-5-mini"
|
||||
|
|
@ -332,6 +330,8 @@ type AIMetrics struct {
|
|||
WidgetAccess bool `json:"widgetaccess"`
|
||||
ThinkingLevel string `json:"thinkinglevel,omitempty"`
|
||||
AIMode string `json:"aimode,omitempty"`
|
||||
AIProvider string `json:"aiprovider,omitempty"`
|
||||
IsLocal bool `json:"islocal,omitempty"`
|
||||
}
|
||||
|
||||
type AIFunctionCallInput struct {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,23 @@ import (
|
|||
|
||||
var AzureResourceNameRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`)
|
||||
|
||||
const (
|
||||
OpenAIResponsesEndpoint = "https://api.openai.com/v1/responses"
|
||||
OpenAIChatEndpoint = "https://api.openai.com/v1/chat/completions"
|
||||
OpenRouterChatEndpoint = "https://openrouter.ai/api/v1/chat/completions"
|
||||
AzureLegacyEndpointTemplate = "https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s"
|
||||
AzureResponsesEndpointTemplate = "https://%s.openai.azure.com/openai/v1/responses"
|
||||
AzureChatEndpointTemplate = "https://%s.openai.azure.com/openai/v1/chat/completions"
|
||||
GoogleGeminiEndpointTemplate = "https://generativelanguage.googleapis.com/v1beta/models/%s:streamGenerateContent"
|
||||
|
||||
AzureLegacyDefaultAPIVersion = "2025-04-01-preview"
|
||||
|
||||
OpenAIAPITokenSecretName = "OPENAI_KEY"
|
||||
OpenRouterAPITokenSecretName = "OPENROUTER_KEY"
|
||||
AzureOpenAIAPITokenSecretName = "AZURE_OPENAI_KEY"
|
||||
GoogleAIAPITokenSecretName = "GOOGLE_AI_KEY"
|
||||
)
|
||||
|
||||
func resolveAIMode(requestedMode string, premium bool) (string, *wconfig.AIModeConfigType, error) {
|
||||
mode := requestedMode
|
||||
if mode == "" {
|
||||
|
|
@ -52,14 +69,21 @@ func applyProviderDefaults(config *wconfig.AIModeConfigType) {
|
|||
}
|
||||
}
|
||||
if config.Provider == uctypes.AIProvider_OpenAI {
|
||||
if config.Endpoint == "" {
|
||||
config.Endpoint = uctypes.DefaultOpenAIEndpoint
|
||||
}
|
||||
if config.APIType == "" {
|
||||
config.APIType = getOpenAIAPIType(config.Model)
|
||||
}
|
||||
if config.Endpoint == "" {
|
||||
switch config.APIType {
|
||||
case uctypes.APIType_OpenAIResponses:
|
||||
config.Endpoint = OpenAIResponsesEndpoint
|
||||
case uctypes.APIType_OpenAIChat:
|
||||
config.Endpoint = OpenAIChatEndpoint
|
||||
default:
|
||||
config.Endpoint = OpenAIChatEndpoint
|
||||
}
|
||||
}
|
||||
if config.APITokenSecretName == "" {
|
||||
config.APITokenSecretName = "OPENAI_KEY"
|
||||
config.APITokenSecretName = OpenAIAPITokenSecretName
|
||||
}
|
||||
if len(config.Capabilities) == 0 {
|
||||
if isO1Model(config.Model) {
|
||||
|
|
@ -70,29 +94,29 @@ func applyProviderDefaults(config *wconfig.AIModeConfigType) {
|
|||
}
|
||||
}
|
||||
if config.Provider == uctypes.AIProvider_OpenRouter {
|
||||
if config.Endpoint == "" {
|
||||
config.Endpoint = uctypes.DefaultOpenRouterEndpoint
|
||||
}
|
||||
if config.APIType == "" {
|
||||
config.APIType = uctypes.APIType_OpenAIChat
|
||||
}
|
||||
if config.Endpoint == "" {
|
||||
config.Endpoint = OpenRouterChatEndpoint
|
||||
}
|
||||
if config.APITokenSecretName == "" {
|
||||
config.APITokenSecretName = "OPENROUTER_KEY"
|
||||
config.APITokenSecretName = OpenRouterAPITokenSecretName
|
||||
}
|
||||
}
|
||||
if config.Provider == uctypes.AIProvider_AzureLegacy {
|
||||
if config.AzureAPIVersion == "" {
|
||||
config.AzureAPIVersion = "2025-04-01-preview"
|
||||
config.AzureAPIVersion = AzureLegacyDefaultAPIVersion
|
||||
}
|
||||
if config.Endpoint == "" && isValidAzureResourceName(config.AzureResourceName) && config.AzureDeployment != "" {
|
||||
config.Endpoint = fmt.Sprintf("https://%s.openai.azure.com/openai/deployments/%s/chat/completions?api-version=%s",
|
||||
config.Endpoint = fmt.Sprintf(AzureLegacyEndpointTemplate,
|
||||
config.AzureResourceName, config.AzureDeployment, config.AzureAPIVersion)
|
||||
}
|
||||
if config.APIType == "" {
|
||||
config.APIType = uctypes.APIType_OpenAIChat
|
||||
}
|
||||
if config.APITokenSecretName == "" {
|
||||
config.APITokenSecretName = "AZURE_OPENAI_KEY"
|
||||
config.APITokenSecretName = AzureOpenAIAPITokenSecretName
|
||||
}
|
||||
}
|
||||
if config.Provider == uctypes.AIProvider_Azure {
|
||||
|
|
@ -103,16 +127,15 @@ func applyProviderDefaults(config *wconfig.AIModeConfigType) {
|
|||
config.APIType = getAzureAPIType(config.Model)
|
||||
}
|
||||
if config.Endpoint == "" && isValidAzureResourceName(config.AzureResourceName) && isAzureAPIType(config.APIType) {
|
||||
base := fmt.Sprintf("https://%s.openai.azure.com/openai/v1", config.AzureResourceName)
|
||||
switch config.APIType {
|
||||
case uctypes.APIType_OpenAIResponses:
|
||||
config.Endpoint = base + "/responses"
|
||||
config.Endpoint = fmt.Sprintf(AzureResponsesEndpointTemplate, config.AzureResourceName)
|
||||
case uctypes.APIType_OpenAIChat:
|
||||
config.Endpoint = base + "/chat/completions"
|
||||
config.Endpoint = fmt.Sprintf(AzureChatEndpointTemplate, config.AzureResourceName)
|
||||
}
|
||||
}
|
||||
if config.APITokenSecretName == "" {
|
||||
config.APITokenSecretName = "AZURE_OPENAI_KEY"
|
||||
config.APITokenSecretName = AzureOpenAIAPITokenSecretName
|
||||
}
|
||||
}
|
||||
if config.Provider == uctypes.AIProvider_Google {
|
||||
|
|
@ -120,10 +143,10 @@ func applyProviderDefaults(config *wconfig.AIModeConfigType) {
|
|||
config.APIType = uctypes.APIType_GoogleGemini
|
||||
}
|
||||
if config.Endpoint == "" && config.Model != "" {
|
||||
config.Endpoint = fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/%s:streamGenerateContent", config.Model)
|
||||
config.Endpoint = fmt.Sprintf(GoogleGeminiEndpointTemplate, config.Model)
|
||||
}
|
||||
if config.APITokenSecretName == "" {
|
||||
config.APITokenSecretName = "GOOGLE_AI_KEY"
|
||||
config.APITokenSecretName = GoogleAIAPITokenSecretName
|
||||
}
|
||||
if len(config.Capabilities) == 0 {
|
||||
config.Capabilities = []string{uctypes.AICapabilityTools, uctypes.AICapabilityImages, uctypes.AICapabilityPdfs}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,14 @@ func getSystemPrompt(apiType string, model string, isBuilder bool) []string {
|
|||
return []string{basePrompt}
|
||||
}
|
||||
|
||||
func isLocalEndpoint(endpoint string) bool {
|
||||
if endpoint == "" {
|
||||
return false
|
||||
}
|
||||
endpointLower := strings.ToLower(endpoint)
|
||||
return strings.Contains(endpointLower, "localhost") || strings.Contains(endpointLower, "127.0.0.1")
|
||||
}
|
||||
|
||||
func getWaveAISettings(premium bool, builderMode bool, rtInfo waveobj.ObjRTInfo) (*uctypes.AIOptsType, error) {
|
||||
maxTokens := DefaultMaxTokens
|
||||
if builderMode {
|
||||
|
|
@ -353,6 +361,11 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseCha
|
|||
defer activeChats.Delete(chatOpts.ChatId)
|
||||
|
||||
stepNum := chatstore.DefaultChatStore.CountUserMessages(chatOpts.ChatId)
|
||||
aiProvider := chatOpts.Config.Provider
|
||||
if aiProvider == "" {
|
||||
aiProvider = uctypes.AIProvider_Custom
|
||||
}
|
||||
isLocal := isLocalEndpoint(chatOpts.Config.Endpoint)
|
||||
metrics := &uctypes.AIMetrics{
|
||||
ChatId: chatOpts.ChatId,
|
||||
StepNum: stepNum,
|
||||
|
|
@ -364,6 +377,8 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, backend UseCha
|
|||
ToolDetail: make(map[string]int),
|
||||
ThinkingLevel: chatOpts.Config.ThinkingLevel,
|
||||
AIMode: chatOpts.Config.AIMode,
|
||||
AIProvider: aiProvider,
|
||||
IsLocal: isLocal,
|
||||
}
|
||||
firstStep := true
|
||||
var cont *uctypes.WaveContinueResponse
|
||||
|
|
@ -563,6 +578,8 @@ func sendAIMetricsTelemetry(ctx context.Context, metrics *uctypes.AIMetrics) {
|
|||
WaveAIWidgetAccess: metrics.WidgetAccess,
|
||||
WaveAIThinkingLevel: metrics.ThinkingLevel,
|
||||
WaveAIMode: metrics.AIMode,
|
||||
WaveAIProvider: metrics.AIProvider,
|
||||
WaveAIIsLocal: metrics.IsLocal,
|
||||
})
|
||||
_ = telemetry.RecordTEvent(ctx, event)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,6 +279,24 @@ func GetSecretNames() ([]string, error) {
|
|||
return names, nil
|
||||
}
|
||||
|
||||
func CountSecrets() (int, error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if !initialized {
|
||||
return 0, fmt.Errorf("secret store not initialized")
|
||||
}
|
||||
|
||||
count := 0
|
||||
for name := range secrets {
|
||||
if name == WriteTsKey {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func GetLinuxStorageBackend() (string, error) {
|
||||
if runtime.GOOS != "linux" {
|
||||
return "", nil
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ var ValidEventNames = map[string]bool{
|
|||
"action:createtab": true,
|
||||
"action:createblock": true,
|
||||
"action:openwaveai": true,
|
||||
"action:other": true,
|
||||
|
||||
"wsh:run": true,
|
||||
|
||||
|
|
@ -83,6 +84,8 @@ type TEventUserProps struct {
|
|||
SettingsCustomWidgets int `json:"settings:customwidgets,omitempty"`
|
||||
SettingsCustomAIPresets int `json:"settings:customaipresets,omitempty"`
|
||||
SettingsCustomSettings int `json:"settings:customsettings,omitempty"`
|
||||
SettingsCustomAIModes int `json:"settings:customaimodes,omitempty"`
|
||||
SettingsSecretsCount int `json:"settings:secretscount,omitempty"`
|
||||
}
|
||||
|
||||
type TEventProps struct {
|
||||
|
|
@ -99,6 +102,7 @@ type TEventProps struct {
|
|||
AppFirstLaunch bool `json:"app:firstlaunch,omitempty"`
|
||||
|
||||
ActionInitiator string `json:"action:initiator,omitempty" tstype:"\"keyboard\" | \"mouse\""`
|
||||
ActionType string `json:"action:type,omitempty"`
|
||||
PanicType string `json:"debug:panictype,omitempty"`
|
||||
BlockView string `json:"block:view,omitempty"`
|
||||
AiBackendType string `json:"ai:backendtype,omitempty"`
|
||||
|
|
@ -148,6 +152,8 @@ type TEventProps struct {
|
|||
WaveAIWidgetAccess bool `json:"waveai:widgetaccess,omitempty"`
|
||||
WaveAIThinkingLevel string `json:"waveai:thinkinglevel,omitempty"`
|
||||
WaveAIMode string `json:"waveai:mode,omitempty"`
|
||||
WaveAIProvider string `json:"waveai:provider,omitempty"`
|
||||
WaveAIIsLocal bool `json:"waveai:islocal,omitempty"`
|
||||
WaveAIFeedback string `json:"waveai:feedback,omitempty" tstype:"\"good\" | \"bad\""`
|
||||
WaveAIAction string `json:"waveai:action,omitempty"`
|
||||
|
||||
|
|
|
|||
|
|
@ -887,6 +887,18 @@ func (fc *FullConfigType) CountCustomAIPresets() int {
|
|||
return count
|
||||
}
|
||||
|
||||
// CountCustomAIModes returns the number of custom AI modes the user has defined.
|
||||
// Custom AI modes are identified as modes that don't start with "waveai@".
|
||||
func (fc *FullConfigType) CountCustomAIModes() int {
|
||||
count := 0
|
||||
for modeID := range fc.WaveAIModes {
|
||||
if !strings.HasPrefix(modeID, "waveai@") {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// CountCustomSettings returns the number of settings in the user's settings file.
|
||||
// This excludes telemetry:enabled and autoupdate:channel which don't count as customizations.
|
||||
func CountCustomSettings() int {
|
||||
|
|
|
|||
Loading…
Reference in a new issue