From 2b2693398bc0116d875d16ac54640733eb02e7ed Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 5 Dec 2025 12:46:36 -0800 Subject: [PATCH 1/8] add new ai:switchcompat field for switching compatibility --- frontend/types/gotypes.d.ts | 3 ++- pkg/wconfig/defaultconfig/waveai.json | 9 ++++++--- pkg/wconfig/settingsconfig.go | 1 + schema/waveai.json | 10 ++++++++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 7aa02b3a3e..988fcf012f 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -24,12 +24,13 @@ declare global { "ai:model"?: string; "ai:thinkinglevel"?: string; "ai:endpoint"?: string; - "ai:apiversion"?: string; + "ai:azureapiversion"?: string; "ai:apitoken"?: string; "ai:apitokensecretname"?: string; "ai:azureresourcename"?: string; "ai:azuredeployment"?: string; "ai:capabilities"?: string[]; + "ai:switchcompat"?: string[]; "waveai:cloud"?: boolean; "waveai:premium"?: boolean; }; diff --git a/pkg/wconfig/defaultconfig/waveai.json b/pkg/wconfig/defaultconfig/waveai.json index bb07f7a340..c115211b73 100644 --- a/pkg/wconfig/defaultconfig/waveai.json +++ b/pkg/wconfig/defaultconfig/waveai.json @@ -8,7 +8,8 @@ "ai:apitype": "openai-responses", "ai:model": "gpt-5-mini", "ai:thinkinglevel": "low", - "ai:capabilities": ["tools", "images", "pdfs"] + "ai:capabilities": ["tools", "images", "pdfs"], + "ai:switchcompat": ["wavecloud"] }, "waveai@balanced": { "display:name": "Balanced", @@ -20,7 +21,8 @@ "ai:model": "gpt-5.1", "ai:thinkinglevel": "low", "ai:capabilities": ["tools", "images", "pdfs"], - "waveai:premium": true + "waveai:premium": true, + "ai:switchcompat": ["wavecloud"] }, "waveai@deep": { "display:name": "Deep", @@ -32,6 +34,7 @@ "ai:model": "gpt-5.1", "ai:thinkinglevel": "medium", "ai:capabilities": ["tools", "images", "pdfs"], - "waveai:premium": true + "waveai:premium": true, + "ai:switchcompat": ["wavecloud"] } } diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 1d6adf1eda..6d0da5dab5 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -278,6 +278,7 @@ type AIModeConfigType struct { AzureResourceName string `json:"ai:azureresourcename,omitempty"` AzureDeployment string `json:"ai:azuredeployment,omitempty"` Capabilities []string `json:"ai:capabilities,omitempty" jsonschema:"enum=pdfs,enum=images,enum=tools"` + SwitchCompat []string `json:"ai:switchcompat,omitempty"` WaveAICloud bool `json:"waveai:cloud,omitempty"` WaveAIPremium bool `json:"waveai:premium,omitempty"` } diff --git a/schema/waveai.json b/schema/waveai.json index b88d3b1bfb..1612cac88d 100644 --- a/schema/waveai.json +++ b/schema/waveai.json @@ -30,7 +30,7 @@ "ai:apitype": { "type": "string", "enum": [ - "anthropic-messages", + "google-gemini", "openai-responses", "openai-chat" ] @@ -49,7 +49,7 @@ "ai:endpoint": { "type": "string" }, - "ai:apiversion": { + "ai:azureapiversion": { "type": "string" }, "ai:apitoken": { @@ -75,6 +75,12 @@ }, "type": "array" }, + "ai:switchcompat": { + "items": { + "type": "string" + }, + "type": "array" + }, "waveai:cloud": { "type": "boolean" }, From 8172111d7854c4a8c9d385568a5ff9009009c8e0 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 5 Dec 2025 13:25:07 -0800 Subject: [PATCH 2/8] refactor --- frontend/app/aipanel/aimode.tsx | 103 +++++++++++++++++--------------- 1 file changed, 55 insertions(+), 48 deletions(-) diff --git a/frontend/app/aipanel/aimode.tsx b/frontend/app/aipanel/aimode.tsx index a30bc0136e..0c7e7a968b 100644 --- a/frontend/app/aipanel/aimode.tsx +++ b/frontend/app/aipanel/aimode.tsx @@ -8,6 +8,48 @@ import { memo, useRef, useState } from "react"; import { getFilteredAIModeConfigs } from "./ai-utils"; import { WaveAIModel } from "./waveai-model"; +interface AIModeMenuItemProps { + config: any; + isSelected: boolean; + isDisabled: boolean; + onClick: () => void; + isFirst?: boolean; + isLast?: boolean; +} + +const AIModeMenuItem = memo(({ config, isSelected, isDisabled, onClick, isFirst, isLast }: AIModeMenuItemProps) => { + return ( + + ); +}); + +AIModeMenuItem.displayName = "AIModeMenuItem"; + export const AIModeDropdown = memo(() => { const model = WaveAIModel.getInstance(); const aiMode = useAtomValue(model.currentAIMode); @@ -86,32 +128,14 @@ export const AIModeDropdown = memo(() => { const isDisabled = !hasPremium && config["waveai:premium"]; const isSelected = currentMode === config.mode; return ( - + isFirst={isFirst} + /> ); })} {hasBothModeTypes && ( @@ -128,32 +152,15 @@ export const AIModeDropdown = memo(() => { const isDisabled = !hasPremium && config["waveai:premium"]; const isSelected = currentMode === config.mode; return ( - + isFirst={isFirst} + isLast={isLast} + /> ); })}
From 0598354477c67e77b56da0b0876aae6c08856b41 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 5 Dec 2025 14:51:24 -0800 Subject: [PATCH 3/8] more aimode refactoring --- frontend/app/aipanel/aimode.tsx | 110 +++++++++++++------------- frontend/app/aipanel/waveai-model.tsx | 10 +++ 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/frontend/app/aipanel/aimode.tsx b/frontend/app/aipanel/aimode.tsx index 0c7e7a968b..d31b1ed65a 100644 --- a/frontend/app/aipanel/aimode.tsx +++ b/frontend/app/aipanel/aimode.tsx @@ -1,7 +1,7 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { atoms, createBlock, getSettingsKeyAtom } from "@/app/store/global"; +import { atoms, getSettingsKeyAtom } from "@/app/store/global"; import { cn, fireAndForget, makeIconClass } from "@/util/util"; import { useAtomValue } from "jotai"; import { memo, useRef, useState } from "react"; @@ -69,7 +69,20 @@ export const AIModeDropdown = memo(() => { hasPremium ); - const hasBothModeTypes = waveProviderConfigs.length > 0 && otherProviderConfigs.length > 0; + interface ConfigSection { + sectionName: string; + configs: any[]; + } + + const sections: ConfigSection[] = []; + if (waveProviderConfigs.length > 0) { + sections.push({ sectionName: "Wave AI Cloud", configs: waveProviderConfigs }); + } + if (otherProviderConfigs.length > 0) { + sections.push({ sectionName: "Custom", configs: otherProviderConfigs }); + } + + const showSectionHeaders = sections.length > 1; const handleSelect = (mode: string) => { const config = aiModeConfigs[mode]; @@ -97,7 +110,14 @@ export const AIModeDropdown = memo(() => { "display:icon": "question", }; - return ( + const handleConfigureClick = () => { + fireAndForget(async () => { + await model.openWaveAIConfig(); + setIsOpen(false); + }); + }; + + return (
- + + + + + +
From 1f1de07f9557bf9f7685fabc5b9265196917ae39 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 5 Dec 2025 15:56:36 -0800 Subject: [PATCH 6/8] add example links, link waveai.json to docs --- docs/docs/waveai-modes.mdx | 12 ++++++------ frontend/app/view/waveconfig/waveconfig-model.ts | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/docs/waveai-modes.mdx b/docs/docs/waveai-modes.mdx index ccdcdfd7f4..d4f0c30a69 100644 --- a/docs/docs/waveai-modes.mdx +++ b/docs/docs/waveai-modes.mdx @@ -28,12 +28,12 @@ Wave AI now supports provider-based configuration which automatically applies se ### Supported Providers -- **`openai`** - OpenAI API (automatically configures endpoint and secret name) -- **`openrouter`** - OpenRouter API (automatically configures endpoint and secret name) -- **`google`** - Google AI (Gemini) -- **`azure`** - Azure OpenAI Service (modern API) -- **`azure-legacy`** - Azure OpenAI Service (legacy deployment API) -- **`custom`** - Custom API endpoint (fully manual configuration) +- **`openai`** - OpenAI API (automatically configures endpoint and secret name) [[see example](#openai)] +- **`openrouter`** - OpenRouter API (automatically configures endpoint and secret name) [[see example](#openrouter)] +- **`google`** - Google AI (Gemini) [[see example](#google-ai-gemini)] +- **`azure`** - Azure OpenAI Service (modern API) [[see example](#azure-openai-modern-api)] +- **`azure-legacy`** - Azure OpenAI Service (legacy deployment API) [[see example](#azure-openai-legacy-deployment-api)] +- **`custom`** - Custom API endpoint (fully manual configuration) [[see examples](#local-model-examples)] ### Supported API Types diff --git a/frontend/app/view/waveconfig/waveconfig-model.ts b/frontend/app/view/waveconfig/waveconfig-model.ts index 5c942a5810..4b501d5874 100644 --- a/frontend/app/view/waveconfig/waveconfig-model.ts +++ b/frontend/app/view/waveconfig/waveconfig-model.ts @@ -90,6 +90,7 @@ const configFiles: ConfigFile[] = [ name: "Wave AI Modes", path: "waveai.json", language: "json", + docsUrl: "https://docs.waveterm.dev/waveai-modes", validator: validateWaveAiJson, hasJsonView: true, // visualComponent: WaveAIVisualContent, From 317d5c251f66c1c647f2e0018aff48b5531745c1 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 5 Dec 2025 16:10:47 -0800 Subject: [PATCH 7/8] add descriptions, update some wording --- .../app/view/waveconfig/waveconfig-model.ts | 10 +++++-- frontend/app/view/waveconfig/waveconfig.tsx | 28 +++++++++++-------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/frontend/app/view/waveconfig/waveconfig-model.ts b/frontend/app/view/waveconfig/waveconfig-model.ts index 4b501d5874..59b3266a79 100644 --- a/frontend/app/view/waveconfig/waveconfig-model.ts +++ b/frontend/app/view/waveconfig/waveconfig-model.ts @@ -9,6 +9,7 @@ import { TabRpcClient } from "@/app/store/wshrpcutil"; import { SecretsContent } from "@/app/view/waveconfig/secretscontent"; import { WaveConfigView } from "@/app/view/waveconfig/waveconfig"; import { WaveAIVisualContent } from "@/app/view/waveconfig/waveaivisual"; +import { isWindows } from "@/util/platformutil"; import { base64ToString, stringToBase64 } from "@/util/util"; import { atom, type PrimitiveAtom } from "jotai"; import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; @@ -22,6 +23,7 @@ export type ConfigFile = { path: string; language?: string; deprecated?: boolean; + description?: string; docsUrl?: string; validator?: ConfigValidator; isSecrets?: boolean; @@ -77,10 +79,11 @@ const configFiles: ConfigFile[] = [ path: "connections.json", language: "json", docsUrl: "https://docs.waveterm.dev/connections", + description: isWindows() ? "SSH hosts and WSL distros" : "SSH hosts", hasJsonView: true, }, { - name: "Widgets", + name: "Sidebar Widgets", path: "widgets.json", language: "json", docsUrl: "https://docs.waveterm.dev/customwidgets", @@ -90,13 +93,14 @@ const configFiles: ConfigFile[] = [ name: "Wave AI Modes", path: "waveai.json", language: "json", - docsUrl: "https://docs.waveterm.dev/waveai-modes", + description: "Local models and BYOK", + docsUrl: "https://docs.waveterm.dev/waveai-modes", validator: validateWaveAiJson, hasJsonView: true, // visualComponent: WaveAIVisualContent, }, { - name: "Backgrounds", + name: "Tab Backgrounds", path: "presets/bg.json", language: "json", docsUrl: "https://docs.waveterm.dev/presets#background-configurations", diff --git a/frontend/app/view/waveconfig/waveconfig.tsx b/frontend/app/view/waveconfig/waveconfig.tsx index cb9bfeff85..1bce94474c 100644 --- a/frontend/app/view/waveconfig/waveconfig.tsx +++ b/frontend/app/view/waveconfig/waveconfig.tsx @@ -41,11 +41,16 @@ const ConfigSidebar = memo(({ model }: ConfigSidebarProps) => {
handleFileSelect(file)} - className={`px-4 py-2 border-b border-border cursor-pointer transition-colors whitespace-nowrap overflow-hidden text-ellipsis ${ + className={`px-4 py-2 border-b border-border cursor-pointer transition-colors ${ selectedFile?.path === file.path ? "bg-accentbg text-primary" : "hover:bg-secondary/50" }`} > - {file.name} +
{file.name}
+ {file.description && ( +
+ {file.description} +
+ )}
))} {deprecatedConfigFiles.length > 0 && ( @@ -168,15 +173,16 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps {selectedFile.docsUrl && ( - - - + + + + + )}
{selectedFile.path} From 6b36d4dbf3002a1ce2ab861d7c6f41b97fc227f5 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 5 Dec 2025 16:23:21 -0800 Subject: [PATCH 8/8] fix nits --- frontend/app/aipanel/aimode.tsx | 8 ++++++-- frontend/app/view/waveconfig/waveconfig-model.ts | 5 ++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/app/aipanel/aimode.tsx b/frontend/app/aipanel/aimode.tsx index 69be37519c..1878af2d13 100644 --- a/frontend/app/aipanel/aimode.tsx +++ b/frontend/app/aipanel/aimode.tsx @@ -61,9 +61,13 @@ function computeCompatibleSections( otherProviderConfigs: any[] ): ConfigSection[] { const currentConfig = aiModeConfigs[currentMode]; - const currentSwitchCompat = currentConfig?.["ai:switchcompat"] || []; - const allConfigs = [...waveProviderConfigs, ...otherProviderConfigs]; + + if (!currentConfig) { + return [{ sectionName: "Incompatible Modes", configs: allConfigs, isIncompatible: true }]; + } + + const currentSwitchCompat = currentConfig["ai:switchcompat"] || []; const compatibleConfigs: any[] = [currentConfig]; const incompatibleConfigs: any[] = []; diff --git a/frontend/app/view/waveconfig/waveconfig-model.ts b/frontend/app/view/waveconfig/waveconfig-model.ts index 59b3266a79..cb2630ad1b 100644 --- a/frontend/app/view/waveconfig/waveconfig-model.ts +++ b/frontend/app/view/waveconfig/waveconfig-model.ts @@ -8,7 +8,6 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { SecretsContent } from "@/app/view/waveconfig/secretscontent"; import { WaveConfigView } from "@/app/view/waveconfig/waveconfig"; -import { WaveAIVisualContent } from "@/app/view/waveconfig/waveaivisual"; import { isWindows } from "@/util/platformutil"; import { base64ToString, stringToBase64 } from "@/util/util"; import { atom, type PrimitiveAtom } from "jotai"; @@ -79,7 +78,7 @@ const configFiles: ConfigFile[] = [ path: "connections.json", language: "json", docsUrl: "https://docs.waveterm.dev/connections", - description: isWindows() ? "SSH hosts and WSL distros" : "SSH hosts", + description: isWindows() ? "SSH hosts and WSL distros" : "SSH hosts", hasJsonView: true, }, { @@ -94,7 +93,7 @@ const configFiles: ConfigFile[] = [ path: "waveai.json", language: "json", description: "Local models and BYOK", - docsUrl: "https://docs.waveterm.dev/waveai-modes", + docsUrl: "https://docs.waveterm.dev/waveai-modes", validator: validateWaveAiJson, hasJsonView: true, // visualComponent: WaveAIVisualContent,