@jetcrab/pi-desk 1.1.31 → 1.1.32-dev
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/README.md +24 -10
- package/README.zh-CN.md +24 -10
- package/package.json +1 -1
- package/src/client/l2_biz/workbench/hooks/l2-workbench-model-context.ts +39 -7
- package/src/client/l2_biz/workbench/l2-workbench-model-context-dialog.tsx +7 -8
- package/src/client/l2_biz/workbench/native-ui/l2-native-ui-mcp.tsx +66 -99
- package/src/client/l2_biz/workbench/native-ui/l2-native-ui-notices.tsx +2 -2
- package/src/client/l2_biz/workbench/native-ui/l2-native-ui-view.tsx +5 -2
- package/src/client/l4_foundation/desktop-window/l4-desktop-titlebar.tsx +8 -15
- package/src/client/l4_foundation/desktop-window/l4-desktop-window.css +0 -4
- package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +12 -16
- package/src/common/l4_foundation/locale/l4-host-error-messages.ts +0 -2
- package/src/server/l1_entry/api/l1-chat-context-get-route.ts +0 -4
- package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +0 -19
- package/src/server/l4_foundation/pi/l4-pi-mcp-command.ts +48 -0
- package/src/server/l4_foundation/pi/l4-pi-session-resources.ts +11 -2
- package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
- package/temp/build/pi-desk/release/.next/build-manifest.json +2 -2
- package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/required-server-files.js +3 -3
- package/temp/build/pi-desk/release/.next/required-server-files.json +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/chat/context/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/page.js +2 -2
- package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/25406.js +11 -11
- package/temp/build/pi-desk/release/.next/server/chunks/35985.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/92386.js +10 -10
- package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
- package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/11937-d5d4d2199356c3ed.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/layout-fdea504082053156.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{page-c27bac30ceaea98b.js → page-20eb04ea250423e4.js} +6 -6
- package/temp/build/pi-desk/release/.next/static/css/e44d06d81c9535c0.css +1 -0
- package/temp/build/pi-desk/release/.next/trace-build +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/11937-ac71fb0f7cef482a.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/layout-ca5ef5994165eb2e.js +0 -1
- package/temp/build/pi-desk/release/.next/static/css/3c59ac068ae404da.css +0 -1
- /package/temp/build/pi-desk/release/.next/static/{n_zNpuntCtgSLQXHItXgr → OKkvmSoIKvwTgI3I2x3yp}/_buildManifest.js +0 -0
- /package/temp/build/pi-desk/release/.next/static/{n_zNpuntCtgSLQXHItXgr → OKkvmSoIKvwTgI3I2x3yp}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -20,16 +20,30 @@ Pi powers the agent. Pi Desk provides the interface, with a lightweight core and
|
|
|
20
20
|
- **Tasks and results in view.** Follow background commands and subagents in the task center, and preview deliverables with the corresponding plugins.
|
|
21
21
|
- **Extend it your way.** Add tools, panels, applications, and custom message views through Pi Desk plugins and the public SDK.
|
|
22
22
|
|
|
23
|
-
## Interface
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
</
|
|
32
|
-
|
|
23
|
+
## Interface preview
|
|
24
|
+
|
|
25
|
+
Explore common Pi Desk views below; appearance varies by version and theme, and you can click any screenshot to view it full size.
|
|
26
|
+
|
|
27
|
+
<table>
|
|
28
|
+
<tr>
|
|
29
|
+
<th width="50%">File preview</th>
|
|
30
|
+
<th width="50%">Side-by-side conversations</th>
|
|
31
|
+
</tr>
|
|
32
|
+
<tr>
|
|
33
|
+
<td>
|
|
34
|
+
<a href="./docs/doc_asset/client-overview.webp">
|
|
35
|
+
<img src="./docs/doc_asset/client-overview.webp" alt="File preview" width="100%" />
|
|
36
|
+
</a>
|
|
37
|
+
</td>
|
|
38
|
+
<td>
|
|
39
|
+
<a href="./docs/doc_asset/multi-window.webp">
|
|
40
|
+
<img src="./docs/doc_asset/multi-window.webp" alt="Side-by-side conversations" width="100%" />
|
|
41
|
+
</a>
|
|
42
|
+
</td>
|
|
43
|
+
</tr>
|
|
44
|
+
</table>
|
|
45
|
+
|
|
46
|
+
<!-- Add more screenshots here: copy the table above for each pair, replacing titles, image paths, and links. Repeat for additional groups. -->
|
|
33
47
|
|
|
34
48
|
Visit [pidesk.dev](https://pidesk.dev) for product demos and [the documentation](https://pidesk.dev/docs/) for usage guides.
|
|
35
49
|
|
package/README.zh-CN.md
CHANGED
|
@@ -20,16 +20,30 @@ Pi 负责代理运行,Pi Desk 提供围绕它的工作界面。核心保持轻
|
|
|
20
20
|
- **任务与结果集中呈现。** 通过相应插件,在任务中心跟踪后台命令和子代理,并预览交付物。
|
|
21
21
|
- **按自己的需要扩展。** 使用 Pi Desk 插件和公共 SDK 添加工具、面板、应用及自定义消息视图。
|
|
22
22
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
</
|
|
32
|
-
|
|
23
|
+
## 界面预览
|
|
24
|
+
|
|
25
|
+
以下展示 Pi Desk 的常用界面;具体外观随版本与主题变化,点击截图可查看大图。
|
|
26
|
+
|
|
27
|
+
<table>
|
|
28
|
+
<tr>
|
|
29
|
+
<th width="50%">文件预览</th>
|
|
30
|
+
<th width="50%">多会话并排</th>
|
|
31
|
+
</tr>
|
|
32
|
+
<tr>
|
|
33
|
+
<td>
|
|
34
|
+
<a href="./docs/doc_asset/client-overview.webp">
|
|
35
|
+
<img src="./docs/doc_asset/client-overview.webp" alt="文件预览" width="100%" />
|
|
36
|
+
</a>
|
|
37
|
+
</td>
|
|
38
|
+
<td>
|
|
39
|
+
<a href="./docs/doc_asset/multi-window.webp">
|
|
40
|
+
<img src="./docs/doc_asset/multi-window.webp" alt="多会话并排" width="100%" />
|
|
41
|
+
</a>
|
|
42
|
+
</td>
|
|
43
|
+
</tr>
|
|
44
|
+
</table>
|
|
45
|
+
|
|
46
|
+
<!-- 后续截图追加位置:每组两张,复制上方表格并替换图名、图片路径和链接;可继续追加多组。 -->
|
|
33
47
|
|
|
34
48
|
在 [pidesk.dev](https://pidesk.dev) 查看产品演示,在[使用文档](https://pidesk.dev/docs/)中了解具体操作。
|
|
35
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
4
4
|
import type { L2ChatSource } from '@common/l2_biz/chat/l2-chat-contract'
|
|
5
5
|
import type { L2ChatModelContextGetResponse } from '@common/l2_biz/chat/l2-chat-context-contract'
|
|
6
6
|
import type {
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
} from '@common/l3_modules/plugin-host/l3-plugin-native-pi-contract'
|
|
10
10
|
import type { L2WorkbenchBiz } from '../l2-workbench-biz'
|
|
11
11
|
import type { L2WorkbenchChatInputRuntime } from '../l2-workbench-chat-input-runtime'
|
|
12
|
+
import type { L2NativeUiBiz, L2NativeUiState } from '../native-ui/l2-native-ui-biz'
|
|
12
13
|
|
|
13
14
|
export type L2WorkbenchContextTab = 'tools' | 'system' | 'mcp'
|
|
14
15
|
|
|
@@ -58,18 +59,49 @@ export function useL2WorkbenchModelContext(input: {
|
|
|
58
59
|
runtime: L2WorkbenchChatInputRuntime
|
|
59
60
|
getContext: L2WorkbenchBiz['getChatModelContext']
|
|
60
61
|
listCommands: L2WorkbenchBiz['listPiCommands']
|
|
62
|
+
nativeBiz: L2NativeUiBiz
|
|
63
|
+
nativeState: L2NativeUiState
|
|
61
64
|
}): {
|
|
62
65
|
context: PanelData<L2ChatModelContextGetResponse>
|
|
63
66
|
tools: PanelData<L3PiNativeToolsListResponse>
|
|
64
67
|
commands: PanelData<L3PiNativeCommandsListResponse>
|
|
65
68
|
} {
|
|
66
|
-
const { source, runtime, getContext, listCommands, open, tab } = input
|
|
69
|
+
const { source, runtime, getContext, listCommands, open, tab, nativeBiz, nativeState } = input
|
|
67
70
|
const loadContext = useCallback(() => getContext({ source }), [getContext, source])
|
|
68
71
|
const loadTools = useCallback(() => runtime.listTools(source), [runtime, source])
|
|
69
72
|
const loadCommands = useCallback(() => listCommands({ source }), [listCommands, source])
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const context = usePanelData(open && tab === 'system', loadContext)
|
|
74
|
+
const tools = usePanelData(open && tab === 'tools', loadTools)
|
|
75
|
+
const commands = usePanelData(open && tab === 'mcp', loadCommands)
|
|
76
|
+
const queriedMcp = useRef(false)
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!open || tab !== 'mcp') {
|
|
79
|
+
queriedMcp.current = false
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
if (
|
|
83
|
+
queriedMcp.current ||
|
|
84
|
+
commands.loading ||
|
|
85
|
+
!commands.value?.commands.some(
|
|
86
|
+
(command) => command.name === 'mcp' && command.source === 'extension'
|
|
87
|
+
) ||
|
|
88
|
+
!nativeState.ready ||
|
|
89
|
+
nativeState.commandPending ||
|
|
90
|
+
nativeState.snapshot.requests.length > 0
|
|
91
|
+
)
|
|
92
|
+
return
|
|
93
|
+
queriedMcp.current = true
|
|
94
|
+
void nativeBiz.executeCommand(source, 'mcp', '')
|
|
95
|
+
}, [
|
|
96
|
+
open,
|
|
97
|
+
tab,
|
|
98
|
+
commands.loading,
|
|
99
|
+
commands.value,
|
|
100
|
+
nativeState.ready,
|
|
101
|
+
nativeState.commandPending,
|
|
102
|
+
nativeState.snapshot.requests.length,
|
|
103
|
+
nativeBiz,
|
|
104
|
+
source
|
|
105
|
+
])
|
|
106
|
+
return { context, tools, commands }
|
|
75
107
|
}
|
|
@@ -54,7 +54,7 @@ export function L2WorkbenchModelContextDialog({
|
|
|
54
54
|
}: L2WorkbenchModelContextDialogProps): React.JSX.Element {
|
|
55
55
|
const { t, i18n } = useTranslation('workbench')
|
|
56
56
|
const [copied, setCopied] = useState(false)
|
|
57
|
-
const [view, setView] = useState<L2WorkbenchContextTab>('
|
|
57
|
+
const [view, setView] = useState<L2WorkbenchContextTab>('system')
|
|
58
58
|
const toast = useL4AppToast()
|
|
59
59
|
const { context, tools, commands } = useL2WorkbenchModelContext({
|
|
60
60
|
source,
|
|
@@ -62,7 +62,9 @@ export function L2WorkbenchModelContextDialog({
|
|
|
62
62
|
tab: view,
|
|
63
63
|
runtime,
|
|
64
64
|
getContext,
|
|
65
|
-
listCommands
|
|
65
|
+
listCommands,
|
|
66
|
+
nativeBiz,
|
|
67
|
+
nativeState: nativeUi.state
|
|
66
68
|
})
|
|
67
69
|
const data = view === 'system' ? context : view === 'tools' ? tools : commands
|
|
68
70
|
const question = nativeUi.state.snapshot.requests[0]?.id
|
|
@@ -101,8 +103,8 @@ export function L2WorkbenchModelContextDialog({
|
|
|
101
103
|
value={view}
|
|
102
104
|
onChange={setView}
|
|
103
105
|
options={[
|
|
104
|
-
{ value: 'tools', label: t('tools') },
|
|
105
106
|
{ value: 'system', label: t('systemPrompt') },
|
|
107
|
+
{ value: 'tools', label: t('tools') },
|
|
106
108
|
{ value: 'mcp', label: t('mcpSession', { defaultValue: '会话 MCP' }) }
|
|
107
109
|
]}
|
|
108
110
|
/>
|
|
@@ -172,13 +174,10 @@ export function L2WorkbenchModelContextDialog({
|
|
|
172
174
|
)}
|
|
173
175
|
state={nativeUi.state}
|
|
174
176
|
disabled={commandsDisabled}
|
|
175
|
-
onExecute={(args
|
|
177
|
+
onExecute={(args) => {
|
|
176
178
|
void nativeBiz.executeCommand(source, 'mcp', args)
|
|
177
|
-
if (login) {
|
|
178
|
-
nativeUi.setCollapsed(false)
|
|
179
|
-
onOpenChange(false)
|
|
180
|
-
}
|
|
181
179
|
}}
|
|
180
|
+
onRetryConnection={nativeUi.refresh}
|
|
182
181
|
/>
|
|
183
182
|
) : null}
|
|
184
183
|
</div>
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { LoaderCircleIcon, RefreshCwIcon } from 'lucide-react'
|
|
4
4
|
import { useTranslation } from 'react-i18next'
|
|
5
5
|
import { Button } from '@client/l4_foundation/ui/shadcn/button'
|
|
6
|
-
import { Input } from '@client/l4_foundation/ui/shadcn/input'
|
|
7
|
-
import { L4ChoiceGroup } from '@client/l4_foundation/ui/l4-choice-group'
|
|
8
6
|
import type { L2NativeUiState } from './l2-native-ui-biz'
|
|
9
7
|
import { L2NativeUiNotices } from './l2-native-ui-notices'
|
|
10
8
|
|
|
@@ -12,119 +10,88 @@ export function L2NativeUiMcp({
|
|
|
12
10
|
available,
|
|
13
11
|
state,
|
|
14
12
|
disabled,
|
|
15
|
-
onExecute
|
|
13
|
+
onExecute,
|
|
14
|
+
onRetryConnection
|
|
16
15
|
}: {
|
|
17
16
|
available: boolean
|
|
18
17
|
state: L2NativeUiState
|
|
19
18
|
disabled: boolean
|
|
20
|
-
onExecute: (args: string
|
|
19
|
+
onExecute: (args: string) => void
|
|
20
|
+
onRetryConnection: () => void
|
|
21
21
|
}): React.JSX.Element {
|
|
22
22
|
const { t } = useTranslation('workbench')
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const [confirmLogout, setConfirmLogout] = useState(false)
|
|
26
|
-
const needsName = action !== 'status'
|
|
27
|
-
const target = name.trim()
|
|
28
|
-
const invalidName = needsName && !/^[a-zA-Z0-9_-]+$/.test(target)
|
|
23
|
+
const commandDisabled =
|
|
24
|
+
!state.ready || state.commandPending !== null || state.snapshot.requests.length > 0
|
|
29
25
|
return (
|
|
30
26
|
<div className="space-y-4">
|
|
31
27
|
{available ? (
|
|
32
|
-
<
|
|
33
|
-
className="space-y-3"
|
|
34
|
-
onSubmit={(event) => {
|
|
35
|
-
event.preventDefault()
|
|
36
|
-
if (
|
|
37
|
-
disabled ||
|
|
38
|
-
!state.ready ||
|
|
39
|
-
state.commandPending ||
|
|
40
|
-
invalidName ||
|
|
41
|
-
(action === 'logout' && !confirmLogout)
|
|
42
|
-
)
|
|
43
|
-
return
|
|
44
|
-
onExecute(action === 'status' ? '' : `${action} ${target}`, action === 'login')
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
47
|
-
<L4ChoiceGroup
|
|
48
|
-
label={t('mcpSessionActions', { defaultValue: 'MCP 会话操作' })}
|
|
49
|
-
layout="segmented"
|
|
50
|
-
value={action}
|
|
51
|
-
onChange={(value) => {
|
|
52
|
-
setAction(value)
|
|
53
|
-
setConfirmLogout(false)
|
|
54
|
-
}}
|
|
55
|
-
options={[
|
|
56
|
-
{ value: 'status', label: t('mcpStatus', { defaultValue: '状态' }) },
|
|
57
|
-
{ value: 'login', label: t('mcpLogin', { defaultValue: '登录' }) },
|
|
58
|
-
{ value: 'reconnect', label: t('mcpReconnect', { defaultValue: '重连' }) },
|
|
59
|
-
{ value: 'logout', label: t('mcpLogout', { defaultValue: '退出登录' }) }
|
|
60
|
-
]}
|
|
61
|
-
/>
|
|
62
|
-
{needsName ? (
|
|
63
|
-
<label className="block space-y-1 text-sm font-medium">
|
|
64
|
-
<span>{t('mcpServerName', { defaultValue: '服务名称' })}</span>
|
|
65
|
-
<Input
|
|
66
|
-
value={name}
|
|
67
|
-
onChange={(event) => setName(event.target.value)}
|
|
68
|
-
placeholder={t('mcpServerNamePlaceholder', { defaultValue: '输入真实服务名称' })}
|
|
69
|
-
autoComplete="off"
|
|
70
|
-
maxLength={128}
|
|
71
|
-
/>
|
|
72
|
-
</label>
|
|
73
|
-
) : null}
|
|
74
|
-
{needsName && target && invalidName ? (
|
|
75
|
-
<p className="text-sm text-destructive" role="alert">
|
|
76
|
-
{t('mcpServerNameInvalid', {
|
|
77
|
-
defaultValue: '服务名称只能包含字母、数字、下划线和连字符。'
|
|
78
|
-
})}
|
|
79
|
-
</p>
|
|
80
|
-
) : null}
|
|
81
|
-
{action === 'logout' ? (
|
|
82
|
-
<label className="flex min-h-8 cursor-pointer items-start gap-2 text-sm">
|
|
83
|
-
<input
|
|
84
|
-
type="checkbox"
|
|
85
|
-
className="mt-1 size-4 accent-primary"
|
|
86
|
-
checked={confirmLogout}
|
|
87
|
-
onChange={(event) => setConfirmLogout(event.target.checked)}
|
|
88
|
-
/>
|
|
89
|
-
<span>
|
|
90
|
-
{t('mcpLogoutConfirm', {
|
|
91
|
-
defaultValue: '删除共享登录凭据,其他会话下次访问也可能需要重新登录。'
|
|
92
|
-
})}
|
|
93
|
-
</span>
|
|
94
|
-
</label>
|
|
95
|
-
) : null}
|
|
28
|
+
<section aria-label={t('mcpSessionActions')} className="space-y-3">
|
|
96
29
|
<div className="flex flex-wrap items-center gap-2">
|
|
97
30
|
<Button
|
|
98
|
-
type="
|
|
31
|
+
type="button"
|
|
32
|
+
variant="outline"
|
|
99
33
|
size="sm"
|
|
100
|
-
disabled={
|
|
101
|
-
|
|
102
|
-
!state.ready ||
|
|
103
|
-
state.commandPending !== null ||
|
|
104
|
-
invalidName ||
|
|
105
|
-
(action === 'logout' && !confirmLogout)
|
|
106
|
-
}
|
|
34
|
+
disabled={commandDisabled}
|
|
35
|
+
onClick={() => onExecute('')}
|
|
107
36
|
>
|
|
108
|
-
|
|
37
|
+
<RefreshCwIcon />
|
|
38
|
+
{t('mcpRefreshStatus')}
|
|
109
39
|
</Button>
|
|
110
|
-
{
|
|
111
|
-
<
|
|
112
|
-
{
|
|
113
|
-
|
|
114
|
-
|
|
40
|
+
{(['login', 'reconnect', 'logout'] as const).map((action) => (
|
|
41
|
+
<Button
|
|
42
|
+
key={action}
|
|
43
|
+
type="button"
|
|
44
|
+
variant="ghost"
|
|
45
|
+
size="sm"
|
|
46
|
+
disabled={commandDisabled || disabled}
|
|
47
|
+
onClick={() => onExecute(action)}
|
|
48
|
+
>
|
|
49
|
+
{t(
|
|
50
|
+
action === 'login'
|
|
51
|
+
? 'mcpLogin'
|
|
52
|
+
: action === 'reconnect'
|
|
53
|
+
? 'mcpReconnect'
|
|
54
|
+
: 'mcpLogout'
|
|
55
|
+
)}
|
|
56
|
+
</Button>
|
|
57
|
+
))}
|
|
115
58
|
</div>
|
|
116
|
-
|
|
59
|
+
{state.commandPending ? (
|
|
60
|
+
<p
|
|
61
|
+
role="status"
|
|
62
|
+
className="flex items-center gap-2 wrap-anywhere text-sm text-muted-foreground"
|
|
63
|
+
>
|
|
64
|
+
<LoaderCircleIcon className="size-4 shrink-0 animate-spin" />
|
|
65
|
+
{t('mcpExecuting')} {state.commandPending}
|
|
66
|
+
</p>
|
|
67
|
+
) : disabled ? (
|
|
68
|
+
<p className="text-sm text-muted-foreground">{t('mcpChangesWhenIdle')}</p>
|
|
69
|
+
) : null}
|
|
70
|
+
{state.error || !state.ready ? (
|
|
71
|
+
<div role="status" className="flex flex-wrap items-center gap-2 text-sm">
|
|
72
|
+
<span className="min-w-0 flex-1 wrap-anywhere">
|
|
73
|
+
{state.error ?? t('nativeUiDisconnected')}
|
|
74
|
+
</span>
|
|
75
|
+
<Button
|
|
76
|
+
type="button"
|
|
77
|
+
variant="outline"
|
|
78
|
+
size="sm"
|
|
79
|
+
disabled={state.refreshing}
|
|
80
|
+
onClick={onRetryConnection}
|
|
81
|
+
>
|
|
82
|
+
{t('nativeUiRetry')}
|
|
83
|
+
</Button>
|
|
84
|
+
</div>
|
|
85
|
+
) : null}
|
|
86
|
+
</section>
|
|
117
87
|
) : (
|
|
118
|
-
<p className="text-sm text-muted-foreground">
|
|
119
|
-
{t('mcpCommandUnavailable', { defaultValue: '当前会话未注册 MCP 命令。' })}
|
|
120
|
-
</p>
|
|
88
|
+
<p className="text-sm text-muted-foreground">{t('mcpCommandUnavailable')}</p>
|
|
121
89
|
)}
|
|
122
|
-
<
|
|
123
|
-
<h3 className="mb-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
</div>
|
|
90
|
+
<section className="border-t pt-4">
|
|
91
|
+
<h3 className="mb-1 text-sm font-medium">{t('nativeCommandOutput')}</h3>
|
|
92
|
+
<p className="mb-3 text-sm text-muted-foreground">{t('mcpNoticesDescription')}</p>
|
|
93
|
+
<L2NativeUiNotices notices={[...state.notices].reverse()} />
|
|
94
|
+
</section>
|
|
128
95
|
</div>
|
|
129
96
|
)
|
|
130
97
|
}
|
|
@@ -37,7 +37,7 @@ export function L2NativeUiNotices({
|
|
|
37
37
|
const { t } = useTranslation('workbench')
|
|
38
38
|
return (
|
|
39
39
|
<section
|
|
40
|
-
aria-label={t('nativeCommandOutput', { defaultValue: '
|
|
40
|
+
aria-label={t('nativeCommandOutput', { defaultValue: '会话通知' })}
|
|
41
41
|
className="space-y-3"
|
|
42
42
|
>
|
|
43
43
|
{notices.length ? (
|
|
@@ -51,7 +51,7 @@ export function L2NativeUiNotices({
|
|
|
51
51
|
))
|
|
52
52
|
) : (
|
|
53
53
|
<p className="py-6 text-sm text-muted-foreground">
|
|
54
|
-
{t('nativeCommandOutputEmpty', { defaultValue: '
|
|
54
|
+
{t('nativeCommandOutputEmpty', { defaultValue: '暂无会话通知' })}
|
|
55
55
|
</p>
|
|
56
56
|
)}
|
|
57
57
|
</section>
|
|
@@ -236,6 +236,8 @@ export function L2NativeUiQuestions({
|
|
|
236
236
|
const { t } = useTranslation('workbench')
|
|
237
237
|
const { state } = model
|
|
238
238
|
const request = state.snapshot.requests[0]
|
|
239
|
+
const signingIn =
|
|
240
|
+
state.commandPending === '/mcp login' || state.commandPending?.startsWith('/mcp login ')
|
|
239
241
|
return (
|
|
240
242
|
<>
|
|
241
243
|
{state.error || (!state.ready && request) ? (
|
|
@@ -286,14 +288,15 @@ export function L2NativeUiQuestions({
|
|
|
286
288
|
) : null}
|
|
287
289
|
{state.notices.length > 0 ? (
|
|
288
290
|
<Collapsible
|
|
289
|
-
|
|
291
|
+
key={request ? `notices:${request.id}` : signingIn ? 'mcp-login' : 'notices'}
|
|
292
|
+
defaultOpen={Boolean(request || signingIn)}
|
|
290
293
|
className="mb-2 rounded-lg border px-3 py-2"
|
|
291
294
|
data-testid="native-ui-output"
|
|
292
295
|
>
|
|
293
296
|
<div className="flex items-center gap-2">
|
|
294
297
|
<CollapsibleTrigger className="group flex min-h-7 min-w-0 flex-1 cursor-pointer items-center gap-2 text-left text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
295
298
|
<ChevronDownIcon className="size-3.5 shrink-0 transition-transform group-data-panel-open:rotate-180 motion-reduce:transition-none" />
|
|
296
|
-
{t('nativeCommandOutput', { defaultValue: '
|
|
299
|
+
{t('nativeCommandOutput', { defaultValue: '会话通知' })}
|
|
297
300
|
</CollapsibleTrigger>
|
|
298
301
|
<Button
|
|
299
302
|
type="button"
|
|
@@ -31,7 +31,6 @@ export function L4DesktopTitlebar(): React.JSX.Element {
|
|
|
31
31
|
const [state, setState] = useState<L4DesktopWindowState | null>(null)
|
|
32
32
|
const [title, setTitle] = useState('Pi Desk')
|
|
33
33
|
const [locale, setLocale] = useState<'zh-CN' | 'en'>('zh-CN')
|
|
34
|
-
const [focused, setFocused] = useState(true)
|
|
35
34
|
|
|
36
35
|
useEffect(() => {
|
|
37
36
|
const desktop = getL4DesktopWindow()
|
|
@@ -50,25 +49,21 @@ export function L4DesktopTitlebar(): React.JSX.Element {
|
|
|
50
49
|
void desktop
|
|
51
50
|
.invoke('state')
|
|
52
51
|
.then((next) => {
|
|
53
|
-
if (!disposed)
|
|
52
|
+
if (!disposed) {
|
|
53
|
+
setState((current) =>
|
|
54
|
+
current && current.maximized !== next.maximized ? next : current
|
|
55
|
+
)
|
|
56
|
+
}
|
|
54
57
|
})
|
|
55
58
|
.catch((error: unknown) => {
|
|
56
59
|
console.error('[desktop-window] 读取窗口状态失败', error)
|
|
57
60
|
})
|
|
58
61
|
})
|
|
59
62
|
}
|
|
60
|
-
const onFocus = (): void => {
|
|
61
|
-
setFocused(true)
|
|
62
|
-
}
|
|
63
|
-
const onBlur = (): void => {
|
|
64
|
-
setFocused(false)
|
|
65
|
-
}
|
|
66
63
|
const observer = new MutationObserver(readTitle)
|
|
67
64
|
observer.observe(document.head, { childList: true, subtree: true, characterData: true })
|
|
68
65
|
observer.observe(root, { attributes: true, attributeFilter: ['lang'] })
|
|
69
66
|
window.addEventListener('resize', refreshState)
|
|
70
|
-
window.addEventListener('focus', onFocus)
|
|
71
|
-
window.addEventListener('blur', onBlur)
|
|
72
67
|
|
|
73
68
|
// 按钮已挂载并绑定事件后才关闭系统装饰;旧页面不会调用此能力。
|
|
74
69
|
void desktop
|
|
@@ -76,7 +71,6 @@ export function L4DesktopTitlebar(): React.JSX.Element {
|
|
|
76
71
|
.then((next) => {
|
|
77
72
|
if (disposed) return
|
|
78
73
|
readTitle()
|
|
79
|
-
setFocused(document.hasFocus())
|
|
80
74
|
root.dataset.piDeskWindow = ''
|
|
81
75
|
setState(next)
|
|
82
76
|
})
|
|
@@ -90,8 +84,6 @@ export function L4DesktopTitlebar(): React.JSX.Element {
|
|
|
90
84
|
observer.disconnect()
|
|
91
85
|
cancelAnimationFrame(frame)
|
|
92
86
|
window.removeEventListener('resize', refreshState)
|
|
93
|
-
window.removeEventListener('focus', onFocus)
|
|
94
|
-
window.removeEventListener('blur', onBlur)
|
|
95
87
|
delete root.dataset.piDeskWindow
|
|
96
88
|
void desktop.invoke('detach').catch((error: unknown) => {
|
|
97
89
|
console.error('[desktop-window] 恢复系统标题栏失败', error)
|
|
@@ -104,7 +96,9 @@ export function L4DesktopTitlebar(): React.JSX.Element {
|
|
|
104
96
|
if (!desktop) return
|
|
105
97
|
try {
|
|
106
98
|
const next = await desktop.invoke(action)
|
|
107
|
-
if (adapter.current === desktop)
|
|
99
|
+
if (adapter.current === desktop) {
|
|
100
|
+
setState((current) => (current?.maximized === next.maximized ? current : next))
|
|
101
|
+
}
|
|
108
102
|
} catch (error) {
|
|
109
103
|
console.error(`[desktop-window] 窗口操作失败 action=${action}`, error)
|
|
110
104
|
try {
|
|
@@ -129,7 +123,6 @@ export function L4DesktopTitlebar(): React.JSX.Element {
|
|
|
129
123
|
<header
|
|
130
124
|
className="pi-desk-titlebar"
|
|
131
125
|
data-testid="desktop-titlebar"
|
|
132
|
-
data-focused={focused}
|
|
133
126
|
hidden={!state}
|
|
134
127
|
aria-label={text.window}
|
|
135
128
|
onMouseDown={onDrag}
|
|
@@ -17,19 +17,18 @@ export const L2_WORKBENCH_LOCALE_MESSAGES = {
|
|
|
17
17
|
modelChanging: 'Changing model…',
|
|
18
18
|
modelContextUnknown: 'Context unknown',
|
|
19
19
|
mcpSessionActions: 'Session MCP actions',
|
|
20
|
-
|
|
20
|
+
mcpRefreshStatus: 'Refresh status',
|
|
21
21
|
mcpLogin: 'Sign in',
|
|
22
22
|
mcpReconnect: 'Reconnect',
|
|
23
23
|
mcpLogout: 'Sign out',
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
mcpExecute: 'Run',
|
|
24
|
+
mcpChangesWhenIdle:
|
|
25
|
+
'Status is available while running. Sign in, reconnect or sign out when the task finishes.',
|
|
26
|
+
mcpNoticesDescription:
|
|
27
|
+
'MCP status and authorization links appear here, together with other plugin notifications.',
|
|
29
28
|
mcpExecuting: 'Running',
|
|
30
29
|
mcpCommandUnavailable: 'No MCP command is registered in this session.',
|
|
31
|
-
nativeCommandOutput: 'Session
|
|
32
|
-
nativeCommandOutputEmpty: 'No session
|
|
30
|
+
nativeCommandOutput: 'Session notifications',
|
|
31
|
+
nativeCommandOutputEmpty: 'No session notifications yet',
|
|
33
32
|
searchToolsSources: 'Search names, sources or descriptions',
|
|
34
33
|
toolFilter: 'Tool usage',
|
|
35
34
|
allTools: 'All tools',
|
|
@@ -472,19 +471,16 @@ export const L2_WORKBENCH_LOCALE_MESSAGES = {
|
|
|
472
471
|
modelChanging: '正在切换模型…',
|
|
473
472
|
modelContextUnknown: '上下文未知',
|
|
474
473
|
mcpSessionActions: 'MCP 会话操作',
|
|
475
|
-
|
|
474
|
+
mcpRefreshStatus: '刷新状态',
|
|
476
475
|
mcpLogin: '登录',
|
|
477
476
|
mcpReconnect: '重连',
|
|
478
477
|
mcpLogout: '退出登录',
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
mcpServerNameInvalid: '服务名称只能包含字母、数字、下划线和连字符。',
|
|
482
|
-
mcpLogoutConfirm: '删除共享登录凭据,其他会话下次访问也可能需要重新登录。',
|
|
483
|
-
mcpExecute: '执行',
|
|
478
|
+
mcpChangesWhenIdle: '运行中可查看状态;登录、重连和退出登录需等任务结束。',
|
|
479
|
+
mcpNoticesDescription: '这里显示 MCP 状态、授权链接及其他插件的会话通知。',
|
|
484
480
|
mcpExecuting: '正在执行',
|
|
485
481
|
mcpCommandUnavailable: '当前会话未注册 MCP 命令。',
|
|
486
|
-
nativeCommandOutput: '
|
|
487
|
-
nativeCommandOutputEmpty: '
|
|
482
|
+
nativeCommandOutput: '会话通知',
|
|
483
|
+
nativeCommandOutputEmpty: '暂无会话通知',
|
|
488
484
|
searchToolsSources: '搜索名称、来源或描述',
|
|
489
485
|
toolFilter: '工具使用方式',
|
|
490
486
|
allTools: '全部工具',
|
|
@@ -55,7 +55,6 @@ export const L4_HOST_ERROR_MESSAGES = {
|
|
|
55
55
|
capabilityRuleInvalid: 'Check the capability mode rules',
|
|
56
56
|
chatContextJsonInvalid: 'Invalid JSON for system context request',
|
|
57
57
|
chatContextInvalid: 'Invalid system context request',
|
|
58
|
-
chatContextBusy: 'Session is running. System context is temporarily unavailable',
|
|
59
58
|
chatContextFailed: 'Could not read system context',
|
|
60
59
|
chatMessageInvalid: 'Invalid message detail request',
|
|
61
60
|
chatMessageMissing: 'Chat message not found',
|
|
@@ -215,7 +214,6 @@ export const L4_HOST_ERROR_MESSAGES = {
|
|
|
215
214
|
capabilityRuleInvalid: '请检查模式规则',
|
|
216
215
|
chatContextJsonInvalid: '系统上下文请求 JSON 格式无效',
|
|
217
216
|
chatContextInvalid: '系统上下文请求参数无效',
|
|
218
|
-
chatContextBusy: '当前会话正在运行,暂时无法查看系统上下文',
|
|
219
217
|
chatContextFailed: '读取系统上下文失败',
|
|
220
218
|
chatMessageInvalid: '消息详情参数无效',
|
|
221
219
|
chatMessageMissing: '聊天消息不存在',
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
L2ChatSourceBindingError
|
|
7
7
|
} from '@server/l2_biz/work-session/l2-work-session-chat-runtime'
|
|
8
8
|
import { getL2WorkSessionManage } from '@server/l2_biz/work-session/l2-work-session-manage'
|
|
9
|
-
import { L4PiModelContextBusyError } from '@server/l4_foundation/pi/l4-pi-chat-worker'
|
|
10
9
|
import {
|
|
11
10
|
createL4ApiErrorResponse,
|
|
12
11
|
createL4ApiSuccessResponse
|
|
@@ -41,9 +40,6 @@ export async function POST(request: Request): Promise<Response> {
|
|
|
41
40
|
key: 'errors:chatSourceBusy'
|
|
42
41
|
})
|
|
43
42
|
}
|
|
44
|
-
if (error instanceof L4PiModelContextBusyError) {
|
|
45
|
-
return createL4ApiErrorResponse(409, error.message, { key: 'errors:chatContextBusy' })
|
|
46
|
-
}
|
|
47
43
|
|
|
48
44
|
console.error('[Pi Desk][ChatModelContextApi] 读取系统上下文失败', {
|
|
49
45
|
workId: input.data.source.workId,
|
|
@@ -127,13 +127,6 @@ export class L4PiDirectBashRunningError extends Error {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
export class L4PiModelContextBusyError extends Error {
|
|
131
|
-
constructor() {
|
|
132
|
-
super('当前会话正在运行,暂时无法查看系统上下文')
|
|
133
|
-
this.name = 'L4PiModelContextBusyError'
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
130
|
export interface L4PiChatContextUsage {
|
|
138
131
|
tokens: number | null
|
|
139
132
|
contextWindow: number
|
|
@@ -803,19 +796,7 @@ export class L4PiChatWorker {
|
|
|
803
796
|
}
|
|
804
797
|
|
|
805
798
|
async readModelContext(): Promise<L4PiModelContext> {
|
|
806
|
-
if (
|
|
807
|
-
this.startingAcceptedInput ||
|
|
808
|
-
this.acceptedInputs.length > 0 ||
|
|
809
|
-
this.activeValidation ||
|
|
810
|
-
this.pendingPrompt ||
|
|
811
|
-
this.activeCompactionTempId ||
|
|
812
|
-
this.observedRunPromises.size > 0
|
|
813
|
-
) {
|
|
814
|
-
throw new L4PiModelContextBusyError()
|
|
815
|
-
}
|
|
816
|
-
|
|
817
799
|
const session = await this.ensureSession()
|
|
818
|
-
if (!session.isIdle || session.isBashRunning) throw new L4PiModelContextBusyError()
|
|
819
800
|
|
|
820
801
|
const activeToolNames = new Set(session.getActiveToolNames())
|
|
821
802
|
const tools = session
|