From 7423296f65ec97b19a815b2da835cc3f554a15d2 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 14 Oct 2025 21:58:51 -0700 Subject: [PATCH 1/8] working on linking the real blockid to the tooluse... --- frontend/app/aipanel/aitypes.ts | 1 + frontend/app/block/block-model.ts | 51 ++++++++++++++++++++++++++ frontend/app/block/blockframe.tsx | 16 +++++++- pkg/aiusechat/openai/openai-backend.go | 19 +++++++++- pkg/aiusechat/tools.go | 14 ------- pkg/aiusechat/tools_screenshot.go | 10 +---- pkg/aiusechat/tools_term.go | 10 +---- pkg/aiusechat/tools_web.go | 7 +--- pkg/aiusechat/uctypes/usechat-types.go | 6 ++- pkg/aiusechat/usechat.go | 8 ++-- pkg/wcore/wcore.go | 21 +++++++++++ 11 files changed, 119 insertions(+), 44 deletions(-) create mode 100644 frontend/app/block/block-model.ts diff --git a/frontend/app/aipanel/aitypes.ts b/frontend/app/aipanel/aitypes.ts index f16c1f6e3c..98bbc0756c 100644 --- a/frontend/app/aipanel/aitypes.ts +++ b/frontend/app/aipanel/aitypes.ts @@ -17,6 +17,7 @@ type WaveUIDataTypes = { status: "pending" | "error" | "completed"; errormessage?: string; approval?: "needs-approval" | "user-approved" | "user-denied" | "auto-approved" | "timeout"; + blockid?: string; }; }; diff --git a/frontend/app/block/block-model.ts b/frontend/app/block/block-model.ts new file mode 100644 index 0000000000..e2ce23e374 --- /dev/null +++ b/frontend/app/block/block-model.ts @@ -0,0 +1,51 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { globalStore } from "@/app/store/jotaiStore"; +import * as jotai from "jotai"; + +export interface BlockHighlightType { + blockId: string; + icon: string; +} + +export class BlockModel { + private static instance: BlockModel | null = null; + private blockHighlightAtomCache = new Map>(); + + blockHighlightAtom: jotai.PrimitiveAtom = jotai.atom(null) as jotai.PrimitiveAtom; + + private constructor() { + // Empty for now + } + + getBlockHighlightAtom(blockId: string): jotai.Atom { + let atom = this.blockHighlightAtomCache.get(blockId); + if (!atom) { + atom = jotai.atom((get) => { + const highlight = get(this.blockHighlightAtom); + if (highlight?.blockId === blockId) { + return highlight; + } + return null; + }); + this.blockHighlightAtomCache.set(blockId, atom); + } + return atom; + } + + setBlockHighlight(highlight: BlockHighlightType | null) { + globalStore.set(this.blockHighlightAtom, highlight); + } + + static getInstance(): BlockModel { + if (!BlockModel.instance) { + BlockModel.instance = new BlockModel(); + } + return BlockModel.instance; + } + + static resetInstance(): void { + BlockModel.instance = null; + } +} \ No newline at end of file diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 7e788b5cfd..4884e79304 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -1,6 +1,7 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { BlockModel } from "@/app/block/block-model"; import { blockViewToIcon, blockViewToName, ConnectionButton, getBlockHeaderIcon, Input } from "@/app/block/blockutil"; import { Button } from "@/app/element/button"; import { useDimensionsWithCallbackRef } from "@/app/hook/useDimensions"; @@ -26,6 +27,7 @@ import { MagnifyIcon } from "@/element/magnify"; import { MenuButton } from "@/element/menubutton"; import { NodeModel } from "@/layout/index"; import * as util from "@/util/util"; +import { makeIconClass } from "@/util/util"; import { computeBgStyleFromMeta } from "@/util/waveutil"; import clsx from "clsx"; import * as jotai from "jotai"; @@ -483,9 +485,11 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => { const blockNum = jotai.useAtomValue(nodeModel.blockNum); const isLayoutMode = jotai.useAtomValue(atoms.controlShiftDelayAtom); const showOverlayBlockNums = jotai.useAtomValue(getSettingsKeyAtom("app:showoverlayblocknums")) ?? true; + const blockHighlight = jotai.useAtomValue(BlockModel.getInstance().getBlockHighlightAtom(nodeModel.blockId)); const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", nodeModel.blockId)); const style: React.CSSProperties = {}; let showBlockMask = false; + if (isFocused) { const tabData = jotai.useAtomValue(atoms.tabAtom); const tabActiveBorderColor = tabData?.meta?.["bg:activebordercolor"]; @@ -505,6 +509,7 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => { style.borderColor = blockData.meta["frame:bordercolor"]; } } + let innerElem = null; if (isLayoutMode && showOverlayBlockNums) { showBlockMask = true; @@ -513,9 +518,18 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => {
{blockNum}
); + } else if (blockHighlight) { + showBlockMask = true; + const iconClass = makeIconClass(blockHighlight.icon, false); + innerElem = ( +
+ +
+ ); } + return ( -
+
{innerElem}
); diff --git a/pkg/aiusechat/openai/openai-backend.go b/pkg/aiusechat/openai/openai-backend.go index 6f4a4301f2..d667234227 100644 --- a/pkg/aiusechat/openai/openai-backend.go +++ b/pkg/aiusechat/openai/openai-backend.go @@ -20,6 +20,7 @@ import ( "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" "github.com/wavetermdev/waveterm/pkg/util/utilfn" + "github.com/wavetermdev/waveterm/pkg/wcore" "github.com/wavetermdev/waveterm/pkg/web/sse" ) @@ -813,7 +814,7 @@ func handleOpenAIEvent( // _ = sse.AiMsgToolInputAvailable(st.toolCallID, st.toolName, raw) toolDef := state.chatOpts.GetToolDefinition(st.toolName) - toolUseData := createToolUseData(st.toolCallID, st.toolName, toolDef, ev.Arguments) + toolUseData := createToolUseData(st.toolCallID, st.toolName, toolDef, ev.Arguments, state.chatOpts) state.toolUseData[st.toolCallID] = toolUseData if toolUseData.Approval == uctypes.ApprovalNeedsApproval && state.chatOpts.RegisterToolApproval != nil { state.chatOpts.RegisterToolApproval(st.toolCallID) @@ -840,7 +841,8 @@ func handleOpenAIEvent( return nil, nil } } -func createToolUseData(toolCallID, toolName string, toolDef *uctypes.ToolDefinition, arguments string) *uctypes.UIMessageDataToolUse { + +func createToolUseData(toolCallID, toolName string, toolDef *uctypes.ToolDefinition, arguments string, chatOpts uctypes.WaveChatOpts) *uctypes.UIMessageDataToolUse { toolUseData := &uctypes.UIMessageDataToolUse{ ToolCallId: toolCallID, ToolName: toolName, @@ -868,6 +870,19 @@ func createToolUseData(toolCallID, toolName string, toolDef *uctypes.ToolDefinit toolUseData.Approval = toolDef.ToolApproval(parsedArgs) } + if chatOpts.TabId != "" { + if argsMap, ok := parsedArgs.(map[string]any); ok { + if widgetId, ok := argsMap["widget_id"].(string); ok && widgetId != "" { + ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelFn() + fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, chatOpts.TabId, widgetId) + if err == nil { + toolUseData.BlockId = fullBlockId + } + } + } + } + return toolUseData } diff --git a/pkg/aiusechat/tools.go b/pkg/aiusechat/tools.go index 8b9ba3f82b..091d149806 100644 --- a/pkg/aiusechat/tools.go +++ b/pkg/aiusechat/tools.go @@ -15,20 +15,6 @@ import ( "github.com/wavetermdev/waveterm/pkg/wstore" ) -func resolveBlockIdFromPrefix(tab *waveobj.Tab, blockIdPrefix string) (string, error) { - if len(blockIdPrefix) != 8 { - return "", fmt.Errorf("widget_id must be 8 characters") - } - - for _, blockId := range tab.BlockIds { - if strings.HasPrefix(blockId, blockIdPrefix) { - return blockId, nil - } - } - - return "", fmt.Errorf("widget_id not found: %q", blockIdPrefix) -} - func MakeBlockShortDesc(block *waveobj.Block) string { if block.Meta == nil { return "" diff --git a/pkg/aiusechat/tools_screenshot.go b/pkg/aiusechat/tools_screenshot.go index ad366cd20e..5b4007933f 100644 --- a/pkg/aiusechat/tools_screenshot.go +++ b/pkg/aiusechat/tools_screenshot.go @@ -9,11 +9,10 @@ import ( "time" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" - "github.com/wavetermdev/waveterm/pkg/waveobj" + "github.com/wavetermdev/waveterm/pkg/wcore" "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" "github.com/wavetermdev/waveterm/pkg/wshutil" - "github.com/wavetermdev/waveterm/pkg/wstore" ) func makeTabCaptureBlockScreenshot(tabId string) func(any) (string, error) { @@ -31,12 +30,7 @@ func makeTabCaptureBlockScreenshot(tabId string) func(any) (string, error) { ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() - tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId) - if err != nil { - return "", fmt.Errorf("error getting tab: %w", err) - } - - fullBlockId, err := resolveBlockIdFromPrefix(tab, blockIdPrefix) + fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, tabId, blockIdPrefix) if err != nil { return "", err } diff --git a/pkg/aiusechat/tools_term.go b/pkg/aiusechat/tools_term.go index 5b400be3f8..ad28bcc895 100644 --- a/pkg/aiusechat/tools_term.go +++ b/pkg/aiusechat/tools_term.go @@ -11,11 +11,10 @@ import ( "time" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" - "github.com/wavetermdev/waveterm/pkg/waveobj" + "github.com/wavetermdev/waveterm/pkg/wcore" "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" "github.com/wavetermdev/waveterm/pkg/wshutil" - "github.com/wavetermdev/waveterm/pkg/wstore" ) type TermGetScrollbackToolInput struct { @@ -121,12 +120,7 @@ func GetTermGetScrollbackToolDefinition(tabId string) uctypes.ToolDefinition { ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() - tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId) - if err != nil { - return nil, fmt.Errorf("error getting tab: %w", err) - } - - fullBlockId, err := resolveBlockIdFromPrefix(tab, parsed.WidgetId) + fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, tabId, parsed.WidgetId) if err != nil { return nil, err } diff --git a/pkg/aiusechat/tools_web.go b/pkg/aiusechat/tools_web.go index 80aa9d51f5..3ead729ccd 100644 --- a/pkg/aiusechat/tools_web.go +++ b/pkg/aiusechat/tools_web.go @@ -86,12 +86,7 @@ func GetWebNavigateToolDefinition(tabId string) uctypes.ToolDefinition { ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() - tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId) - if err != nil { - return nil, fmt.Errorf("error getting tab: %w", err) - } - - fullBlockId, err := resolveBlockIdFromPrefix(tab, parsed.WidgetId) + fullBlockId, err := wcore.ResolveBlockIdFromPrefix(ctx, tabId, parsed.WidgetId) if err != nil { return nil, err } diff --git a/pkg/aiusechat/uctypes/usechat-types.go b/pkg/aiusechat/uctypes/usechat-types.go index a8890e243c..7f478e945e 100644 --- a/pkg/aiusechat/uctypes/usechat-types.go +++ b/pkg/aiusechat/uctypes/usechat-types.go @@ -140,6 +140,7 @@ type UIMessageDataToolUse struct { Status string `json:"status"` ErrorMessage string `json:"errormessage,omitempty"` Approval string `json:"approval,omitempty"` + BlockId string `json:"blockid,omitempty"` } func (d *UIMessageDataToolUse) IsApproved() bool { @@ -422,14 +423,15 @@ type WaveChatOpts struct { Config AIOptsType Tools []ToolDefinition SystemPrompt []string - TabStateGenerator func() (string, []ToolDefinition, error) + TabStateGenerator func() (string, []ToolDefinition, string, error) WidgetAccess bool RegisterToolApproval func(string) AllowNativeWebSearch bool - // emphemeral to the step + // ephemeral to the step TabState string TabTools []ToolDefinition + TabId string } func (opts *WaveChatOpts) GetToolDefinition(toolName string) *ToolDefinition { diff --git a/pkg/aiusechat/usechat.go b/pkg/aiusechat/usechat.go index a7e54e27a9..7a661f10bf 100644 --- a/pkg/aiusechat/usechat.go +++ b/pkg/aiusechat/usechat.go @@ -362,10 +362,11 @@ func RunAIChat(ctx context.Context, sseHandler *sse.SSEHandlerCh, chatOpts uctyp var cont *uctypes.WaveContinueResponse for { if chatOpts.TabStateGenerator != nil { - tabState, tabTools, tabErr := chatOpts.TabStateGenerator() + tabState, tabTools, tabId, tabErr := chatOpts.TabStateGenerator() if tabErr == nil { chatOpts.TabState = tabState chatOpts.TabTools = tabTools + chatOpts.TabId = tabId } } stopReason, rtnMessage, err := runAIChatStep(ctx, sseHandler, chatOpts, cont) @@ -621,8 +622,9 @@ func WaveAIPostMessageHandler(w http.ResponseWriter, r *http.Request) { chatOpts.SystemPrompt = []string{SystemPromptText} } - chatOpts.TabStateGenerator = func() (string, []uctypes.ToolDefinition, error) { - return GenerateTabStateAndTools(r.Context(), req.TabId, req.WidgetAccess) + chatOpts.TabStateGenerator = func() (string, []uctypes.ToolDefinition, string, error) { + tabState, tabTools, err := GenerateTabStateAndTools(r.Context(), req.TabId, req.WidgetAccess) + return tabState, tabTools, req.TabId, err } // Validate the message diff --git a/pkg/wcore/wcore.go b/pkg/wcore/wcore.go index 04f1c873e3..812a2d0a42 100644 --- a/pkg/wcore/wcore.go +++ b/pkg/wcore/wcore.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "log" + "strings" "time" "github.com/google/uuid" @@ -108,3 +109,23 @@ func SendWaveObjUpdate(oref waveobj.ORef) { }, }) } + + +func ResolveBlockIdFromPrefix(ctx context.Context, tabId string, blockIdPrefix string) (string, error) { + if len(blockIdPrefix) != 8 { + return "", fmt.Errorf("widget_id must be 8 characters") + } + + tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId) + if err != nil { + return "", fmt.Errorf("error getting tab: %w", err) + } + + for _, blockId := range tab.BlockIds { + if strings.HasPrefix(blockId, blockIdPrefix) { + return blockId, nil + } + } + + return "", fmt.Errorf("widget_id not found: %q", blockIdPrefix) +} From 9013ec4ddbbf101412cb1b2117da32704f351c49 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 14 Oct 2025 22:10:35 -0700 Subject: [PATCH 2/8] linking the tools to the blocks via highlight --- frontend/app/aipanel/aimessage.tsx | 24 +++++++++++++++++++++++- frontend/app/block/blockframe.tsx | 8 ++++++-- package-lock.json | 4 ++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/frontend/app/aipanel/aimessage.tsx b/frontend/app/aipanel/aimessage.tsx index 02ccb5d947..200e6213a0 100644 --- a/frontend/app/aipanel/aimessage.tsx +++ b/frontend/app/aipanel/aimessage.tsx @@ -1,6 +1,7 @@ // Copyright 2025, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { BlockModel } from "@/app/block/block-model"; import { WaveStreamdown } from "@/app/element/streamdown"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; @@ -238,8 +239,29 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { }); }; + const handleMouseEnter = () => { + if (toolData.blockid) { + BlockModel.getInstance().setBlockHighlight({ + blockId: toolData.blockid, + icon: "sparkles", + }); + } + }; + + const handleMouseLeave = () => { + if (toolData.blockid) { + BlockModel.getInstance().setBlockHighlight(null); + } + }; + return ( -
+
{statusIcon}
{toolData.toolname}
diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 4884e79304..7920c5e8b3 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -510,6 +510,10 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => { } } + if (blockHighlight && !style.borderColor) { + style.borderColor = "rgb(59, 130, 246)"; + } + let innerElem = null; if (isLayoutMode && showOverlayBlockNums) { showBlockMask = true; @@ -523,13 +527,13 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => { const iconClass = makeIconClass(blockHighlight.icon, false); innerElem = (
- +
); } return ( -
+
{innerElem}
); diff --git a/package-lock.json b/package-lock.json index 74aec0396c..54a16f19ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.12.0-beta.2", + "version": "0.12.0-beta.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.12.0-beta.2", + "version": "0.12.0-beta.3", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ From 9df81261639faa273e73e7eee0947c3cb70b4529 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 15 Oct 2025 11:51:55 -0700 Subject: [PATCH 3/8] remove old AI widget from the sidebar if you have no custom settings --- frontend/app/store/global.ts | 13 +++++++++++++ frontend/app/workspace/widgets.tsx | 7 ++++++- frontend/types/custom.d.ts | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 425f8ee7d3..9a1b273143 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -104,6 +104,18 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { const settingsAtom = atom((get) => { return get(fullConfigAtom)?.settings ?? {}; }) as Atom; + const hasCustomAIPresetsAtom = atom((get) => { + const fullConfig = get(fullConfigAtom); + if (!fullConfig?.presets) { + return false; + } + for (const presetId in fullConfig.presets) { + if (presetId.startsWith("ai@") && presetId !== "ai@global" && presetId !== "ai@wave") { + return true; + } + } + return false; + }) as Atom; const tabAtom: Atom = atom((get) => { return WOS.getObjectValue(WOS.makeORef("tab", initOpts.tabId), get); }); @@ -160,6 +172,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { workspace: workspaceAtom, fullConfigAtom, settingsAtom, + hasCustomAIPresetsAtom, tabAtom, staticTabId: staticTabIdAtom, isFullScreen: isFullScreenAtom, diff --git a/frontend/app/workspace/widgets.tsx b/frontend/app/workspace/widgets.tsx index b56471c4d5..f22e784cf6 100644 --- a/frontend/app/workspace/widgets.tsx +++ b/frontend/app/workspace/widgets.tsx @@ -69,6 +69,7 @@ const Widget = memo(({ widget, mode }: { widget: WidgetConfigType; mode: "normal const Widgets = memo(() => { const fullConfig = useAtomValue(atoms.fullConfigAtom); + const hasCustomAIPresets = useAtomValue(atoms.hasCustomAIPresetsAtom); const [mode, setMode] = useState<"normal" | "compact" | "supercompact">("normal"); const containerRef = useRef(null); const measurementRef = useRef(null); @@ -93,7 +94,11 @@ const Widgets = memo(() => { magnified: true, }; const showHelp = fullConfig?.settings?.["widget:showhelp"] ?? true; - const widgets = sortByDisplayOrder(fullConfig?.widgets); + const widgetsMap = fullConfig?.widgets ?? {}; + const filteredWidgets = hasCustomAIPresets + ? widgetsMap + : Object.fromEntries(Object.entries(widgetsMap).filter(([key]) => key !== "defwidget@ai")); + const widgets = sortByDisplayOrder(filteredWidgets); const checkModeNeeded = useCallback(() => { if (!containerRef.current || !measurementRef.current) return; diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 52a5f50d34..e816e548eb 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -14,6 +14,7 @@ declare global { workspace: jotai.Atom; // driven from WOS fullConfigAtom: jotai.PrimitiveAtom; // driven from WOS, settings -- updated via WebSocket settingsAtom: jotai.Atom; // derrived from fullConfig + hasCustomAIPresetsAtom: jotai.Atom; // derived from fullConfig tabAtom: jotai.Atom; // driven from WOS staticTabId: jotai.Atom; isFullScreen: jotai.PrimitiveAtom; From 4204092346a5992aa8a18a2eaa008d68ee67631b Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 15 Oct 2025 11:59:08 -0700 Subject: [PATCH 4/8] deprecate AI presets, and update presets general docs to remove AI references --- docs/docs/ai-presets.mdx | 4 ++++ docs/docs/presets.mdx | 18 +++--------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/docs/docs/ai-presets.mdx b/docs/docs/ai-presets.mdx index 7af440692b..b8c7b34546 100644 --- a/docs/docs/ai-presets.mdx +++ b/docs/docs/ai-presets.mdx @@ -3,6 +3,10 @@ sidebar_position: 3.6 id: "ai-presets" title: "AI Presets" --- +:::warning Deprecation Notice +The AI Widget and its presets are being replaced by [Wave AI](./waveai.mdx). Please refer to the Wave AI documentation for the latest AI features and configuration options. +::: + ![AI Presets Menu](./img/ai-presets.png#right) diff --git a/docs/docs/presets.mdx b/docs/docs/presets.mdx index 158f35c7a7..4b83f6a43d 100644 --- a/docs/docs/presets.mdx +++ b/docs/docs/presets.mdx @@ -6,9 +6,8 @@ title: "Presets" # Presets -Wave's preset system allows you to save and apply multiple configuration settings at once. Presets can be used in two different scenarios: +Wave's preset system allows you to save and apply multiple configuration settings at once. Presets are used for: -- AI models: Configure different AI providers and models (see [AI Presets](/ai-presets)) - Tab backgrounds: Apply visual styles to your tabs ## Managing Presets @@ -25,7 +24,7 @@ You can easily edit your presets using the built-in editor: ```bash wsh editconfig presets.json # Edit main presets file -wsh editconfig presets/ai.json # Edit AI presets +wsh editconfig presets/bg.json # Edit background presets ``` ::: @@ -47,7 +46,6 @@ Presets follow this format: The `preset-type` determines where the preset appears in Wave's interface: -- `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 ### Common Keys @@ -58,19 +56,9 @@ The `preset-type` determines where the preset appears in Wave's interface: | display:order | float | Controls the order in the menu (optional) | :::info -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. +When a preset is applied, it overrides the default configuration values for that tab or block. Using `bg:*` will clear any previously overridden values, setting them back to defaults. It's recommended to include this key in your presets to ensure a clean slate. ::: -## AI Presets - -For configuring AI providers and models, see our dedicated [AI Presets](/ai-presets) documentation. It covers setting up presets for: - -- Local LLMs via Ollama -- Azure OpenAI -- Anthropic's Claude -- Perplexity -- And more - ## 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. From 591e4232a3f7c2ce4ff555843877c58bef2d3b6d Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 15 Oct 2025 12:06:39 -0700 Subject: [PATCH 5/8] remove lots of legacy stuff about the AI widget from our docs --- docs/docs/customization.mdx | 2 -- docs/docs/faq.mdx | 6 ------ docs/docs/gettingstarted.mdx | 4 ++-- docs/docs/index.mdx | 8 ++++++-- docs/docs/keybindings.mdx | 8 +++++--- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/docs/customization.mdx b/docs/docs/customization.mdx index ed290d0a68..02fedca70a 100644 --- a/docs/docs/customization.mdx +++ b/docs/docs/customization.mdx @@ -81,6 +81,4 @@ wsh setbg --print "#ff0000" For more advanced customization options including gradients, colors, and saving your own background presets, check out our [Background Configuration](/presets#background-configurations) documentation. -## Presets -For more advanced customization, to set up multiple AI models, and your own tab backgrounds, check out our [Presets Documentation](./presets). diff --git a/docs/docs/faq.mdx b/docs/docs/faq.mdx index 559944c4e4..74967cbb91 100644 --- a/docs/docs/faq.mdx +++ b/docs/docs/faq.mdx @@ -6,12 +6,6 @@ title: "FAQ" # FAQ -### How do I configure Wave to use different AI models/providers? - -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. - -See our [AI Presets documentation](/ai-presets) for detailed setup instructions for each provider. - ### How do I enable Claude Code support with Shift+Enter? Wave supports Claude Code and similar AI coding tools that expect Shift+Enter to send an escape sequence + newline (`\u001b\n`) instead of a regular carriage return. This can be enabled using the `term:shiftenternewline` configuration setting. diff --git a/docs/docs/gettingstarted.mdx b/docs/docs/gettingstarted.mdx index cffcdbf083..e9df15ca54 100644 --- a/docs/docs/gettingstarted.mdx +++ b/docs/docs/gettingstarted.mdx @@ -90,7 +90,7 @@ You can also download installers directly from our [Downloads page](https://www. ### Tabs and Blocks - **Tabs**: Like browser tabs, these help organize your work. Create new tabs with . -- **Blocks**: The building blocks of Wave. Each block can be a terminal, web browser, file preview, AI chat, or other widget. +- **Blocks**: The building blocks of Wave. Each block can be a terminal, web browser, file preview, or other widget. - **Layout**: Blocks can be dragged, dropped, and resized to create your ideal layout. ### Key Features @@ -152,7 +152,7 @@ You can also download installers directly from our [Downloads page](https://www. - Explore [Key Bindings](./keybindings) to work more efficiently - Learn about [Tab Layouts](./layout) to organize your workspace - Set up [Custom Widgets](./customwidgets) for quick access to your tools -- Configure [AI Presets](./ai-presets) to use your preferred AI models +- Configure [Wave AI](./waveai) to use your preferred AI models - Check out [Configuration](./config) for detailed customization options ## Getting Help diff --git a/docs/docs/index.mdx b/docs/docs/index.mdx index 1c5ee7f3f8..f1665faae8 100644 --- a/docs/docs/index.mdx +++ b/docs/docs/index.mdx @@ -19,6 +19,12 @@ Check out [Getting Started](./gettingstarted) for installation instructions. ![Wave Screenshot](./img/wave-screenshot.webp) + | Clear AI Chat | +| Key | Function | +| ----------------------- | ----------------------- | +| | Toggle WaveAI panel | +| | Focus WaveAI input | +| | Clear AI Chat | ## Terminal Keybindings From 91045cbd42b04553c614f32b103e89808608f8c3 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 15 Oct 2025 12:21:03 -0700 Subject: [PATCH 6/8] safer highlight. timeout after 2s --- frontend/app/aipanel/aimessage.tsx | 44 +++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/frontend/app/aipanel/aimessage.tsx b/frontend/app/aipanel/aimessage.tsx index 200e6213a0..31756ce860 100644 --- a/frontend/app/aipanel/aimessage.tsx +++ b/frontend/app/aipanel/aimessage.tsx @@ -6,7 +6,7 @@ import { WaveStreamdown } from "@/app/element/streamdown"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { cn } from "@/util/util"; -import { memo, useEffect, useState } from "react"; +import { memo, useEffect, useRef, useState } from "react"; import { getFileIcon } from "./ai-utils"; import { WaveUIMessage, WaveUIMessagePart } from "./aitypes"; import { WaveAIModel } from "./waveai-model"; @@ -202,6 +202,8 @@ interface AIToolUseProps { const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { const toolData = part.data; const [userApprovalOverride, setUserApprovalOverride] = useState(null); + const highlightTimeoutRef = useRef(null); + const highlightedBlockIdRef = useRef(null); const statusIcon = toolData.status === "completed" ? "✓" : toolData.status === "error" ? "✗" : "•"; const statusColor = @@ -223,6 +225,14 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { return () => clearInterval(interval); }, [isStreaming, effectiveApproval, toolData.toolcallid]); + useEffect(() => { + return () => { + if (highlightTimeoutRef.current) { + clearTimeout(highlightTimeoutRef.current); + } + }; + }, []); + const handleApprove = () => { setUserApprovalOverride("user-approved"); RpcApi.WaveAIToolApproveCommand(TabRpcClient, { @@ -240,17 +250,37 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { }; const handleMouseEnter = () => { - if (toolData.blockid) { - BlockModel.getInstance().setBlockHighlight({ - blockId: toolData.blockid, - icon: "sparkles", - }); + if (!toolData.blockid) return; + + if (highlightTimeoutRef.current) { + clearTimeout(highlightTimeoutRef.current); } + + highlightedBlockIdRef.current = toolData.blockid; + BlockModel.getInstance().setBlockHighlight({ + blockId: toolData.blockid, + icon: "sparkles", + }); + + highlightTimeoutRef.current = setTimeout(() => { + if (highlightedBlockIdRef.current === toolData.blockid) { + BlockModel.getInstance().setBlockHighlight(null); + highlightedBlockIdRef.current = null; + } + }, 2000); }; const handleMouseLeave = () => { - if (toolData.blockid) { + if (!toolData.blockid) return; + + if (highlightTimeoutRef.current) { + clearTimeout(highlightTimeoutRef.current); + highlightTimeoutRef.current = null; + } + + if (highlightedBlockIdRef.current === toolData.blockid) { BlockModel.getInstance().setBlockHighlight(null); + highlightedBlockIdRef.current = null; } }; From 404c6c9b6d4d34a6e09e50448479ce8da2c4a694 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 15 Oct 2025 12:21:56 -0700 Subject: [PATCH 7/8] remove old presets/ai.json reference --- docs/docs/presets.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/presets.mdx b/docs/docs/presets.mdx index 4b83f6a43d..31fc5f57d7 100644 --- a/docs/docs/presets.mdx +++ b/docs/docs/presets.mdx @@ -17,7 +17,7 @@ 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/bg.json`, `presets/ai.json`). +All presets are aggregated regardless of which file they're in, so you can use the `presets` directory to organize them (e.g., `presets/bg.json`). :::info You can easily edit your presets using the built-in editor: From 98939fa655c36f5256d4c2f8290a4edfeea77caa Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 15 Oct 2025 16:10:24 -0700 Subject: [PATCH 8/8] remove cursor-pointer --- frontend/app/aipanel/aimessage.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/app/aipanel/aimessage.tsx b/frontend/app/aipanel/aimessage.tsx index 31756ce860..e426e9114a 100644 --- a/frontend/app/aipanel/aimessage.tsx +++ b/frontend/app/aipanel/aimessage.tsx @@ -286,9 +286,7 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => { return (