Compare commits
5 Commits
v0.10.3-de
...
v0.10.3-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f6c8523c0 | ||
|
|
8c24a7daf3 | ||
|
|
682937e945 | ||
|
|
35ff359c0f | ||
|
|
c7195469bd |
19
README.md
19
README.md
@@ -44,19 +44,22 @@ Run CodeNomad as a local server and access it via your web browser. Perfect for
|
|||||||
npx @neuralnomads/codenomad --launch
|
npx @neuralnomads/codenomad --launch
|
||||||
```
|
```
|
||||||
|
|
||||||
For dev version
|
Full server/CLI documentation (flags + env vars, TLS, auth, remote access):
|
||||||
|
- [packages/server/README.md](packages/server/README.md)
|
||||||
|
|
||||||
|
To see all available options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @neuralnomads/codenomad --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🧪 Dev Releases
|
||||||
|
Bleeding-edge builds are published as GitHub pre-releases and are generated automatically from the `dev` branch.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx @neuralnomads/codenomad-dev --launch
|
npx @neuralnomads/codenomad-dev --launch
|
||||||
```
|
```
|
||||||
|
|
||||||
Dev builds are published as GitHub pre-releases:
|
|
||||||
https://github.com/shantur/CodeNomad/releases
|
|
||||||
|
|
||||||
Dev releases are bleeding-edge builds, generated automatically every time a new commit is pushed to the `dev` branch.
|
|
||||||
|
|
||||||
This command starts the server and opens the web client in your default browser.
|
|
||||||
|
|
||||||
## Highlights
|
## Highlights
|
||||||
|
|
||||||
- **Multi-Instance**: Juggle several OpenCode sessions side-by-side with tabs.
|
- **Multi-Instance**: Juggle several OpenCode sessions side-by-side with tabs.
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ You can run CodeNomad directly without installing it:
|
|||||||
npx @neuralnomads/codenomad --launch
|
npx @neuralnomads/codenomad --launch
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To list all CLI options:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx @neuralnomads/codenomad --help
|
||||||
|
```
|
||||||
|
|
||||||
On startup, CodeNomad prints two URLs:
|
On startup, CodeNomad prints two URLs:
|
||||||
|
|
||||||
- `Local Connection URL : ...` (used by desktop shells)
|
- `Local Connection URL : ...` (used by desktop shells)
|
||||||
@@ -44,6 +50,16 @@ npm install -g @neuralnomads/codenomad
|
|||||||
codenomad --launch
|
codenomad --launch
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Install Locally (per-project)
|
||||||
|
If you prefer to install CodeNomad into a project and run the local binary:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @neuralnomads/codenomad
|
||||||
|
npx codenomad --launch
|
||||||
|
```
|
||||||
|
|
||||||
|
(`npx codenomad ...` will use `./node_modules/.bin/codenomad` when present.)
|
||||||
|
|
||||||
### Common Flags
|
### Common Flags
|
||||||
You can configure the server using flags or environment variables:
|
You can configure the server using flags or environment variables:
|
||||||
|
|
||||||
@@ -63,10 +79,30 @@ You can configure the server using flags or environment variables:
|
|||||||
| `--config <path>` | `CLI_CONFIG` | Config file location |
|
| `--config <path>` | `CLI_CONFIG` | Config file location |
|
||||||
| `--launch` | `CLI_LAUNCH` | Open the UI in a Chromium-based browser |
|
| `--launch` | `CLI_LAUNCH` | Open the UI in a Chromium-based browser |
|
||||||
| `--log-level <level>` | `CLI_LOG_LEVEL` | Logging level (trace, debug, info, warn, error) |
|
| `--log-level <level>` | `CLI_LOG_LEVEL` | Logging level (trace, debug, info, warn, error) |
|
||||||
|
| `--log-destination <path>` | `CLI_LOG_DESTINATION` | Log destination file (defaults to stdout) |
|
||||||
| `--username <username>` | `CODENOMAD_SERVER_USERNAME` | Username for CodeNomad's internal auth (default `codenomad`) |
|
| `--username <username>` | `CODENOMAD_SERVER_USERNAME` | Username for CodeNomad's internal auth (default `codenomad`) |
|
||||||
| `--password <password>` | `CODENOMAD_SERVER_PASSWORD` | Password for CodeNomad's internal auth |
|
| `--password <password>` | `CODENOMAD_SERVER_PASSWORD` | Password for CodeNomad's internal auth |
|
||||||
| `--generate-token` | `CODENOMAD_GENERATE_TOKEN` | Emit a one-time local bootstrap token for desktop flows |
|
| `--generate-token` | `CODENOMAD_GENERATE_TOKEN` | Emit a one-time local bootstrap token for desktop flows |
|
||||||
| `--dangerously-skip-auth` | `CODENOMAD_SKIP_AUTH` | Disable CodeNomad's internal auth (use only behind a trusted perimeter) |
|
| `--dangerously-skip-auth` | `CODENOMAD_SKIP_AUTH` | Disable CodeNomad's internal auth (use only behind a trusted perimeter) |
|
||||||
|
| `--ui-dir <path>` | `CLI_UI_DIR` | Directory containing the built UI bundle |
|
||||||
|
| `--ui-dev-server <url>` | `CLI_UI_DEV_SERVER` | Proxy UI requests to a running dev server (requires `--https=false --http=true`) |
|
||||||
|
| `--ui-no-update` | `CLI_UI_NO_UPDATE` | Disable remote UI updates |
|
||||||
|
| `--ui-auto-update <enabled>` | `CLI_UI_AUTO_UPDATE` | Enable remote UI updates (true|false) |
|
||||||
|
| `--ui-manifest-url <url>` | `CLI_UI_MANIFEST_URL` | Remote UI manifest URL |
|
||||||
|
|
||||||
|
### Dev Releases (Advanced)
|
||||||
|
If you want the latest bleeding-edge builds (published as GitHub pre-releases), use the dev package:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx @neuralnomads/codenomad-dev --launch
|
||||||
|
```
|
||||||
|
|
||||||
|
These environment variables control how CodeNomad checks for dev updates:
|
||||||
|
|
||||||
|
| Env Variable | Description |
|
||||||
|
|-------------|-------------|
|
||||||
|
| `CODENOMAD_UPDATE_CHANNEL` | Update channel (use `dev` to enable dev build update checks) |
|
||||||
|
| `CODENOMAD_GITHUB_REPO` | GitHub repo used for dev release checks (default `NeuralNomadsAI/CodeNomad`) |
|
||||||
|
|
||||||
### HTTP vs HTTPS
|
### HTTP vs HTTPS
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ const App: Component = () => {
|
|||||||
preferences,
|
preferences,
|
||||||
recordWorkspaceLaunch,
|
recordWorkspaceLaunch,
|
||||||
toggleShowThinkingBlocks,
|
toggleShowThinkingBlocks,
|
||||||
|
toggleKeyboardShortcutHints,
|
||||||
toggleShowTimelineTools,
|
toggleShowTimelineTools,
|
||||||
toggleAutoCleanupBlankSessions,
|
toggleAutoCleanupBlankSessions,
|
||||||
toggleUsageMetrics,
|
toggleUsageMetrics,
|
||||||
@@ -80,6 +81,13 @@ const App: Component = () => {
|
|||||||
const [remoteAccessOpen, setRemoteAccessOpen] = createSignal(false)
|
const [remoteAccessOpen, setRemoteAccessOpen] = createSignal(false)
|
||||||
const [instanceTabBarHeight, setInstanceTabBarHeight] = createSignal(0)
|
const [instanceTabBarHeight, setInstanceTabBarHeight] = createSignal(0)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (typeof document === "undefined") return
|
||||||
|
const shouldShow =
|
||||||
|
runtimeEnv.host !== "web" && runtimeEnv.platform !== "mobile" && (preferences().showKeyboardShortcutHints ?? true)
|
||||||
|
document.documentElement.dataset.keyboardHints = shouldShow ? "show" : "hide"
|
||||||
|
})
|
||||||
|
|
||||||
const updateInstanceTabBarHeight = () => {
|
const updateInstanceTabBarHeight = () => {
|
||||||
if (typeof document === "undefined") return
|
if (typeof document === "undefined") return
|
||||||
const element = document.querySelector<HTMLElement>(".tab-bar-instance")
|
const element = document.querySelector<HTMLElement>(".tab-bar-instance")
|
||||||
@@ -293,6 +301,7 @@ const App: Component = () => {
|
|||||||
preferences,
|
preferences,
|
||||||
toggleAutoCleanupBlankSessions,
|
toggleAutoCleanupBlankSessions,
|
||||||
toggleShowThinkingBlocks,
|
toggleShowThinkingBlocks,
|
||||||
|
toggleKeyboardShortcutHints,
|
||||||
toggleShowTimelineTools,
|
toggleShowTimelineTools,
|
||||||
toggleUsageMetrics,
|
toggleUsageMetrics,
|
||||||
togglePromptSubmitOnEnter,
|
togglePromptSubmitOnEnter,
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ const CommandPalette: Component<CommandPaletteProps> = (props) => {
|
|||||||
|
|
||||||
const groupedCommandList = () => processedCommands().groups
|
const groupedCommandList = () => processedCommands().groups
|
||||||
const orderedCommands = () => processedCommands().ordered
|
const orderedCommands = () => processedCommands().ordered
|
||||||
|
|
||||||
|
const isCommandDisabled = (command: Command) => {
|
||||||
|
return command.disabled ? Boolean(resolveResolvable(command.disabled)) : false
|
||||||
|
}
|
||||||
const selectedIndex = createMemo(() => {
|
const selectedIndex = createMemo(() => {
|
||||||
const ordered = orderedCommands()
|
const ordered = orderedCommands()
|
||||||
if (ordered.length === 0) return -1
|
if (ordered.length === 0) return -1
|
||||||
@@ -138,10 +142,11 @@ const CommandPalette: Component<CommandPaletteProps> = (props) => {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentId = selectedCommandId()
|
const currentId = selectedCommandId()
|
||||||
if (!currentId || !ordered.some((cmd) => cmd.id === currentId)) {
|
if (!currentId || !ordered.some((cmd) => cmd.id === currentId)) {
|
||||||
setSelectedCommandId(ordered[0].id)
|
const firstEnabled = ordered.find((cmd) => !isCommandDisabled(cmd))
|
||||||
|
setSelectedCommandId((firstEnabled || ordered[0])?.id ?? null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -195,12 +200,14 @@ const CommandPalette: Component<CommandPaletteProps> = (props) => {
|
|||||||
if (index < 0 || index >= ordered.length) return
|
if (index < 0 || index >= ordered.length) return
|
||||||
const command = ordered[index]
|
const command = ordered[index]
|
||||||
if (!command) return
|
if (!command) return
|
||||||
|
if (isCommandDisabled(command)) return
|
||||||
props.onExecute(command)
|
props.onExecute(command)
|
||||||
props.onClose()
|
props.onClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommandClick(command: Command) {
|
function handleCommandClick(command: Command) {
|
||||||
|
if (isCommandDisabled(command)) return
|
||||||
props.onExecute(command)
|
props.onExecute(command)
|
||||||
props.onClose()
|
props.onClose()
|
||||||
}
|
}
|
||||||
@@ -265,11 +272,13 @@ const CommandPalette: Component<CommandPaletteProps> = (props) => {
|
|||||||
<For each={group.commands}>
|
<For each={group.commands}>
|
||||||
{(command, localIndex) => {
|
{(command, localIndex) => {
|
||||||
const commandIndex = group.startIndex + localIndex()
|
const commandIndex = group.startIndex + localIndex()
|
||||||
|
const disabled = isCommandDisabled(command)
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-command-index={commandIndex}
|
data-command-index={commandIndex}
|
||||||
onClick={() => handleCommandClick(command)}
|
onClick={() => handleCommandClick(command)}
|
||||||
|
disabled={disabled}
|
||||||
class={`modal-item ${selectedCommandId() === command.id ? "modal-item-highlight" : ""}`}
|
class={`modal-item ${selectedCommandId() === command.id ? "modal-item-highlight" : ""}`}
|
||||||
onPointerMove={(event) => {
|
onPointerMove={(event) => {
|
||||||
if (event.movementX === 0 && event.movementY === 0) return
|
if (event.movementX === 0 && event.movementY === 0) return
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ const FileSystemBrowserDialog: Component<FileSystemBrowserDialogProps> = (props)
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-footer">
|
<div class="panel-footer keyboard-hints">
|
||||||
<div class="panel-footer-hints">
|
<div class="panel-footer-hints">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<kbd class="kbd">↑</kbd>
|
<kbd class="kbd">↑</kbd>
|
||||||
|
|||||||
@@ -548,7 +548,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||||||
: t("folderSelection.browse.button")}
|
: t("folderSelection.browse.button")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Kbd shortcut="cmd+n" class="ml-2" />
|
<Kbd shortcut="cmd+n" class="ml-2 kbd-hint" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -573,7 +573,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel panel-footer shrink-0 hidden sm:block">
|
<div class="panel panel-footer shrink-0 hidden sm:block keyboard-hints">
|
||||||
<div class="panel-footer-hints">
|
<div class="panel-footer-hints">
|
||||||
<Show when={folders().length > 0}>
|
<Show when={folders().length > 0}>
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
@@ -591,7 +591,7 @@ const FolderSelectionView: Component<FolderSelectionViewProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<Kbd shortcut="cmd+n" />
|
<Kbd shortcut="cmd+n" class="kbd-hint" />
|
||||||
<span>{t("folderSelection.hints.browse")}</span>
|
<span>{t("folderSelection.hints.browse")}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface HintRowProps {
|
|||||||
|
|
||||||
const HintRow: Component<HintRowProps> = (props) => {
|
const HintRow: Component<HintRowProps> = (props) => {
|
||||||
return (
|
return (
|
||||||
<span aria-hidden={props.ariaHidden} class={`text-xs text-muted ${props.class || ""}`}>
|
<span aria-hidden={props.ariaHidden} class={`keyboard-hints text-xs text-muted ${props.class || ""}`}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -502,7 +502,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
<span>{t("instanceWelcome.new.createButton")}</span>
|
<span>{t("instanceWelcome.new.createButton")}</span>
|
||||||
</div>
|
</div>
|
||||||
<Kbd shortcut={newSessionShortcutString()} class="ml-2" />
|
<Kbd shortcut={newSessionShortcutString()} class="ml-2 kbd-hint" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -539,7 +539,7 @@ const InstanceWelcomeView: Component<InstanceWelcomeViewProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div class="panel-footer hidden sm:block">
|
<div class="panel-footer hidden sm:block keyboard-hints">
|
||||||
|
|
||||||
<div class="panel-footer-hints">
|
<div class="panel-footer-hints">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
|
|||||||
@@ -633,7 +633,7 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||||||
>
|
>
|
||||||
{t("instanceShell.commandPalette.button")}
|
{t("instanceShell.commandPalette.button")}
|
||||||
</button>
|
</button>
|
||||||
<span class="connection-status-shortcut-hint">
|
<span class="connection-status-shortcut-hint kbd-hint">
|
||||||
<Kbd shortcut="cmd+shift+p" />
|
<Kbd shortcut="cmd+shift+p" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -730,7 +730,7 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="session-toolbar-right flex-1 flex items-center gap-3">
|
<div class="session-toolbar-right flex-1 flex items-center gap-3">
|
||||||
<span class="connection-status-shortcut-hint">
|
<span class="connection-status-shortcut-hint kbd-hint">
|
||||||
<Kbd shortcut="cmd+shift+p" />
|
<Kbd shortcut="cmd+shift+p" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export default function MessageListHeader(props: MessageListHeaderProps) {
|
|||||||
{t("messageListHeader.commandPalette.button")}
|
{t("messageListHeader.commandPalette.button")}
|
||||||
</button>
|
</button>
|
||||||
<span class="connection-status-shortcut-hint">
|
<span class="connection-status-shortcut-hint">
|
||||||
<Kbd shortcut="cmd+shift+p" />
|
<Kbd shortcut="cmd+shift+p" class="kbd-hint" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -867,7 +867,7 @@ export default function MessageSection(props: MessageSectionProps) {
|
|||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<span>{t("messageSection.empty.tips.commandPalette")}</span>
|
<span>{t("messageSection.empty.tips.commandPalette")}</span>
|
||||||
<Kbd shortcut="cmd+shift+p" class="ml-2" />
|
<Kbd shortcut="cmd+shift+p" class="ml-2 kbd-hint" />
|
||||||
</li>
|
</li>
|
||||||
<li>{t("messageSection.empty.tips.askAboutCodebase")}</li>
|
<li>{t("messageSection.empty.tips.askAboutCodebase")}</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -480,7 +480,7 @@ export default function PromptInput(props: PromptInputProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={shouldShowOverlay()}>
|
<Show when={shouldShowOverlay()}>
|
||||||
<div class={`prompt-input-overlay ${mode() === "shell" ? "shell-mode" : ""}`}>
|
<div class={`prompt-input-overlay keyboard-hints ${mode() === "shell" ? "shell-mode" : ""}`}>
|
||||||
<Show
|
<Show
|
||||||
when={props.escapeInDebounce}
|
when={props.escapeInDebounce}
|
||||||
fallback={
|
fallback={
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ const SessionPicker: Component<SessionPickerProps> = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<kbd class="kbd ml-2">
|
<kbd class="kbd ml-2 kbd-hint">
|
||||||
Cmd+Enter
|
Cmd+Enter
|
||||||
</kbd>
|
</kbd>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export interface Command {
|
|||||||
description: Resolvable<string>
|
description: Resolvable<string>
|
||||||
keywords?: Resolvable<string[]>
|
keywords?: Resolvable<string[]>
|
||||||
shortcut?: KeyboardShortcut
|
shortcut?: KeyboardShortcut
|
||||||
|
disabled?: Resolvable<boolean>
|
||||||
action: () => void | Promise<void>
|
action: () => void | Promise<void>
|
||||||
category?: Resolvable<string>
|
category?: Resolvable<string>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { getLogger } from "../logger"
|
|||||||
import { requestData } from "../opencode-api"
|
import { requestData } from "../opencode-api"
|
||||||
import { emitSessionSidebarRequest } from "../session-sidebar-events"
|
import { emitSessionSidebarRequest } from "../session-sidebar-events"
|
||||||
import { tGlobal } from "../i18n"
|
import { tGlobal } from "../i18n"
|
||||||
|
import { runtimeEnv } from "../runtime-env"
|
||||||
|
|
||||||
const log = getLogger("actions")
|
const log = getLogger("actions")
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ function splitKeywords(key: string): string[] {
|
|||||||
export interface UseCommandsOptions {
|
export interface UseCommandsOptions {
|
||||||
preferences: Accessor<Preferences>
|
preferences: Accessor<Preferences>
|
||||||
toggleShowThinkingBlocks: () => void
|
toggleShowThinkingBlocks: () => void
|
||||||
|
toggleKeyboardShortcutHints: () => void
|
||||||
toggleShowTimelineTools: () => void
|
toggleShowTimelineTools: () => void
|
||||||
toggleUsageMetrics: () => void
|
toggleUsageMetrics: () => void
|
||||||
toggleAutoCleanupBlankSessions: () => void
|
toggleAutoCleanupBlankSessions: () => void
|
||||||
@@ -454,6 +456,26 @@ export function useCommands(options: UseCommandsOptions) {
|
|||||||
action: options.toggleShowTimelineTools,
|
action: options.toggleShowTimelineTools,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
commandRegistry.register({
|
||||||
|
id: "keyboard-shortcut-hints",
|
||||||
|
label: () =>
|
||||||
|
tGlobal(
|
||||||
|
options.preferences().showKeyboardShortcutHints
|
||||||
|
? "commands.keyboardShortcutHints.label.hide"
|
||||||
|
: "commands.keyboardShortcutHints.label.show",
|
||||||
|
),
|
||||||
|
description: () =>
|
||||||
|
tGlobal(
|
||||||
|
runtimeEnv.host === "web"
|
||||||
|
? "commands.keyboardShortcutHints.description.disabledWeb"
|
||||||
|
: "commands.keyboardShortcutHints.description",
|
||||||
|
),
|
||||||
|
category: "System",
|
||||||
|
keywords: () => splitKeywords("commands.keyboardShortcutHints.keywords"),
|
||||||
|
disabled: () => runtimeEnv.host === "web",
|
||||||
|
action: options.toggleKeyboardShortcutHints,
|
||||||
|
})
|
||||||
|
|
||||||
commandRegistry.register({
|
commandRegistry.register({
|
||||||
id: "thinking-default-visibility",
|
id: "thinking-default-visibility",
|
||||||
label: () => {
|
label: () => {
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ export const commandMessages = {
|
|||||||
"commands.timelineToolCalls.description": "Toggle tool call entries in the message timeline",
|
"commands.timelineToolCalls.description": "Toggle tool call entries in the message timeline",
|
||||||
"commands.timelineToolCalls.keywords": "timeline, tool, toggle",
|
"commands.timelineToolCalls.keywords": "timeline, tool, toggle",
|
||||||
|
|
||||||
|
"commands.keyboardShortcutHints.label.show": "Show Keyboard Shortcut Hints",
|
||||||
|
"commands.keyboardShortcutHints.label.hide": "Hide Keyboard Shortcut Hints",
|
||||||
|
"commands.keyboardShortcutHints.description": "Show or hide keyboard shortcut hints across the UI",
|
||||||
|
"commands.keyboardShortcutHints.description.disabledWeb": "Disabled in WebUI (shortcut hints are always hidden)",
|
||||||
|
"commands.keyboardShortcutHints.keywords": "shortcut, shortcuts, keyboard, keybind, hints",
|
||||||
|
|
||||||
"commands.common.expanded": "Expanded",
|
"commands.common.expanded": "Expanded",
|
||||||
"commands.common.collapsed": "Collapsed",
|
"commands.common.collapsed": "Collapsed",
|
||||||
"commands.common.visible": "Visible",
|
"commands.common.visible": "Visible",
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ export const commandMessages = {
|
|||||||
"commands.timelineToolCalls.description": "Alternar entradas de llamadas de herramienta en la línea de tiempo de mensajes",
|
"commands.timelineToolCalls.description": "Alternar entradas de llamadas de herramienta en la línea de tiempo de mensajes",
|
||||||
"commands.timelineToolCalls.keywords": "línea de tiempo, herramienta, alternar",
|
"commands.timelineToolCalls.keywords": "línea de tiempo, herramienta, alternar",
|
||||||
|
|
||||||
|
"commands.keyboardShortcutHints.label.show": "Mostrar atajos de teclado",
|
||||||
|
"commands.keyboardShortcutHints.label.hide": "Ocultar atajos de teclado",
|
||||||
|
"commands.keyboardShortcutHints.description": "Mostrar u ocultar sugerencias de atajos de teclado en la interfaz",
|
||||||
|
"commands.keyboardShortcutHints.description.disabledWeb": "Desactivado en WebUI (los atajos siempre se ocultan)",
|
||||||
|
"commands.keyboardShortcutHints.keywords": "atajo, atajos, teclado, keybind, pistas",
|
||||||
|
|
||||||
"commands.common.expanded": "Expandido",
|
"commands.common.expanded": "Expandido",
|
||||||
"commands.common.collapsed": "Colapsado",
|
"commands.common.collapsed": "Colapsado",
|
||||||
"commands.common.visible": "Visible",
|
"commands.common.visible": "Visible",
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ export const commandMessages = {
|
|||||||
"commands.timelineToolCalls.description": "Afficher/masquer les entrées d'appel d'outil dans la timeline des messages",
|
"commands.timelineToolCalls.description": "Afficher/masquer les entrées d'appel d'outil dans la timeline des messages",
|
||||||
"commands.timelineToolCalls.keywords": "timeline, outil, basculer",
|
"commands.timelineToolCalls.keywords": "timeline, outil, basculer",
|
||||||
|
|
||||||
|
"commands.keyboardShortcutHints.label.show": "Afficher les raccourcis clavier",
|
||||||
|
"commands.keyboardShortcutHints.label.hide": "Masquer les raccourcis clavier",
|
||||||
|
"commands.keyboardShortcutHints.description": "Afficher ou masquer les indices de raccourcis clavier dans l'interface",
|
||||||
|
"commands.keyboardShortcutHints.description.disabledWeb": "Désactivé en WebUI (les raccourcis sont toujours masqués)",
|
||||||
|
"commands.keyboardShortcutHints.keywords": "raccourci, raccourcis, clavier, keybind, indices",
|
||||||
|
|
||||||
"commands.common.expanded": "Développé",
|
"commands.common.expanded": "Développé",
|
||||||
"commands.common.collapsed": "Réduit",
|
"commands.common.collapsed": "Réduit",
|
||||||
"commands.common.visible": "Visible",
|
"commands.common.visible": "Visible",
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ export const commandMessages = {
|
|||||||
"commands.timelineToolCalls.description": "メッセージタイムラインのツールコール表示を切り替え",
|
"commands.timelineToolCalls.description": "メッセージタイムラインのツールコール表示を切り替え",
|
||||||
"commands.timelineToolCalls.keywords": "タイムライン, ツール, 切り替え, timeline, tool, toggle",
|
"commands.timelineToolCalls.keywords": "タイムライン, ツール, 切り替え, timeline, tool, toggle",
|
||||||
|
|
||||||
|
"commands.keyboardShortcutHints.label.show": "キーボードショートカットのヒントを表示",
|
||||||
|
"commands.keyboardShortcutHints.label.hide": "キーボードショートカットのヒントを非表示",
|
||||||
|
"commands.keyboardShortcutHints.description": "UI 全体のキーボードショートカットヒントを表示/非表示",
|
||||||
|
"commands.keyboardShortcutHints.description.disabledWeb": "WebUI では無効(ヒントは常に非表示)",
|
||||||
|
"commands.keyboardShortcutHints.keywords": "ショートカット, キーボード, ヒント, shortcuts, keyboard, hints",
|
||||||
|
|
||||||
"commands.common.expanded": "展開",
|
"commands.common.expanded": "展開",
|
||||||
"commands.common.collapsed": "折りたたみ",
|
"commands.common.collapsed": "折りたたみ",
|
||||||
"commands.common.visible": "表示",
|
"commands.common.visible": "表示",
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ export const commandMessages = {
|
|||||||
"commands.timelineToolCalls.description": "Переключить отображение вызовов инструментов в таймлайне сообщений",
|
"commands.timelineToolCalls.description": "Переключить отображение вызовов инструментов в таймлайне сообщений",
|
||||||
"commands.timelineToolCalls.keywords": "таймлайн, tool, переключить",
|
"commands.timelineToolCalls.keywords": "таймлайн, tool, переключить",
|
||||||
|
|
||||||
|
"commands.keyboardShortcutHints.label.show": "Показать подсказки сочетаний",
|
||||||
|
"commands.keyboardShortcutHints.label.hide": "Скрыть подсказки сочетаний",
|
||||||
|
"commands.keyboardShortcutHints.description": "Показать или скрыть подсказки сочетаний клавиш в интерфейсе",
|
||||||
|
"commands.keyboardShortcutHints.description.disabledWeb": "Отключено в WebUI (подсказки всегда скрыты)",
|
||||||
|
"commands.keyboardShortcutHints.keywords": "shortcut, shortcuts, keyboard, keybind, подсказки",
|
||||||
|
|
||||||
"commands.common.expanded": "Развернуто",
|
"commands.common.expanded": "Развернуто",
|
||||||
"commands.common.collapsed": "Свернуто",
|
"commands.common.collapsed": "Свернуто",
|
||||||
"commands.common.visible": "Видимо",
|
"commands.common.visible": "Видимо",
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ export const commandMessages = {
|
|||||||
"commands.timelineToolCalls.description": "切换消息时间轴中的工具调用条目",
|
"commands.timelineToolCalls.description": "切换消息时间轴中的工具调用条目",
|
||||||
"commands.timelineToolCalls.keywords": "timeline, tool, toggle, 时间轴, 工具, 切换",
|
"commands.timelineToolCalls.keywords": "timeline, tool, toggle, 时间轴, 工具, 切换",
|
||||||
|
|
||||||
|
"commands.keyboardShortcutHints.label.show": "显示键盘快捷键提示",
|
||||||
|
"commands.keyboardShortcutHints.label.hide": "隐藏键盘快捷键提示",
|
||||||
|
"commands.keyboardShortcutHints.description": "显示或隐藏界面中的键盘快捷键提示",
|
||||||
|
"commands.keyboardShortcutHints.description.disabledWeb": "WebUI 中已禁用(提示始终隐藏)",
|
||||||
|
"commands.keyboardShortcutHints.keywords": "shortcuts, keyboard, hints, 快捷键, 键盘, 提示",
|
||||||
|
|
||||||
"commands.common.expanded": "展开",
|
"commands.common.expanded": "展开",
|
||||||
"commands.common.collapsed": "折叠",
|
"commands.common.collapsed": "折叠",
|
||||||
"commands.common.visible": "可见",
|
"commands.common.visible": "可见",
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type ListeningMode = "local" | "all"
|
|||||||
|
|
||||||
export interface Preferences {
|
export interface Preferences {
|
||||||
showThinkingBlocks: boolean
|
showThinkingBlocks: boolean
|
||||||
|
showKeyboardShortcutHints: boolean
|
||||||
thinkingBlocksExpansion: ExpansionPreference
|
thinkingBlocksExpansion: ExpansionPreference
|
||||||
showTimelineTools: boolean
|
showTimelineTools: boolean
|
||||||
promptSubmitOnEnter: boolean
|
promptSubmitOnEnter: boolean
|
||||||
@@ -78,6 +79,7 @@ const MAX_FAVORITE_MODELS = 50
|
|||||||
|
|
||||||
const defaultPreferences: Preferences = {
|
const defaultPreferences: Preferences = {
|
||||||
showThinkingBlocks: false,
|
showThinkingBlocks: false,
|
||||||
|
showKeyboardShortcutHints: true,
|
||||||
thinkingBlocksExpansion: "expanded",
|
thinkingBlocksExpansion: "expanded",
|
||||||
showTimelineTools: true,
|
showTimelineTools: true,
|
||||||
promptSubmitOnEnter: false,
|
promptSubmitOnEnter: false,
|
||||||
@@ -131,6 +133,7 @@ function normalizePreferences(pref?: Partial<Preferences> & { agentModelSelectio
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultPreferences.showThinkingBlocks,
|
showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultPreferences.showThinkingBlocks,
|
||||||
|
showKeyboardShortcutHints: sanitized.showKeyboardShortcutHints ?? defaultPreferences.showKeyboardShortcutHints,
|
||||||
thinkingBlocksExpansion: sanitized.thinkingBlocksExpansion ?? defaultPreferences.thinkingBlocksExpansion,
|
thinkingBlocksExpansion: sanitized.thinkingBlocksExpansion ?? defaultPreferences.thinkingBlocksExpansion,
|
||||||
showTimelineTools: sanitized.showTimelineTools ?? defaultPreferences.showTimelineTools,
|
showTimelineTools: sanitized.showTimelineTools ?? defaultPreferences.showTimelineTools,
|
||||||
promptSubmitOnEnter: sanitized.promptSubmitOnEnter ?? defaultPreferences.promptSubmitOnEnter,
|
promptSubmitOnEnter: sanitized.promptSubmitOnEnter ?? defaultPreferences.promptSubmitOnEnter,
|
||||||
@@ -393,6 +396,10 @@ function toggleShowThinkingBlocks(): void {
|
|||||||
updatePreferences({ showThinkingBlocks: !preferences().showThinkingBlocks })
|
updatePreferences({ showThinkingBlocks: !preferences().showThinkingBlocks })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleKeyboardShortcutHints(): void {
|
||||||
|
updatePreferences({ showKeyboardShortcutHints: !preferences().showKeyboardShortcutHints })
|
||||||
|
}
|
||||||
|
|
||||||
function toggleShowTimelineTools(): void {
|
function toggleShowTimelineTools(): void {
|
||||||
updatePreferences({ showTimelineTools: !preferences().showTimelineTools })
|
updatePreferences({ showTimelineTools: !preferences().showTimelineTools })
|
||||||
}
|
}
|
||||||
@@ -511,6 +518,7 @@ interface ConfigContextValue {
|
|||||||
setThemePreference: typeof setThemePreference
|
setThemePreference: typeof setThemePreference
|
||||||
updateConfig: typeof updateConfig
|
updateConfig: typeof updateConfig
|
||||||
toggleShowThinkingBlocks: typeof toggleShowThinkingBlocks
|
toggleShowThinkingBlocks: typeof toggleShowThinkingBlocks
|
||||||
|
toggleKeyboardShortcutHints: typeof toggleKeyboardShortcutHints
|
||||||
toggleShowTimelineTools: typeof toggleShowTimelineTools
|
toggleShowTimelineTools: typeof toggleShowTimelineTools
|
||||||
toggleUsageMetrics: typeof toggleUsageMetrics
|
toggleUsageMetrics: typeof toggleUsageMetrics
|
||||||
toggleAutoCleanupBlankSessions: typeof toggleAutoCleanupBlankSessions
|
toggleAutoCleanupBlankSessions: typeof toggleAutoCleanupBlankSessions
|
||||||
@@ -548,6 +556,7 @@ const configContextValue: ConfigContextValue = {
|
|||||||
setThemePreference,
|
setThemePreference,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
toggleShowThinkingBlocks,
|
toggleShowThinkingBlocks,
|
||||||
|
toggleKeyboardShortcutHints,
|
||||||
toggleShowTimelineTools,
|
toggleShowTimelineTools,
|
||||||
toggleUsageMetrics,
|
toggleUsageMetrics,
|
||||||
toggleAutoCleanupBlankSessions,
|
toggleAutoCleanupBlankSessions,
|
||||||
@@ -608,6 +617,7 @@ export {
|
|||||||
updateConfig,
|
updateConfig,
|
||||||
updatePreferences,
|
updatePreferences,
|
||||||
toggleShowThinkingBlocks,
|
toggleShowThinkingBlocks,
|
||||||
|
toggleKeyboardShortcutHints,
|
||||||
toggleShowTimelineTools,
|
toggleShowTimelineTools,
|
||||||
toggleAutoCleanupBlankSessions,
|
toggleAutoCleanupBlankSessions,
|
||||||
toggleUsageMetrics,
|
toggleUsageMetrics,
|
||||||
|
|||||||
@@ -46,6 +46,11 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-item:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-list-container[data-pointer-mode="pointer"] .modal-item:hover {
|
.modal-list-container[data-pointer-mode="pointer"] .modal-item:hover {
|
||||||
background-color: var(--surface-hover);
|
background-color: var(--surface-hover);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,19 @@
|
|||||||
@apply opacity-50;
|
@apply opacity-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shortcut hints are useful on desktop native apps, but are noisy/irrelevant on
|
||||||
|
touch-first devices and in WebUI where browser shortcuts often conflict.
|
||||||
|
*/
|
||||||
|
html[data-runtime-host="web"] .keyboard-hints,
|
||||||
|
html[data-runtime-host="web"] .kbd-hint,
|
||||||
|
html[data-runtime-platform="mobile"] .keyboard-hints,
|
||||||
|
html[data-runtime-platform="mobile"] .kbd-hint,
|
||||||
|
html[data-keyboard-hints="hide"] .keyboard-hints,
|
||||||
|
html[data-keyboard-hints="hide"] .kbd-hint {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Truncate from the start (keeps end visible; good for paths) */
|
/* Truncate from the start (keeps end visible; good for paths) */
|
||||||
.truncate-start {
|
.truncate-start {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Reference in New Issue
Block a user