@steerable/agent-shell-web 0.6.46 → 0.6.48
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 +4 -4
- package/src/components/AgentSidebar.tsx +12 -1
- package/src/components/SidebarRelease.tsx +67 -0
- package/src/components/settings/AppUpdateSettingsPanel.tsx +56 -0
- package/src/lib/app-release.ts +66 -0
- package/src/lib/electron-bridge.ts +31 -0
- package/src/lib/tauri-bridge.ts +22 -1
- package/src/pages/SettingsPage.tsx +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell-web",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.48",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Steerable framework — product-neutral renderer SPA source for @steerable/agent-shell (React + Vite). Consumed by product web entry points via the `@/` alias (anchored at this package's src/) and the createProductViteConfig factory. Published as a source package (src/), plus the compiled vite.base config factory (vite.base.js + .d.ts) for Node-side config loading.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"react-router-dom": "^6.30.0",
|
|
46
46
|
"rehype-raw": "^7.0.0",
|
|
47
47
|
"remark-gfm": "^4.0.1",
|
|
48
|
-
"@steerable/agent-protocol": "0.6.
|
|
49
|
-
"@steerable/agent-ui": "0.6.
|
|
48
|
+
"@steerable/agent-protocol": "0.6.48",
|
|
49
|
+
"@steerable/agent-ui": "0.6.48"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@tailwindcss/vite": "^4.3.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"typescript": "^5.8.3",
|
|
60
60
|
"vite": "^5.4.21",
|
|
61
61
|
"vitest": "^3.2.4",
|
|
62
|
-
"@steerable/pack-sdk": "0.6.
|
|
62
|
+
"@steerable/pack-sdk": "0.6.48"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"gen": "echo agent-shell-web",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
* │ ...(无项目对话,按日期分组) │ ← 无项目排在项目分组之后
|
|
71
71
|
* ├─────────────────────────────────┤
|
|
72
72
|
* │ ▢_ 终端 ⌘T │
|
|
73
|
-
* │ ⚙ 设置
|
|
73
|
+
* │ ⚙ 设置 v0.2.2 │ ← /settings;右侧是当前版本,检查更新在设置页
|
|
74
74
|
* └─────────────────────────────────┘
|
|
75
75
|
*
|
|
76
76
|
* 项目模式:项目 = 名字 + 托管家目录(Documents/<应用名>/<项目名>/)+
|
|
@@ -117,6 +117,10 @@ import {
|
|
|
117
117
|
import type { UseChatsAndAgentsResult } from "@/hooks/useChatsAndAgents";
|
|
118
118
|
import type { RightPanelState } from "@/layouts/AgentLayout";
|
|
119
119
|
import { BrandLockup } from "@/components/BrandLockup";
|
|
120
|
+
import {
|
|
121
|
+
SidebarVersionLabel,
|
|
122
|
+
useAppRelease,
|
|
123
|
+
} from "@/components/SidebarRelease";
|
|
120
124
|
import { CreateProjectModal } from "@/components/CreateProjectModal";
|
|
121
125
|
|
|
122
126
|
const DEFAULT_DOT_COLOR = "#7c3aed";
|
|
@@ -285,6 +289,7 @@ export function AgentSidebar({
|
|
|
285
289
|
const location = useLocation();
|
|
286
290
|
const { chatId: currentChatId } = useParams<{ chatId?: string }>();
|
|
287
291
|
const bridge = getElectronBridge();
|
|
292
|
+
const release = useAppRelease();
|
|
288
293
|
const onSettingsPage = location.pathname === "/settings";
|
|
289
294
|
// /settings?section=skills|mcp|agents|(缺省 = 综合设置)—— 各自高亮。
|
|
290
295
|
const settingsSection = useMemo(
|
|
@@ -1085,8 +1090,14 @@ export function AgentSidebar({
|
|
|
1085
1090
|
>
|
|
1086
1091
|
<LuSettings className="h-3.5 w-3.5" />
|
|
1087
1092
|
<span>设置</span>
|
|
1093
|
+
<SidebarVersionLabel release={release} />
|
|
1088
1094
|
</button>
|
|
1089
1095
|
)}
|
|
1096
|
+
{!hasGeneralSettingsChrome() && release.version ? (
|
|
1097
|
+
<div className="mt-0.5 flex h-7 w-full items-center px-2.5">
|
|
1098
|
+
<SidebarVersionLabel release={release} />
|
|
1099
|
+
</div>
|
|
1100
|
+
) : null}
|
|
1090
1101
|
</div>
|
|
1091
1102
|
|
|
1092
1103
|
{projectMenu &&
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
sidebarReleaseView,
|
|
4
|
+
type AppReleaseView,
|
|
5
|
+
} from '@/lib/app-release';
|
|
6
|
+
import {
|
|
7
|
+
getElectronBridge,
|
|
8
|
+
type AppReleaseSnapshot,
|
|
9
|
+
} from '@/lib/electron-bridge';
|
|
10
|
+
|
|
11
|
+
export function useAppRelease(): AppReleaseView & { run: () => void } {
|
|
12
|
+
const [snap, setSnap] = useState<AppReleaseSnapshot | null>(null);
|
|
13
|
+
const [confirmedCurrent, setConfirmedCurrent] = useState(false);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const app = getElectronBridge()?.app;
|
|
17
|
+
if (!app) return;
|
|
18
|
+
let alive = true;
|
|
19
|
+
void app.snapshot().then((next) => {
|
|
20
|
+
if (alive) setSnap(next);
|
|
21
|
+
}).catch(() => {});
|
|
22
|
+
const off = app.onState((next) => {
|
|
23
|
+
if (!alive) return;
|
|
24
|
+
setSnap(next);
|
|
25
|
+
if (next.phase !== 'idle') setConfirmedCurrent(false);
|
|
26
|
+
});
|
|
27
|
+
return () => {
|
|
28
|
+
alive = false;
|
|
29
|
+
off();
|
|
30
|
+
};
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const run = useCallback(() => {
|
|
34
|
+
const app = getElectronBridge()?.app;
|
|
35
|
+
if (!app || !snap || !snap.enabled) return;
|
|
36
|
+
if (snap.phase !== 'idle' && snap.phase !== 'error' && snap.phase !== 'ready') return;
|
|
37
|
+
const task = snap.phase === 'ready' ? app.install() : app.check();
|
|
38
|
+
void task
|
|
39
|
+
.then((next) => {
|
|
40
|
+
setSnap(next);
|
|
41
|
+
setConfirmedCurrent(next.phase === 'idle' && !next.availableVersion);
|
|
42
|
+
})
|
|
43
|
+
.catch((error: unknown) => {
|
|
44
|
+
setConfirmedCurrent(false);
|
|
45
|
+
setSnap({
|
|
46
|
+
...snap,
|
|
47
|
+
phase: 'error',
|
|
48
|
+
message: error instanceof Error ? error.message : String(error),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}, [snap]);
|
|
52
|
+
|
|
53
|
+
return { ...sidebarReleaseView(snap, confirmedCurrent), run };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function SidebarVersionLabel({ release }: { release: AppReleaseView }) {
|
|
57
|
+
if (!release.version) return null;
|
|
58
|
+
return (
|
|
59
|
+
<span
|
|
60
|
+
className="ml-auto text-[10px] tabular-nums text-agent-muted-foreground/70"
|
|
61
|
+
data-testid="sidebar-app-version"
|
|
62
|
+
title={`当前版本 ${release.version}`}
|
|
63
|
+
>
|
|
64
|
+
v{release.version}
|
|
65
|
+
</span>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { LuInfo } from 'react-icons/lu';
|
|
2
|
+
import { useAppRelease } from '@/components/SidebarRelease';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 综合设置里的版本与更新。侧栏只显示版本号;检查、下载和安装在这里。
|
|
6
|
+
* 没有桌面更新通道时不渲染。
|
|
7
|
+
*/
|
|
8
|
+
export function AppUpdateSettingsPanel() {
|
|
9
|
+
const release = useAppRelease();
|
|
10
|
+
if (!release.version) return null;
|
|
11
|
+
const detail =
|
|
12
|
+
release.actionTitle && release.actionTitle !== release.actionLabel
|
|
13
|
+
? release.actionTitle
|
|
14
|
+
: null;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<section className="space-y-2" data-testid="settings-section-update">
|
|
18
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
19
|
+
<LuInfo className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
20
|
+
关于
|
|
21
|
+
</h2>
|
|
22
|
+
<div className="flex items-center gap-3 rounded-agent-md border border-agent-border bg-agent-card p-2.5">
|
|
23
|
+
<div className="min-w-0 flex-1">
|
|
24
|
+
<p className="text-xs font-medium text-agent-foreground">当前版本</p>
|
|
25
|
+
<p
|
|
26
|
+
className="mt-1 text-xs tabular-nums text-agent-muted-foreground"
|
|
27
|
+
data-testid="settings-app-version"
|
|
28
|
+
>
|
|
29
|
+
v{release.version}
|
|
30
|
+
</p>
|
|
31
|
+
{detail ? (
|
|
32
|
+
<p className="mt-1 text-xs leading-relaxed text-agent-muted-foreground">{detail}</p>
|
|
33
|
+
) : null}
|
|
34
|
+
</div>
|
|
35
|
+
{release.actionLabel ? (
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
className={[
|
|
39
|
+
'flex h-7 shrink-0 items-center rounded-full px-3 text-xs transition-colors',
|
|
40
|
+
release.emphasize
|
|
41
|
+
? 'bg-agent-foreground font-medium text-agent-canvas hover:opacity-90'
|
|
42
|
+
: 'border border-agent-border text-agent-foreground hover:bg-agent-foreground/5',
|
|
43
|
+
release.clickable ? '' : 'cursor-default opacity-60',
|
|
44
|
+
].join(' ')}
|
|
45
|
+
data-testid="settings-app-update"
|
|
46
|
+
title={release.actionTitle}
|
|
47
|
+
disabled={!release.clickable}
|
|
48
|
+
onClick={release.run}
|
|
49
|
+
>
|
|
50
|
+
{release.actionLabel}
|
|
51
|
+
</button>
|
|
52
|
+
) : null}
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AppReleasePhase, AppReleaseSnapshot } from './electron-bridge';
|
|
2
|
+
|
|
3
|
+
export interface AppReleaseView {
|
|
4
|
+
version: string | null;
|
|
5
|
+
actionLabel: string | null;
|
|
6
|
+
actionTitle?: string;
|
|
7
|
+
clickable: boolean;
|
|
8
|
+
/** 已下载、可以重启安装。 */
|
|
9
|
+
emphasize: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function sidebarUpdateClickable(phase: AppReleasePhase): boolean {
|
|
13
|
+
return phase === 'idle' || phase === 'error' || phase === 'ready';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sidebarUpdateLabel(
|
|
17
|
+
snap: AppReleaseSnapshot,
|
|
18
|
+
confirmedCurrent: boolean,
|
|
19
|
+
): string | null {
|
|
20
|
+
if (!snap.enabled || snap.phase === 'disabled') return null;
|
|
21
|
+
switch (snap.phase) {
|
|
22
|
+
case 'checking':
|
|
23
|
+
return '检查中';
|
|
24
|
+
case 'downloading':
|
|
25
|
+
return typeof snap.percent === 'number' ? `下载 ${Math.round(snap.percent)}%` : '下载中';
|
|
26
|
+
case 'ready':
|
|
27
|
+
return snap.availableVersion ? `升级 ${snap.availableVersion}` : '升级';
|
|
28
|
+
case 'installing':
|
|
29
|
+
return '正在安装';
|
|
30
|
+
case 'error':
|
|
31
|
+
return '重试';
|
|
32
|
+
case 'idle':
|
|
33
|
+
if (snap.availableVersion) return `发现 ${snap.availableVersion}`;
|
|
34
|
+
if (snap.message) return snap.message;
|
|
35
|
+
return confirmedCurrent ? '已是最新' : '检查更新';
|
|
36
|
+
default:
|
|
37
|
+
return '检查更新';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function sidebarUpdateTitle(snap: AppReleaseSnapshot): string | undefined {
|
|
42
|
+
if (snap.phase === 'error' || (snap.phase === 'idle' && snap.availableVersion)) {
|
|
43
|
+
return snap.message;
|
|
44
|
+
}
|
|
45
|
+
if (snap.phase === 'ready' && snap.availableVersion) {
|
|
46
|
+
return `重启并安装 ${snap.availableVersion}`;
|
|
47
|
+
}
|
|
48
|
+
if (snap.phase === 'downloading') return '正在下载更新';
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function sidebarReleaseView(
|
|
53
|
+
snap: AppReleaseSnapshot | null,
|
|
54
|
+
confirmedCurrent: boolean,
|
|
55
|
+
): AppReleaseView {
|
|
56
|
+
if (!snap?.version) {
|
|
57
|
+
return { version: null, actionLabel: null, clickable: false, emphasize: false };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
version: snap.version,
|
|
61
|
+
actionLabel: sidebarUpdateLabel(snap, confirmedCurrent),
|
|
62
|
+
actionTitle: sidebarUpdateTitle(snap),
|
|
63
|
+
clickable: snap.enabled && sidebarUpdateClickable(snap.phase),
|
|
64
|
+
emphasize: snap.phase === 'ready',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -88,6 +88,28 @@ export interface TerminalSpawnOptions {
|
|
|
88
88
|
rows?: number;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
export type AppReleasePhase =
|
|
92
|
+
| 'disabled'
|
|
93
|
+
| 'idle'
|
|
94
|
+
| 'checking'
|
|
95
|
+
| 'downloading'
|
|
96
|
+
| 'ready'
|
|
97
|
+
| 'installing'
|
|
98
|
+
| 'error';
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 桌面版本与更新。`version` 是当前安装版本,`availableVersion` 是待安装版本。
|
|
102
|
+
* 与主进程 `AppReleaseSnapshot` 对齐。
|
|
103
|
+
*/
|
|
104
|
+
export interface AppReleaseSnapshot {
|
|
105
|
+
version: string;
|
|
106
|
+
enabled: boolean;
|
|
107
|
+
phase: AppReleasePhase;
|
|
108
|
+
availableVersion?: string;
|
|
109
|
+
percent?: number;
|
|
110
|
+
message?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
export interface HostBridge {
|
|
92
114
|
runtime: 'local';
|
|
93
115
|
platform: NodeJS.Platform;
|
|
@@ -246,6 +268,15 @@ export interface HostBridge {
|
|
|
246
268
|
}) => void,
|
|
247
269
|
) => () => void;
|
|
248
270
|
};
|
|
271
|
+
/**
|
|
272
|
+
* 桌面版本与更新。Electron / Tauri 提供;浏览器预览没有这一段。
|
|
273
|
+
*/
|
|
274
|
+
app?: {
|
|
275
|
+
snapshot: () => Promise<AppReleaseSnapshot>;
|
|
276
|
+
check: () => Promise<AppReleaseSnapshot>;
|
|
277
|
+
install: () => Promise<AppReleaseSnapshot>;
|
|
278
|
+
onState: (callback: (snapshot: AppReleaseSnapshot) => void) => () => void;
|
|
279
|
+
};
|
|
249
280
|
// 场景包的 invoke 命名空间(如包 preload 贡献的 `<pack>` / `<pack>Mock`)
|
|
250
281
|
// 不进本接口——包用自己的结构化收窄访问器(见各包 web/bridge.ts),
|
|
251
282
|
// shell 桥接口保持产品中立(3.1)。
|
package/src/lib/tauri-bridge.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { invoke } from '@tauri-apps/api/core';
|
|
9
9
|
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
|
|
10
10
|
import html2canvas from 'html2canvas';
|
|
11
|
-
import type { HostBridge } from './electron-bridge';
|
|
11
|
+
import type { AppReleaseSnapshot, HostBridge } from './electron-bridge';
|
|
12
12
|
import { createHttpBridge } from './http-bridge';
|
|
13
13
|
|
|
14
14
|
type VoidCallback = () => void;
|
|
@@ -85,6 +85,27 @@ export function createTauriBridge(): HostBridge {
|
|
|
85
85
|
offMenuNewChat: newChatMenu.off,
|
|
86
86
|
onMenuOpenTerminal: terminalMenu.on,
|
|
87
87
|
offMenuOpenTerminal: terminalMenu.off,
|
|
88
|
+
app: {
|
|
89
|
+
snapshot: () => invoke<AppReleaseSnapshot>('app_release_snapshot'),
|
|
90
|
+
check: () => invoke<AppReleaseSnapshot>('app_release_check'),
|
|
91
|
+
install: () => invoke<AppReleaseSnapshot>('app_release_install'),
|
|
92
|
+
onState(callback) {
|
|
93
|
+
let unlisten: UnlistenFn | undefined;
|
|
94
|
+
let cancelled = false;
|
|
95
|
+
const pending = listen<AppReleaseSnapshot>('app-update-state', (event) => {
|
|
96
|
+
callback(event.payload);
|
|
97
|
+
});
|
|
98
|
+
void pending.then((stop) => {
|
|
99
|
+
if (cancelled) stop();
|
|
100
|
+
else unlisten = stop;
|
|
101
|
+
});
|
|
102
|
+
return () => {
|
|
103
|
+
cancelled = true;
|
|
104
|
+
if (unlisten) unlisten();
|
|
105
|
+
else void pending.then((stop) => stop());
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
},
|
|
88
109
|
};
|
|
89
110
|
}
|
|
90
111
|
|
|
@@ -4,6 +4,7 @@ import { useOutletContext, useSearchParams } from 'react-router-dom';
|
|
|
4
4
|
import { isElectron } from '@/lib/electron-bridge';
|
|
5
5
|
import { AgentsSettingsPanel } from '@/components/settings/AgentsSettingsPanel';
|
|
6
6
|
import { AppearanceSettingsPanel } from '@/components/settings/AppearanceSettingsPanel';
|
|
7
|
+
import { AppUpdateSettingsPanel } from '@/components/settings/AppUpdateSettingsPanel';
|
|
7
8
|
import { DiagnoseSettingsPanel } from '@/components/settings/DiagnoseSettingsPanel';
|
|
8
9
|
import { InsightsSettingsPanel } from '@/components/settings/InsightsSettingsPanel';
|
|
9
10
|
import {
|
|
@@ -36,7 +37,7 @@ function resolveSection(raw: string | null): SettingsSection {
|
|
|
36
37
|
* - `skills` 侧栏「Skill 设置」
|
|
37
38
|
* - `mcp` 侧栏「MCP 设置」
|
|
38
39
|
* - `agents` 侧栏「智能体管理」
|
|
39
|
-
* - 缺省/`general` 侧栏底「设置」(界面 / 模型 / 搜索 / 用量 / 安全 / 洞察 /
|
|
40
|
+
* - 缺省/`general` 侧栏底「设置」(界面 / 模型 / 搜索 / 用量 / 安全 / 洞察 / 遥测 / 关于)
|
|
40
41
|
*
|
|
41
42
|
* Panel 数据自管理(挂载即拉取),页面本身不持有后端状态。
|
|
42
43
|
*/
|
|
@@ -216,6 +217,8 @@ export function SettingsPage() {
|
|
|
216
217
|
<panel.Component />
|
|
217
218
|
</section>
|
|
218
219
|
))}
|
|
220
|
+
|
|
221
|
+
<AppUpdateSettingsPanel />
|
|
219
222
|
</>
|
|
220
223
|
)}
|
|
221
224
|
</div>
|