Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/docs/ai-presets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 0 additions & 2 deletions docs/docs/customization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).
6 changes: 0 additions & 6 deletions docs/docs/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/gettingstarted.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Kbd k="Cmd:t"/>.
- **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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions docs/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ Check out [Getting Started](./gettingstarted) for installation instructions.
![Wave Screenshot](./img/wave-screenshot.webp)

<CardGroup>
<Card
href="./waveai"
icon="fa-sparkles"
title="Wave AI"
description="Context-aware terminal assistant with access to terminal output, widgets, and filesystem."
/>
<Card
href="./customization"
icon="fa-paintbrush"
Expand Down Expand Up @@ -80,8 +86,6 @@ Other References:

## Roadmap

Wave is constantly improving! Our roadmap will be continuously updated with our goals for each release. You can find it [here](https://github.com/wavetermdev/waveterm/blob/main/ROADMAP.md).

Want to provide input to our future releases? Connect with us on [Discord](https://discord.gg/XfvZ334gwU) or open a [Feature Request](https://github.com/wavetermdev/waveterm/issues/new/choose)!

## Links
Expand Down
8 changes: 5 additions & 3 deletions docs/docs/keybindings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ Chords are shown with a + between the keys. You have 2 seconds to hit the 2nd ch

## WaveAI Keybindings

| Key | Function |
| ---------------- | ------------- |
| <Kbd k="Cmd:l"/> | Clear AI Chat |
| Key | Function |
| ----------------------- | ----------------------- |
| <Kbd k="Cmd:Shift:a"/> | Toggle WaveAI panel |
| <Kbd k="Ctrl:Shift:0"/> | Focus WaveAI input |
| <Kbd k="Cmd:k"/> | Clear AI Chat |

## Terminal Keybindings

Expand Down
20 changes: 4 additions & 16 deletions docs/docs/presets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,14 +17,14 @@ 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:

```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
```

:::
Expand All @@ -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
Expand All @@ -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.
:::
Comment on lines +59 to 60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Clarify scope: “tab or block” vs. tab-only bg presets.

The “preset-type” section lists only bg (tab menu). If block-level application isn’t supported, remove “or block”; if it is, mention that in the preset-type list.

-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.
+When a preset is applied, it overrides the default configuration values for that tab. 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.

Alternatively (if blocks are supported), add a second bullet under “The preset-type determines...” to document block usage.


🏁 Script executed:

#!/bin/bash
rg -n -C2 'preset-type' docs/docs/presets.mdx
rg -n 'block' docs/docs/presets.mdx

Length of output: 861


Add block-level details to preset-type docs
The bg preset-type also configures block-level backgrounds (e.g., bg:bordercolor, bg:activebordercolor). Under “The preset-type determines…,” add a second bullet noting block usage:

  • bg: Appears in the “Backgrounds” submenu when right-clicking a tab, and applies to blocks via keys like bg:bordercolor and bg:activebordercolor.
🤖 Prompt for AI Agents
In docs/docs/presets.mdx around lines 59-60, add a second bullet under “The
`preset-type` determines…” explaining that the `bg` preset-type not only appears
in the “Backgrounds” submenu when right-clicking a tab but also configures
block-level backgrounds via keys like `bg:bordercolor` and
`bg:activebordercolor`; update that section to include this concise bullet so
readers know `bg` applies to both tabs and blocks with those example keys.


## 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.
Expand Down
54 changes: 52 additions & 2 deletions frontend/app/aipanel/aimessage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// 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";
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";
Expand Down Expand Up @@ -201,6 +202,8 @@ interface AIToolUseProps {
const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
const toolData = part.data;
const [userApprovalOverride, setUserApprovalOverride] = useState<string | null>(null);
const highlightTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const highlightedBlockIdRef = useRef<string | null>(null);

const statusIcon = toolData.status === "completed" ? "✓" : toolData.status === "error" ? "✗" : "•";
const statusColor =
Expand All @@ -222,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, {
Expand All @@ -238,8 +249,47 @@ const AIToolUse = memo(({ part, isStreaming }: AIToolUseProps) => {
});
};

const handleMouseEnter = () => {
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) return;

if (highlightTimeoutRef.current) {
clearTimeout(highlightTimeoutRef.current);
highlightTimeoutRef.current = null;
}

if (highlightedBlockIdRef.current === toolData.blockid) {
BlockModel.getInstance().setBlockHighlight(null);
highlightedBlockIdRef.current = null;
}
};
Comment on lines +252 to +285
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Race condition: timeout can clear another component's active highlight.

When multiple AIToolUse components are hovered in quick succession, the timeout from an earlier component can incorrectly clear the highlight set by a later component. The check highlightedBlockIdRef.current === toolData.blockid only validates the local ref, not whether the global highlight still corresponds to this component's blockid.

Scenario:

  1. Hover over Tool A (block1) → sets highlight to block1, schedules 2s timeout.
  2. Hover over Tool B (block2) → sets highlight to block2, schedules 2s timeout (A's timeout still pending).
  3. A's timeout fires → checks highlightedBlockIdRef.current === toolData.blockid (true) → clears global highlight.
  4. Result: B's highlight vanishes prematurely even though the user is still interacting with B.

Apply this diff to verify the global state before clearing:

 highlightTimeoutRef.current = setTimeout(() => {
-    if (highlightedBlockIdRef.current === toolData.blockid) {
+    const currentHighlight = globalStore.get(BlockModel.getInstance().blockHighlightAtom);
+    if (highlightedBlockIdRef.current === toolData.blockid && currentHighlight?.blockId === toolData.blockid) {
         BlockModel.getInstance().setBlockHighlight(null);
         highlightedBlockIdRef.current = null;
     }
 }, 2000);

Also update handleMouseLeave for consistency:

 const handleMouseLeave = () => {
     if (!toolData.blockid) return;

     if (highlightTimeoutRef.current) {
         clearTimeout(highlightTimeoutRef.current);
         highlightTimeoutRef.current = null;
     }

-    if (highlightedBlockIdRef.current === toolData.blockid) {
+    const currentHighlight = globalStore.get(BlockModel.getInstance().blockHighlightAtom);
+    if (highlightedBlockIdRef.current === toolData.blockid && currentHighlight?.blockId === toolData.blockid) {
         BlockModel.getInstance().setBlockHighlight(null);
         highlightedBlockIdRef.current = null;
     }
 };

You'll need to import globalStore:

+import { globalStore } from "@/app/store/global";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleMouseEnter = () => {
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) return;
if (highlightTimeoutRef.current) {
clearTimeout(highlightTimeoutRef.current);
highlightTimeoutRef.current = null;
}
if (highlightedBlockIdRef.current === toolData.blockid) {
BlockModel.getInstance().setBlockHighlight(null);
highlightedBlockIdRef.current = null;
}
};
// at the top of the file, alongside your other imports
import { globalStore } from "@/app/store/global";
const handleMouseEnter = () => {
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(() => {
const currentHighlight = globalStore.get(
BlockModel.getInstance().blockHighlightAtom
);
if (
highlightedBlockIdRef.current === toolData.blockid &&
currentHighlight?.blockId === toolData.blockid
) {
BlockModel.getInstance().setBlockHighlight(null);
highlightedBlockIdRef.current = null;
}
}, 2000);
};
const handleMouseLeave = () => {
if (!toolData.blockid) return;
if (highlightTimeoutRef.current) {
clearTimeout(highlightTimeoutRef.current);
highlightTimeoutRef.current = null;
}
const currentHighlight = globalStore.get(
BlockModel.getInstance().blockHighlightAtom
);
if (
highlightedBlockIdRef.current === toolData.blockid &&
currentHighlight?.blockId === toolData.blockid
) {
BlockModel.getInstance().setBlockHighlight(null);
highlightedBlockIdRef.current = null;
}
};
🤖 Prompt for AI Agents
In frontend/app/aipanel/aimessage.tsx around lines 252 to 285, the timeout
handler and mouse-leave logic can clear another component's global highlight
because they only check the component-local ref; update both handlers to verify
the global store's highlighted block id matches this component's
toolData.blockid before clearing the global highlight, and only clear if it
matches (i.e., read globalStore.getHighlightedBlockId() or equivalent and
compare to toolData.blockid), keep clearing and nulling the local timeout/ref as
before, and import globalStore at the top of the file.


return (
<div className={cn("flex items-start gap-2 p-2 rounded bg-gray-800 border border-gray-700", statusColor)}>
<div
className={cn("flex items-start gap-2 p-2 rounded bg-gray-800 border border-gray-700", statusColor)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<span className="font-bold">{statusIcon}</span>
<div className="flex-1">
<div className="font-semibold">{toolData.toolname}</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/app/aipanel/aitypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type WaveUIDataTypes = {
status: "pending" | "error" | "completed";
errormessage?: string;
approval?: "needs-approval" | "user-approved" | "user-denied" | "auto-approved" | "timeout";
blockid?: string;
};
};

Expand Down
51 changes: 51 additions & 0 deletions frontend/app/block/block-model.ts
Original file line number Diff line number Diff line change
@@ -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<string, jotai.Atom<BlockHighlightType | null>>();

blockHighlightAtom: jotai.PrimitiveAtom<BlockHighlightType> = jotai.atom(null) as jotai.PrimitiveAtom<BlockHighlightType>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the type declaration to match the nullable initialization.

The atom is initialized with null but typed as PrimitiveAtom<BlockHighlightType> (non-nullable). This type mismatch could lead to type safety issues.

Apply this diff to fix the type:

-    blockHighlightAtom: jotai.PrimitiveAtom<BlockHighlightType> = jotai.atom(null) as jotai.PrimitiveAtom<BlockHighlightType>;
+    blockHighlightAtom: jotai.PrimitiveAtom<BlockHighlightType | null> = jotai.atom<BlockHighlightType | null>(null);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
blockHighlightAtom: jotai.PrimitiveAtom<BlockHighlightType> = jotai.atom(null) as jotai.PrimitiveAtom<BlockHighlightType>;
blockHighlightAtom: jotai.PrimitiveAtom<BlockHighlightType | null> = jotai.atom<BlockHighlightType | null>(null);
🤖 Prompt for AI Agents
In frontend/app/block/block-model.ts around line 16, the atom is initialized
with null but typed as jotai.PrimitiveAtom<BlockHighlightType> (non-nullable);
change the type to include null (jotai.PrimitiveAtom<BlockHighlightType | null>)
and/or annotate the atom generic as jotai.atom<BlockHighlightType | null>(null)
so the declared type matches the nullable initialization.


private constructor() {
// Empty for now
}

getBlockHighlightAtom(blockId: string): jotai.Atom<BlockHighlightType | null> {
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;
}
}
20 changes: 19 additions & 1 deletion frontend/app/block/blockframe.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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<Block>(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"];
Expand All @@ -505,6 +509,11 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => {
style.borderColor = blockData.meta["frame:bordercolor"];
}
}

if (blockHighlight && !style.borderColor) {
style.borderColor = "rgb(59, 130, 246)";
}

let innerElem = null;
if (isLayoutMode && showOverlayBlockNums) {
showBlockMask = true;
Expand All @@ -513,9 +522,18 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => {
<div className="bignum">{blockNum}</div>
</div>
);
} else if (blockHighlight) {
showBlockMask = true;
const iconClass = makeIconClass(blockHighlight.icon, false);
innerElem = (
<div className="block-mask-inner">
<i className={iconClass} style={{ fontSize: "48px", opacity: 0.5 }} />
</div>
);
}

return (
<div className={clsx("block-mask", { "show-block-mask": showBlockMask })} style={style}>
<div className={clsx("block-mask", { "show-block-mask": showBlockMask, "bg-blue-500/10": blockHighlight })} style={style}>
{innerElem}
</div>
);
Expand Down
13 changes: 13 additions & 0 deletions frontend/app/store/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
const settingsAtom = atom((get) => {
return get(fullConfigAtom)?.settings ?? {};
}) as Atom<SettingsType>;
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<boolean>;
const tabAtom: Atom<Tab> = atom((get) => {
return WOS.getObjectValue(WOS.makeORef("tab", initOpts.tabId), get);
});
Expand Down Expand Up @@ -160,6 +172,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
workspace: workspaceAtom,
fullConfigAtom,
settingsAtom,
hasCustomAIPresetsAtom,
tabAtom,
staticTabId: staticTabIdAtom,
isFullScreen: isFullScreenAtom,
Expand Down
7 changes: 6 additions & 1 deletion frontend/app/workspace/widgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>(null);
const measurementRef = useRef<HTMLDivElement>(null);
Expand All @@ -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;
Expand Down
Loading
Loading