@agentprojectcontext/apx 1.44.0 → 1.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/interfaces/web/dist/assets/{index-BAKk7d_M.css → index-CilEtMjV.css} +1 -1
- package/src/interfaces/web/dist/assets/{index-Cjj_d3SA.js → index-oOjZZktw.js} +120 -120
- package/src/interfaces/web/dist/assets/index-oOjZZktw.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/App.tsx +3 -6
- package/src/interfaces/web/src/components/desktop/DesktopStatusCard.tsx +116 -0
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +7 -10
- package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +185 -0
- package/src/interfaces/web/src/i18n/en.ts +2 -0
- package/src/interfaces/web/src/i18n/es.ts +2 -0
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +21 -3
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +16 -266
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
- package/src/interfaces/web/dist/assets/index-Cjj_d3SA.js.map +0 -1
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
|
|
19
19
|
<link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
|
|
20
20
|
<link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
|
|
21
|
-
<script type="module" crossorigin src="/assets/index-
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-oOjZZktw.js"></script>
|
|
22
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CilEtMjV.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-background text-foreground antialiased">
|
|
25
25
|
<div id="root"></div>
|
|
@@ -5,9 +5,7 @@ import { ProjectSidebar, projectKindLabel } from "./components/layout/ProjectSid
|
|
|
5
5
|
import { ApxAdminScreen } from "./screens/ApxAdminScreen";
|
|
6
6
|
import { ProjectScreen } from "./screens/ProjectScreen";
|
|
7
7
|
import { SettingsScreen } from "./screens/SettingsScreen";
|
|
8
|
-
import { VoiceScreen } from "./screens/modules/VoiceScreen";
|
|
9
8
|
import { DesktopScreen } from "./screens/modules/DesktopScreen";
|
|
10
|
-
import { DeckScreen } from "./screens/modules/DeckScreen";
|
|
11
9
|
import { CodeScreen } from "./screens/modules/CodeScreen";
|
|
12
10
|
import { AddProjectDialog } from "./components/AddProjectDialog";
|
|
13
11
|
import { PairingScreen } from "./screens/PairingScreen";
|
|
@@ -92,9 +90,7 @@ function Shell() {
|
|
|
92
90
|
<Routes>
|
|
93
91
|
<Route path="/" element={<ApxAdminScreen />} />
|
|
94
92
|
<Route path="/settings/*" element={<SettingsScreen />} />
|
|
95
|
-
<Route path="/m/voice/*" element={<VoiceScreen />} />
|
|
96
93
|
<Route path="/m/desktop/*" element={<DesktopScreen />} />
|
|
97
|
-
<Route path="/m/deck/*" element={<DeckScreen />} />
|
|
98
94
|
<Route path="/m/code/*" element={<CodeScreen />} />
|
|
99
95
|
<Route path="/p/:pid/*" element={<ProjectScreen />} />
|
|
100
96
|
<Route path="*" element={<NotFound />} />
|
|
@@ -208,9 +204,7 @@ function LanguageMenu() {
|
|
|
208
204
|
|
|
209
205
|
function moduleLabel(key?: string) {
|
|
210
206
|
switch (key) {
|
|
211
|
-
case "voice": return t("nav.modules.voice");
|
|
212
207
|
case "desktop": return t("nav.modules.desktop");
|
|
213
|
-
case "deck": return t("nav.modules.deck");
|
|
214
208
|
case "code": return t("nav.modules.code");
|
|
215
209
|
default: return key || "";
|
|
216
210
|
}
|
|
@@ -222,6 +216,9 @@ function settingsLabel(key?: string) {
|
|
|
222
216
|
case "engines": return t("settings.tabs.engines");
|
|
223
217
|
case "telegram": return t("settings.tabs.telegram");
|
|
224
218
|
case "devices": return t("settings.tabs.devices");
|
|
219
|
+
case "voice": return t("nav.modules.voice");
|
|
220
|
+
case "deck": return t("nav.modules.deck");
|
|
221
|
+
case "desktop": return t("nav.modules.desktop");
|
|
225
222
|
case "appearance": return t("settings.appearance");
|
|
226
223
|
case "config":
|
|
227
224
|
case "advanced": return t("settings.tabs.advanced");
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
|
+
import useSWR from "swr";
|
|
4
|
+
import { Settings } from "lucide-react";
|
|
5
|
+
import { Section, Kbd, StatusDot } from "../Section";
|
|
6
|
+
import { Button, Loading } from "../ui";
|
|
7
|
+
import { useToast } from "../Toast";
|
|
8
|
+
import { Desktop } from "../../lib/api/desktop";
|
|
9
|
+
import { t } from "../../i18n";
|
|
10
|
+
|
|
11
|
+
// Live status of the floating Desktop window + lifecycle controls
|
|
12
|
+
// (start/stop/restart). Shared between the Desktop rail module and the
|
|
13
|
+
// Settings → Desktop panel so both surfaces keep the same action card.
|
|
14
|
+
// Pass `showConfigLink` on the rail module to link into the settings panel;
|
|
15
|
+
// inside Settings it's redundant, so it's omitted there.
|
|
16
|
+
export function DesktopStatusCard({ showConfigLink = false }: { showConfigLink?: boolean }) {
|
|
17
|
+
const toast = useToast();
|
|
18
|
+
|
|
19
|
+
const { data: status, isLoading: stLoading, mutate: mutateStatus } = useSWR(
|
|
20
|
+
"/desktop/status",
|
|
21
|
+
() => Desktop.status(),
|
|
22
|
+
{ refreshInterval: 5000 },
|
|
23
|
+
);
|
|
24
|
+
const running = !!status?.running;
|
|
25
|
+
|
|
26
|
+
// Which lifecycle action (start/stop/restart) is in flight — drives the
|
|
27
|
+
// per-button spinner and disables its siblings while one runs.
|
|
28
|
+
const [lifeAction, setLifeAction] = useState<"start" | "stop" | "restart" | null>(null);
|
|
29
|
+
|
|
30
|
+
// Start/Stop launch or kill the Electron window (daemon spawns/SIGTERMs it);
|
|
31
|
+
// Restart tells a live window to reload + re-read config (theme, position,
|
|
32
|
+
// shortcut) — the "apply now" the static status-poll never did. All three
|
|
33
|
+
// re-poll status shortly after so the dot + buttons settle.
|
|
34
|
+
const runLifecycle = async (action: "start" | "stop" | "restart", fn: () => Promise<void>) => {
|
|
35
|
+
setLifeAction(action);
|
|
36
|
+
try { await fn(); }
|
|
37
|
+
catch (e) { toast.error((e as Error).message); }
|
|
38
|
+
finally { setLifeAction(null); setTimeout(() => mutateStatus(), 1200); }
|
|
39
|
+
};
|
|
40
|
+
const startDesktop = () => runLifecycle("start", async () => {
|
|
41
|
+
const r = await Desktop.start();
|
|
42
|
+
toast.success(r.already ? t("modules_ui.desktop_start_already") : t("modules_ui.desktop_start_done"));
|
|
43
|
+
});
|
|
44
|
+
const stopDesktop = () => runLifecycle("stop", async () => {
|
|
45
|
+
const r = await Desktop.stop();
|
|
46
|
+
toast.success(r.stopped ? t("modules_ui.desktop_stop_done") : t("modules_ui.desktop_stop_none"));
|
|
47
|
+
});
|
|
48
|
+
const restartDesktop = () => runLifecycle("restart", async () => {
|
|
49
|
+
const r = await Desktop.restart();
|
|
50
|
+
if (r.reloaded > 0) toast.success(t("modules_ui.desktop_restart_done"));
|
|
51
|
+
else toast.info(t("modules_ui.desktop_restart_none"));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Section
|
|
56
|
+
title={t("desktop_screen.status_title")}
|
|
57
|
+
description={t("modules_ui.desktop_status_desc")}
|
|
58
|
+
action={
|
|
59
|
+
<div className="flex items-center gap-2">
|
|
60
|
+
<Button
|
|
61
|
+
variant="primary"
|
|
62
|
+
size="sm"
|
|
63
|
+
onClick={startDesktop}
|
|
64
|
+
loading={lifeAction === "start"}
|
|
65
|
+
disabled={running || (lifeAction !== null && lifeAction !== "start")}
|
|
66
|
+
>
|
|
67
|
+
{t("modules_ui.desktop_start")}
|
|
68
|
+
</Button>
|
|
69
|
+
<Button
|
|
70
|
+
variant="secondary"
|
|
71
|
+
size="sm"
|
|
72
|
+
onClick={stopDesktop}
|
|
73
|
+
loading={lifeAction === "stop"}
|
|
74
|
+
disabled={!running || (lifeAction !== null && lifeAction !== "stop")}
|
|
75
|
+
>
|
|
76
|
+
{t("modules_ui.desktop_stop")}
|
|
77
|
+
</Button>
|
|
78
|
+
<Button
|
|
79
|
+
variant="secondary"
|
|
80
|
+
size="sm"
|
|
81
|
+
onClick={restartDesktop}
|
|
82
|
+
loading={lifeAction === "restart"}
|
|
83
|
+
disabled={!running || (lifeAction !== null && lifeAction !== "restart")}
|
|
84
|
+
title={t("modules_ui.desktop_restart_hint")}
|
|
85
|
+
>
|
|
86
|
+
{t("modules_ui.desktop_restart")}
|
|
87
|
+
</Button>
|
|
88
|
+
{showConfigLink && (
|
|
89
|
+
<Link to="/settings/desktop">
|
|
90
|
+
<Button size="sm" variant="ghost">
|
|
91
|
+
<Settings size={14} /> {t("desktop_screen.open_config")}
|
|
92
|
+
</Button>
|
|
93
|
+
</Link>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
}
|
|
97
|
+
>
|
|
98
|
+
{stLoading ? <Loading /> : (
|
|
99
|
+
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm">
|
|
100
|
+
<StatusDot ok={running} />
|
|
101
|
+
<span className="font-medium">{running ? t("modules_ui.desktop_running") : t("modules_ui.desktop_stopped")}</span>
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
onClick={() => mutateStatus()}
|
|
105
|
+
className="text-xs text-muted-fg underline-offset-2 hover:underline"
|
|
106
|
+
>
|
|
107
|
+
{t("modules_ui.desktop_refresh")}
|
|
108
|
+
</button>
|
|
109
|
+
<span className="text-xs text-muted-fg">
|
|
110
|
+
({t("modules_ui.desktop_from_terminal")} <Kbd>apx desktop start</Kbd> · <Kbd>apx desktop --debug</Kbd>)
|
|
111
|
+
</span>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
</Section>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Discord-style left rail. Logo on top (APX admin), then Base
|
|
2
|
-
// rail-level MODULES (
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Discord-style left rail. Logo on top (APX admin), then Base together with the
|
|
2
|
+
// rail-level MODULES (Desktop/Code) as one group, then the projects column,
|
|
3
|
+
// finally add + settings. The default workspace (id=0) is pinned first.
|
|
4
|
+
// Voice and Deck used to live here too — they now live inside Settings.
|
|
5
5
|
//
|
|
6
6
|
// The projects column is the only flexible zone: top (logo/base/modules) and
|
|
7
7
|
// bottom (add/settings/docs/roby) stay pinned. Projects are listed newest-first
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// also be collapsed into a single folder button (state persisted per browser).
|
|
11
11
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
12
12
|
import { useLocation } from "react-router-dom";
|
|
13
|
-
import { Plus, Settings,
|
|
13
|
+
import { Plus, Settings, Monitor, Terminal, Bot, BookOpen, ChevronDown, Folders, type LucideIcon } from "lucide-react";
|
|
14
14
|
import { Logo } from "./Logo";
|
|
15
15
|
import { ProjectAvatar, projectTone } from "./ProjectAvatar";
|
|
16
16
|
import { Tip } from "../ui/tip";
|
|
@@ -45,10 +45,8 @@ interface ModuleItem {
|
|
|
45
45
|
// top-level entry next to Base rather than living inside Settings.
|
|
46
46
|
function buildModules(): ModuleItem[] {
|
|
47
47
|
return [
|
|
48
|
-
{ id: "voice", label: t("nav.modules.voice"), href: "/m/voice", icon: Mic },
|
|
49
48
|
{ id: "desktop", label: t("nav.modules.desktop"), href: "/m/desktop", icon: Monitor },
|
|
50
|
-
{ id: "
|
|
51
|
-
{ id: "code", label: t("nav.modules.code"), href: "/m/code", icon: Terminal },
|
|
49
|
+
{ id: "code", label: t("nav.modules.code"), href: "/m/code", icon: Terminal },
|
|
52
50
|
];
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -207,8 +205,7 @@ export function ProjectSidebar({ onSelect, onOpenRoby, onOpenAddProject }: Props
|
|
|
207
205
|
/>
|
|
208
206
|
)}
|
|
209
207
|
|
|
210
|
-
{/* Modules — rail-level surfaces
|
|
211
|
-
<div className="my-0.5 h-px w-8 rounded-full bg-border" />
|
|
208
|
+
{/* Modules — rail-level surfaces grouped with Base (no divider). */}
|
|
212
209
|
{MODULES.map((m) => (
|
|
213
210
|
<ProjectAvatar
|
|
214
211
|
key={m.id}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
|
+
import useSWR from "swr";
|
|
4
|
+
import { Section } from "../Section";
|
|
5
|
+
import { Button, Field, Switch, Loading } from "../ui";
|
|
6
|
+
import { UiSelect } from "../UiSelect";
|
|
7
|
+
import { ShortcutInput } from "../ShortcutInput";
|
|
8
|
+
import { DesktopStatusCard } from "../desktop/DesktopStatusCard";
|
|
9
|
+
import { useToast } from "../Toast";
|
|
10
|
+
import { useGlobalConfig } from "../../hooks/useGlobalConfig";
|
|
11
|
+
import { Desktop } from "../../lib/api/desktop";
|
|
12
|
+
import { t } from "../../i18n";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_SHORTCUT = "CommandOrControl+G";
|
|
15
|
+
const positionOpts = () => [
|
|
16
|
+
{ value: "left", label: t("modules_ui.desktop_pos_left") },
|
|
17
|
+
{ value: "center", label: t("modules_ui.desktop_pos_center") },
|
|
18
|
+
{ value: "right", label: t("modules_ui.desktop_pos_right") },
|
|
19
|
+
];
|
|
20
|
+
const themeOpts = () => [
|
|
21
|
+
{ value: "system", label: t("modules_ui.desktop_theme_system") },
|
|
22
|
+
{ value: "light", label: t("modules_ui.desktop_theme_light") },
|
|
23
|
+
{ value: "dark", label: t("modules_ui.desktop_theme_dark") },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Desktop configuration — the persisted settings for the floating voice window
|
|
27
|
+
// (autostart, global shortcut, appearance, activation). Lives in Settings; the
|
|
28
|
+
// Desktop rail module only shows live status + the last conversation. The window
|
|
29
|
+
// itself is an Electron process spawned by `apx desktop start`, so this just
|
|
30
|
+
// edits config the daemon persists and toggles per-user autostart.
|
|
31
|
+
export function DesktopSettingsPanel() {
|
|
32
|
+
const toast = useToast();
|
|
33
|
+
const { config, isLoading: cfgLoading, patch } = useGlobalConfig();
|
|
34
|
+
|
|
35
|
+
// config.desktop isn't on the typed GlobalConfig — read it off a local view.
|
|
36
|
+
const cfgView = config as unknown as {
|
|
37
|
+
desktop?: {
|
|
38
|
+
shortcut?: string; enabled?: boolean;
|
|
39
|
+
theme?: "light" | "dark" | "system";
|
|
40
|
+
position?: "left" | "center" | "right";
|
|
41
|
+
};
|
|
42
|
+
overlay?: { shortcut?: string }; // legacy fallback
|
|
43
|
+
};
|
|
44
|
+
const savedShortcut = cfgView.desktop?.shortcut || cfgView.overlay?.shortcut || DEFAULT_SHORTCUT;
|
|
45
|
+
const enabled = cfgView.desktop?.enabled !== false;
|
|
46
|
+
// Default to "system" so the window follows the OS appearance until the
|
|
47
|
+
// user explicitly pins light/dark.
|
|
48
|
+
const theme = cfgView.desktop?.theme || "system";
|
|
49
|
+
const position = cfgView.desktop?.position || "right";
|
|
50
|
+
|
|
51
|
+
const { data: autostart, mutate: mutateAutostart } = useSWR(
|
|
52
|
+
"/desktop/autostart",
|
|
53
|
+
() => Desktop.autostartGet(),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const [shortcut, setShortcut] = useState(savedShortcut);
|
|
57
|
+
const [busy, setBusy] = useState(false);
|
|
58
|
+
const [autostartBusy, setAutostartBusy] = useState(false);
|
|
59
|
+
useEffect(() => setShortcut(savedShortcut), [savedShortcut]);
|
|
60
|
+
|
|
61
|
+
const saveShortcut = async () => {
|
|
62
|
+
const next = shortcut.trim();
|
|
63
|
+
if (!next || next === savedShortcut) return;
|
|
64
|
+
setBusy(true);
|
|
65
|
+
try {
|
|
66
|
+
await patch({ "desktop.shortcut": next });
|
|
67
|
+
toast.success(t("modules_ui.desktop_shortcut_saved"));
|
|
68
|
+
} catch (e) { toast.error((e as Error).message); }
|
|
69
|
+
finally { setBusy(false); }
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const patchKey = async (key: string, value: unknown, ok: string) => {
|
|
73
|
+
setBusy(true);
|
|
74
|
+
try {
|
|
75
|
+
await patch({ [key]: value });
|
|
76
|
+
toast.success(ok);
|
|
77
|
+
} catch (e) { toast.error((e as Error).message); }
|
|
78
|
+
finally { setBusy(false); }
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const toggleAutostart = async (v: boolean) => {
|
|
82
|
+
setAutostartBusy(true);
|
|
83
|
+
try {
|
|
84
|
+
await Desktop.autostartSet(v);
|
|
85
|
+
await mutateAutostart();
|
|
86
|
+
toast.success(v ? t("modules_ui.desktop_autostart_on") : t("modules_ui.desktop_autostart_off"));
|
|
87
|
+
} catch (e) { toast.error((e as Error).message); }
|
|
88
|
+
finally { setAutostartBusy(false); }
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="space-y-6" data-testid="settings-desktop">
|
|
93
|
+
<DesktopStatusCard />
|
|
94
|
+
|
|
95
|
+
<Section
|
|
96
|
+
title={t("desktop_screen.autostart_title")}
|
|
97
|
+
description={t("modules_ui.desktop_autostart_desc")}
|
|
98
|
+
>
|
|
99
|
+
{!autostart ? <Loading /> : (
|
|
100
|
+
<div className="flex items-center justify-between gap-3">
|
|
101
|
+
<Switch
|
|
102
|
+
checked={autostart.enabled}
|
|
103
|
+
onChange={toggleAutostart}
|
|
104
|
+
disabled={autostartBusy}
|
|
105
|
+
label={autostart.enabled ? t("common.enabled") : t("common.disabled")}
|
|
106
|
+
/>
|
|
107
|
+
<span className="text-xs text-muted-fg">{t("modules_ui.desktop_platform", { platform: autostart.platform })}</span>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
</Section>
|
|
111
|
+
|
|
112
|
+
<Section
|
|
113
|
+
title={t("desktop_screen.shortcut_title")}
|
|
114
|
+
description={t("modules_ui.desktop_shortcut_desc")}
|
|
115
|
+
>
|
|
116
|
+
{cfgLoading ? <Loading /> : (
|
|
117
|
+
<Field
|
|
118
|
+
label={t("modules_ui.desktop_accelerator")}
|
|
119
|
+
hint={t("modules_ui.desktop_accelerator_hint")}
|
|
120
|
+
>
|
|
121
|
+
<ShortcutInput
|
|
122
|
+
value={shortcut}
|
|
123
|
+
onChange={setShortcut}
|
|
124
|
+
disabled={busy}
|
|
125
|
+
trailing={
|
|
126
|
+
<Button
|
|
127
|
+
variant="primary"
|
|
128
|
+
size="sm"
|
|
129
|
+
onClick={saveShortcut}
|
|
130
|
+
loading={busy}
|
|
131
|
+
disabled={!shortcut.trim() || shortcut.trim() === savedShortcut}
|
|
132
|
+
>
|
|
133
|
+
{t("common.save")}
|
|
134
|
+
</Button>
|
|
135
|
+
}
|
|
136
|
+
/>
|
|
137
|
+
</Field>
|
|
138
|
+
)}
|
|
139
|
+
</Section>
|
|
140
|
+
|
|
141
|
+
<Section title={t("desktop_screen.appearance_title")} description={t("modules_ui.desktop_appearance_desc")}>
|
|
142
|
+
{cfgLoading ? <Loading /> : (
|
|
143
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
144
|
+
<Field label={t("modules_ui.desktop_theme")} hint={t("modules_ui.desktop_restart_apply")}>
|
|
145
|
+
<UiSelect
|
|
146
|
+
value={theme}
|
|
147
|
+
onChange={(v) => patchKey("desktop.theme", v, t("modules_ui.desktop_theme_set", { value: v }))}
|
|
148
|
+
options={themeOpts()}
|
|
149
|
+
disabled={busy}
|
|
150
|
+
/>
|
|
151
|
+
</Field>
|
|
152
|
+
<Field label={t("modules_ui.desktop_position")} hint={t("modules_ui.desktop_position_hint")}>
|
|
153
|
+
<UiSelect
|
|
154
|
+
value={position}
|
|
155
|
+
onChange={(v) => patchKey("desktop.position", v, t("modules_ui.desktop_position_set", { value: v }))}
|
|
156
|
+
options={positionOpts()}
|
|
157
|
+
disabled={busy}
|
|
158
|
+
/>
|
|
159
|
+
</Field>
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</Section>
|
|
163
|
+
|
|
164
|
+
<Section
|
|
165
|
+
title={t("desktop_screen.activation_title")}
|
|
166
|
+
description={t("modules_ui.desktop_activation_desc")}
|
|
167
|
+
>
|
|
168
|
+
{cfgLoading ? <Loading /> : (
|
|
169
|
+
<div className="space-y-3">
|
|
170
|
+
<Switch
|
|
171
|
+
checked={enabled}
|
|
172
|
+
onChange={(v) => patchKey("desktop.enabled", v, v ? t("modules_ui.desktop_enabled_toast") : t("modules_ui.desktop_disabled_toast"))}
|
|
173
|
+
disabled={busy}
|
|
174
|
+
label={enabled ? t("modules_ui.desktop_plugin_on") : t("modules_ui.desktop_plugin_off")}
|
|
175
|
+
/>
|
|
176
|
+
<p className="text-xs text-muted-fg">
|
|
177
|
+
{t("modules_ui.desktop_stt_engine")} <Link to="/settings/voice" className="font-medium text-fg underline underline-offset-2">{t("nav.modules.voice")}</Link>{" "}
|
|
178
|
+
{t("modules_ui.desktop_stt_engine_suffix")}
|
|
179
|
+
</p>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</Section>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -180,6 +180,7 @@ export const en = {
|
|
|
180
180
|
account_section: "Account",
|
|
181
181
|
agents_section: "Agents & models",
|
|
182
182
|
channels_section: "Channels & devices",
|
|
183
|
+
modules_section: "Modules",
|
|
183
184
|
advanced_section: "Advanced",
|
|
184
185
|
|
|
185
186
|
tabs: {
|
|
@@ -930,6 +931,7 @@ export const en = {
|
|
|
930
931
|
appearance_title: "Appearance",
|
|
931
932
|
activation_title: "Activation + transcription",
|
|
932
933
|
last_conv_title: "Last conversation",
|
|
934
|
+
open_config: "Configuration",
|
|
933
935
|
},
|
|
934
936
|
|
|
935
937
|
voice_screen: {
|
|
@@ -181,6 +181,7 @@ export const es = {
|
|
|
181
181
|
account_section: "Cuenta",
|
|
182
182
|
agents_section: "Agentes & modelos",
|
|
183
183
|
channels_section: "Canales & dispositivos",
|
|
184
|
+
modules_section: "Módulos",
|
|
184
185
|
advanced_section: "Avanzado",
|
|
185
186
|
|
|
186
187
|
tabs: {
|
|
@@ -928,6 +929,7 @@ export const es = {
|
|
|
928
929
|
appearance_title: "Apariencia",
|
|
929
930
|
activation_title: "Activación + transcripción",
|
|
930
931
|
last_conv_title: "Última conversación",
|
|
932
|
+
open_config: "Configuración",
|
|
931
933
|
},
|
|
932
934
|
|
|
933
935
|
voice_screen: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
2
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
3
3
|
import {
|
|
4
|
-
Bot, Cpu, Database, KeyRound, MessageCircle, Palette, ScrollText, Send, Smartphone, Sparkles, User,
|
|
4
|
+
Bot, Cpu, Database, KeyRound, LayoutGrid, MessageCircle, Mic, Monitor, Palette, ScrollText, Send, Smartphone, Sparkles, User,
|
|
5
5
|
} from "lucide-react";
|
|
6
6
|
import { useNavCollapse, type TabSection } from "../components/common/TabNav";
|
|
7
7
|
import { TabLayout } from "../components/common/TabLayout";
|
|
@@ -14,11 +14,15 @@ import { TelegramSettingsTabs } from "../components/settings/TelegramSettingsTab
|
|
|
14
14
|
import { DevicesPanel } from "../components/settings/DevicesPanel";
|
|
15
15
|
import { AdvancedPanel } from "../components/settings/AdvancedPanel";
|
|
16
16
|
import { AppearancePanel } from "../components/settings/AppearancePanel";
|
|
17
|
+
import { DesktopSettingsPanel } from "../components/settings/DesktopSettingsPanel";
|
|
18
|
+
import { VoiceScreen } from "./modules/VoiceScreen";
|
|
19
|
+
import { DeckScreen } from "./modules/DeckScreen";
|
|
17
20
|
import { STORAGE } from "../constants";
|
|
18
21
|
import { t } from "../i18n";
|
|
19
22
|
|
|
20
23
|
type TabKey =
|
|
21
|
-
| "identity" | "super_agent" | "engines" | "memory" | "skills" | "telegram" | "devices"
|
|
24
|
+
| "identity" | "super_agent" | "engines" | "memory" | "skills" | "telegram" | "devices"
|
|
25
|
+
| "voice" | "deck" | "desktop" | "appearance" | "advanced";
|
|
22
26
|
|
|
23
27
|
const SECTIONS: TabSection[] = [
|
|
24
28
|
{
|
|
@@ -44,6 +48,14 @@ const SECTIONS: TabSection[] = [
|
|
|
44
48
|
{ key: "devices", label: t("settings.tabs.devices"), icon: Smartphone },
|
|
45
49
|
],
|
|
46
50
|
},
|
|
51
|
+
{
|
|
52
|
+
title: t("settings.modules_section"),
|
|
53
|
+
items: [
|
|
54
|
+
{ key: "voice", label: t("nav.modules.voice"), icon: Mic },
|
|
55
|
+
{ key: "deck", label: t("nav.modules.deck"), icon: LayoutGrid },
|
|
56
|
+
{ key: "desktop", label: t("nav.modules.desktop"), icon: Monitor },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
47
59
|
{
|
|
48
60
|
title: t("settings.advanced_section"),
|
|
49
61
|
items: [
|
|
@@ -56,7 +68,7 @@ const SECTIONS: TabSection[] = [
|
|
|
56
68
|
// on xl (and so wants full available width). Single-section panels (identity,
|
|
57
69
|
// super agent, devices, advanced) keep a cosier reading width so wide displays
|
|
58
70
|
// don't blow form fields up to absurd widths.
|
|
59
|
-
const WIDE_TABS = new Set<TabKey>(["engines", "telegram", "memory", "skills", "appearance"]);
|
|
71
|
+
const WIDE_TABS = new Set<TabKey>(["engines", "telegram", "memory", "skills", "appearance", "voice"]);
|
|
60
72
|
|
|
61
73
|
const PANELS: Record<TabKey, () => ReactElement> = {
|
|
62
74
|
identity: () => <IdentityPanel />,
|
|
@@ -66,6 +78,9 @@ const PANELS: Record<TabKey, () => ReactElement> = {
|
|
|
66
78
|
skills: () => <SkillsInspectorPanel />,
|
|
67
79
|
telegram: () => <TelegramSettingsTabs />,
|
|
68
80
|
devices: () => <DevicesPanel />,
|
|
81
|
+
voice: () => <VoiceScreen />,
|
|
82
|
+
deck: () => <DeckScreen />,
|
|
83
|
+
desktop: () => <DesktopSettingsPanel />,
|
|
69
84
|
appearance: () => <AppearancePanel />,
|
|
70
85
|
advanced: () => <AdvancedPanel />,
|
|
71
86
|
};
|
|
@@ -103,6 +118,9 @@ function tabFromPath(pathname: string): TabKey {
|
|
|
103
118
|
case "skills": return "skills";
|
|
104
119
|
case "telegram": return "telegram";
|
|
105
120
|
case "devices": return "devices";
|
|
121
|
+
case "voice": return "voice";
|
|
122
|
+
case "deck": return "deck";
|
|
123
|
+
case "desktop": return "desktop";
|
|
106
124
|
case "appearance": return "appearance";
|
|
107
125
|
case "config":
|
|
108
126
|
case "advanced": return "advanced";
|