@jetcrab/pi-desk 1.1.1 → 1.1.5
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/docs/pi-desk/README.md +47 -0
- package/docs/pi-desk/commands.md +76 -0
- package/docs/pi-desk/examples/local-application/browser/application.js +106 -0
- package/docs/pi-desk/examples/local-application/browser/entry.js +9 -0
- package/docs/pi-desk/examples/local-application/package.json +9 -0
- package/docs/pi-desk/examples/local-application/pi-desk.ts +11 -0
- package/docs/pi-desk/plugins.md +119 -0
- package/package.json +3 -2
- package/src/client/l2_biz/pi-settings/hooks/l2-use-pi-settings.ts +37 -21
- package/src/client/l2_biz/pi-settings/l2-pi-settings.tsx +37 -43
- package/src/client/l2_biz/pi-settings/views/l2-mcp-settings-editor.tsx +3 -1
- package/src/client/l2_biz/pi-settings/views/l2-mcp-settings-list.tsx +1 -1
- package/src/client/l2_biz/plugin-host/hooks/l2-use-plugin-catalog.ts +3 -1
- package/src/client/l2_biz/plugin-host/hooks/l2-use-plugin-download-source.ts +5 -1
- package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +57 -41
- package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +89 -116
- package/src/client/l2_biz/plugin-host/l2-plugin-management.tsx +492 -338
- package/src/client/l2_biz/plugin-host/l2-use-plugin-management.ts +72 -22
- package/src/client/l2_biz/plugin-host/views/l2-plugin-catalog-detail.tsx +104 -76
- package/src/client/l2_biz/plugin-host/views/l2-plugin-catalog-search.tsx +101 -56
- package/src/client/l2_biz/plugin-host/views/l2-plugin-direct-install.tsx +91 -59
- package/src/client/l2_biz/plugin-host/views/l2-plugin-download-source.tsx +36 -27
- package/src/client/l2_biz/plugin-host/views/l2-plugin-installed-list.tsx +218 -208
- package/src/client/l2_biz/plugin-host/views/l2-plugin-management-channel.tsx +12 -5
- package/src/client/l2_biz/plugin-host/views/l2-plugin-management-dialog.tsx +84 -106
- package/src/client/l2_biz/plugin-host/views/l2-plugin-readme.tsx +29 -15
- package/src/client/l2_biz/settings/hooks/l2-use-project-model-default-settings.ts +22 -11
- package/src/client/l2_biz/settings/l2-settings.tsx +6 -11
- package/src/client/l2_biz/settings/views/l2-project-model-default-settings.tsx +20 -14
- package/src/client/l2_biz/terminal/l2-terminal-workspace.tsx +35 -1
- package/src/client/l2_biz/workbench/file-workspace/l2-work-session-file-preview.tsx +6 -2
- package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +14 -1
- package/src/client/l2_biz/workbench/l2-workbench-branch-picker.tsx +33 -5
- package/src/client/l2_biz/workbench/l2-workbench-sidebar-layout.tsx +3 -1
- package/src/client/l2_biz/workbench/l2-workbench.tsx +20 -3
- package/src/client/l3_modules/conversation/l3-conversation-call-summary.tsx +0 -3
- package/src/client/l3_modules/project-files/l3-file-context-menu-content.tsx +1 -0
- package/src/client/l4_foundation/ui/l4-app-dialog.tsx +3 -1
- package/src/client/l4_foundation/ui/l4-image-viewer.tsx +21 -0
- package/src/client/l4_foundation/ui/shadcn/checkbox.tsx +27 -0
- package/src/client/l4_foundation/ui/shadcn/context-menu.tsx +8 -1
- package/src/client/l4_foundation/ui/shadcn/dropdown-menu.tsx +5 -0
- package/src/client/l4_foundation/ui/shadcn/popover.tsx +2 -0
- package/src/client/l4_foundation/ui/shadcn/select.tsx +2 -0
- package/src/client/l4_foundation/ui/shadcn/switch.tsx +31 -0
- package/src/client/l4_foundation/ui/shadcn/tabs.tsx +74 -0
- package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +52 -22
- package/src/common/l3_modules/conversation/l3-conversation-locale-messages.ts +2 -2
- package/src/common/l3_modules/conversation/l3-conversation-pidesk.ts +1 -1
- package/src/common/l4_foundation/plugin/l4-plugin-package.ts +1 -0
- package/src/server/l2_biz/plugin-management/l2-plugin-catalog.ts +16 -5
- package/src/server/l2_biz/plugin-management/l2-plugin-commands.ts +21 -1
- package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +21 -2
- package/src/server/l2_biz/plugin-management/l2-plugin-updates.ts +17 -3
- package/src/server/l4_foundation/pi/l4-pi-basic-session-services.ts +3 -3
- package/src/server/l4_foundation/pi/l4-pi-package-maintenance-runner.mts +4 -11
- package/src/server/l4_foundation/pi/l4-pi-plugin-registry-config.ts +52 -34
- package/src/server/l4_foundation/pi/l4-pi-plugin-registry-network.ts +5 -3
- package/src/server/l4_foundation/pi/l4-pi-plugin-registry.ts +23 -8
- package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +2 -5
- package/src/server/l4_foundation/pidesk/l4-pidesk-command.ts +8 -1
- package/src/server/l4_foundation/pidesk/l4-pidesk-runtime.ts +24 -38
- package/src/server/l4_foundation/pidesk/l4-pidesk-system-prompt.ts +11 -0
- package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
- package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -1
- package/temp/build/pi-desk/release/.next/required-server-files.js +2 -2
- package/temp/build/pi-desk/release/.next/required-server-files.json +2 -2
- 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 +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
- 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/health/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/login/page.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 +79 -79
- package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/25406.js +8 -77
- package/temp/build/pi-desk/release/.next/server/chunks/{81107.js → 35348.js} +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/35985.js +4 -4
- package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/47757.js +6 -75
- package/temp/build/pi-desk/release/.next/server/chunks/50520.js +4 -4
- package/temp/build/pi-desk/release/.next/server/chunks/58330.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/84231.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/89236.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/92386.js +5 -5
- package/temp/build/pi-desk/release/.next/server/chunks/94518.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/9874.js +1 -1
- package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-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/14197-3d3ddd426ac06c9c.js +3 -0
- package/temp/build/pi-desk/release/.next/static/chunks/34903-e4205c0becabea76.js +318 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{54206.7c0792096df8a5cf.js → 54206.82a63ab971911eda.js} +3 -3
- package/temp/build/pi-desk/release/.next/static/chunks/74132-876f256c7a6101af.js +13 -0
- package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +62 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{layout-7c5409cced9c2501.js → layout-4faf3a52d219296d.js} +2 -2
- package/temp/build/pi-desk/release/.next/static/chunks/app/login/{page-8c87fb9e6c918848.js → page-bf893718bb151f3c.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-c6c68b33156be10a.js +158 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{webpack-45424d28099bbefa.js → webpack-7bfbf627379fb4c1.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/css/3213ee28336bddc2.css +1 -0
- package/temp/build/pi-desk/release/.next/trace-build +1 -1
- package/src/server/l4_foundation/pidesk/l4-pidesk-skill-template.ts +0 -87
- package/temp/build/pi-desk/release/.next/static/chunks/27491-8b2d3e345b9142ea.js +0 -318
- package/temp/build/pi-desk/release/.next/static/chunks/7089-600a8f5593b7846c.js +0 -3
- package/temp/build/pi-desk/release/.next/static/chunks/74132-0884df6a6f11861a.js +0 -13
- package/temp/build/pi-desk/release/.next/static/chunks/78923-539a8b5f7af3c54a.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/96995-ae32167974365050.js +0 -62
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-39b146293b6eb488.js +0 -158
- package/temp/build/pi-desk/release/.next/static/css/56486914a8ba0a1b.css +0 -1
- /package/temp/build/pi-desk/release/.next/static/{STVrBHA3z9KPtvBE4WcE2 → 1LdtGYUckBhYZy0R-6yG0}/_buildManifest.js +0 -0
- /package/temp/build/pi-desk/release/.next/static/{STVrBHA3z9KPtvBE4WcE2 → 1LdtGYUckBhYZy0R-6yG0}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { PlusIcon } from 'lucide-react'
|
|
3
|
+
import { LoaderCircleIcon, PlusIcon } from 'lucide-react'
|
|
4
4
|
import { useId, useMemo } from 'react'
|
|
5
5
|
import type { BrowserBeforeLeaveHandler } from '@jetcrab/pi-desk-sdk/browser'
|
|
6
6
|
import { useL4AppSocket } from '@client/l4_foundation/realtime/app-socket/l4-app-socket'
|
|
@@ -43,14 +43,12 @@ export function L2PiSettings({
|
|
|
43
43
|
)
|
|
44
44
|
}, [projects, focusedCwd])
|
|
45
45
|
const scopeLabel =
|
|
46
|
-
state.
|
|
46
|
+
state.displayCwd === null
|
|
47
47
|
? t('全局')
|
|
48
|
-
: `${scopeOptions.find((project) => project.cwd === state.
|
|
48
|
+
: `${scopeOptions.find((project) => project.cwd === state.displayCwd)?.projectName ?? t('当前项目')} · ${state.displayCwd}`
|
|
49
49
|
const editing = Boolean(state.editor && state.mcp)
|
|
50
|
-
const empty = Boolean(
|
|
51
|
-
state.mcp && !state.loading && !state.loadError && !editing && state.rows.length === 0
|
|
52
|
-
)
|
|
53
50
|
const busy = state.pending || Boolean(state.check?.loading)
|
|
51
|
+
const disabled = busy || !state.ready
|
|
54
52
|
const selectedName = state.editor?.kind === 'manual' ? state.editor.draft.originalName : null
|
|
55
53
|
const addManually = (): void => {
|
|
56
54
|
void state.navigate(() => state.startManual(null))
|
|
@@ -64,35 +62,30 @@ export function L2PiSettings({
|
|
|
64
62
|
return (
|
|
65
63
|
<section aria-label="MCP" className="@container/mcp flex h-full min-h-0 min-w-0 flex-col">
|
|
66
64
|
{state.dialog}
|
|
67
|
-
<header
|
|
68
|
-
className=
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</
|
|
88
|
-
|
|
65
|
+
<header className="grid shrink-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-3 border-b p-4 @min-[40rem]/mcp:grid-cols-[auto_minmax(0,1fr)_auto]">
|
|
66
|
+
<h2 className="flex items-center gap-2 text-xl font-semibold">
|
|
67
|
+
MCP
|
|
68
|
+
<span className="inline-flex size-4">
|
|
69
|
+
{state.loading && (
|
|
70
|
+
<LoaderCircleIcon
|
|
71
|
+
role="status"
|
|
72
|
+
aria-label={t('正在读取配置…')}
|
|
73
|
+
className="size-4 animate-spin text-muted-foreground"
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</span>
|
|
77
|
+
</h2>
|
|
78
|
+
<div className="col-start-2 flex flex-wrap justify-end gap-2 @min-[40rem]/mcp:col-start-3 @min-[40rem]/mcp:self-end">
|
|
79
|
+
<Button disabled={disabled} onClick={addManually}>
|
|
80
|
+
<PlusIcon aria-hidden="true" />
|
|
81
|
+
{t('添加服务')}
|
|
82
|
+
</Button>
|
|
83
|
+
<Button variant="outline" disabled={disabled} onClick={importJson}>
|
|
84
|
+
{t('导入 JSON')}
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
89
87
|
{/* Select 尾随隐藏输入,space-y 会给选择框追加底部间距;并排工具栏中需清除。 */}
|
|
90
|
-
<div
|
|
91
|
-
className={cn(
|
|
92
|
-
'col-span-2 row-start-2 w-full @min-[40rem]/mcp:col-span-1 @min-[40rem]/mcp:col-start-2 @min-[40rem]/mcp:row-start-1 @min-[40rem]/mcp:max-w-80 @min-[40rem]/mcp:justify-self-end @min-[40rem]/mcp:[&_[data-slot=select-trigger]]:mb-0',
|
|
93
|
-
empty && '@min-[40rem]/mcp:w-56'
|
|
94
|
-
)}
|
|
95
|
-
>
|
|
88
|
+
<div className="col-span-2 row-start-2 w-full @min-[40rem]/mcp:col-span-1 @min-[40rem]/mcp:col-start-2 @min-[40rem]/mcp:row-start-1 @min-[40rem]/mcp:max-w-80 @min-[40rem]/mcp:justify-self-end @min-[40rem]/mcp:[&_[data-slot=select-trigger]]:mb-0">
|
|
96
89
|
<L2PiSettingsSelect
|
|
97
90
|
id={`${id}-scope`}
|
|
98
91
|
label={t('配置范围')}
|
|
@@ -106,8 +99,7 @@ export function L2PiSettings({
|
|
|
106
99
|
}))
|
|
107
100
|
]}
|
|
108
101
|
onChange={(value) => {
|
|
109
|
-
|
|
110
|
-
void state.navigate(() => state.setCwd(value || null))
|
|
102
|
+
void state.setCwd(value || null)
|
|
111
103
|
}}
|
|
112
104
|
/>
|
|
113
105
|
</div>
|
|
@@ -132,19 +124,20 @@ export function L2PiSettings({
|
|
|
132
124
|
))}
|
|
133
125
|
</div>
|
|
134
126
|
)}
|
|
135
|
-
{state.loadError
|
|
127
|
+
{state.loadError && (
|
|
136
128
|
<div className="space-y-3 p-4">
|
|
137
129
|
<L2PiSettingsError message={state.loadError} />
|
|
138
130
|
<Button variant="outline" onClick={state.retry} disabled={state.loading}>
|
|
139
131
|
{t('重试')}
|
|
140
132
|
</Button>
|
|
141
133
|
</div>
|
|
142
|
-
)
|
|
134
|
+
)}
|
|
135
|
+
{state.loading && !state.mcp ? (
|
|
143
136
|
<p role="status" className="p-4 text-sm text-muted-foreground">
|
|
144
137
|
{t('正在读取配置…')}
|
|
145
138
|
</p>
|
|
146
139
|
) : state.mcp ? (
|
|
147
|
-
<div className="flex min-h-0 min-w-0 flex-1">
|
|
140
|
+
<div aria-busy={state.loading} className="flex min-h-0 min-w-0 flex-1">
|
|
148
141
|
{state.editor?.kind !== 'json' && state.rows.length > 0 && (
|
|
149
142
|
<div
|
|
150
143
|
className={cn(
|
|
@@ -162,7 +155,7 @@ export function L2PiSettings({
|
|
|
162
155
|
<L2McpSettingsList
|
|
163
156
|
rows={state.rows}
|
|
164
157
|
selectedName={selectedName}
|
|
165
|
-
pending={
|
|
158
|
+
pending={disabled}
|
|
166
159
|
onSelect={(row) => {
|
|
167
160
|
if (selectedName === row.name) return
|
|
168
161
|
void state.navigate(() =>
|
|
@@ -179,11 +172,12 @@ export function L2PiSettings({
|
|
|
179
172
|
key={state.editor.kind === 'manual' ? (selectedName ?? 'new') : 'json'}
|
|
180
173
|
editor={state.editor}
|
|
181
174
|
scopeLabel={scopeLabel}
|
|
182
|
-
project={state.
|
|
175
|
+
project={state.displayCwd !== null}
|
|
183
176
|
projectTrusted={state.mcp.projectTrusted}
|
|
184
177
|
local={state.mcp.local}
|
|
185
178
|
inherited={state.mcp.inherited}
|
|
186
|
-
pending={
|
|
179
|
+
pending={disabled}
|
|
180
|
+
saving={state.pending}
|
|
187
181
|
error={state.error}
|
|
188
182
|
check={state.check?.name === selectedName ? state.check : null}
|
|
189
183
|
onDraft={state.updateDraft}
|
|
@@ -200,7 +194,7 @@ export function L2PiSettings({
|
|
|
200
194
|
onDismissCheck={state.dismissCheck}
|
|
201
195
|
/>
|
|
202
196
|
) : state.rows.length === 0 ? (
|
|
203
|
-
<L2McpSettingsEmpty pending={
|
|
197
|
+
<L2McpSettingsEmpty pending={disabled} onAdd={addManually} onImport={importJson} />
|
|
204
198
|
) : null}
|
|
205
199
|
</div>
|
|
206
200
|
) : null}
|
|
@@ -27,6 +27,7 @@ export function L2McpSettingsEditor({
|
|
|
27
27
|
local,
|
|
28
28
|
inherited,
|
|
29
29
|
pending,
|
|
30
|
+
saving,
|
|
30
31
|
error,
|
|
31
32
|
onDraft,
|
|
32
33
|
onJson,
|
|
@@ -45,6 +46,7 @@ export function L2McpSettingsEditor({
|
|
|
45
46
|
local: Record<string, L2McpServerConfig>
|
|
46
47
|
inherited: Record<string, L2McpServerConfig>
|
|
47
48
|
pending: boolean
|
|
49
|
+
saving: boolean
|
|
48
50
|
error: { field: string; message: string } | null
|
|
49
51
|
onDraft: (patch: Partial<L2McpDraft>) => void
|
|
50
52
|
onJson: (text: string) => void
|
|
@@ -402,7 +404,7 @@ export function L2McpSettingsEditor({
|
|
|
402
404
|
(!editor.preview || (collisions.length > 0 && !editor.overwrite)))
|
|
403
405
|
}
|
|
404
406
|
>
|
|
405
|
-
{t(check?.loading ? '正在测试连接…' :
|
|
407
|
+
{t(check?.loading ? '正在测试连接…' : saving ? '保存中…' : '保存')}
|
|
406
408
|
</Button>
|
|
407
409
|
</footer>
|
|
408
410
|
</form>
|
|
@@ -28,7 +28,7 @@ export function L2McpSettingsList({
|
|
|
28
28
|
disabled={pending}
|
|
29
29
|
onClick={() => onSelect(row)}
|
|
30
30
|
className={cn(
|
|
31
|
-
'flex w-full min-w-0 cursor-pointer items-center gap-3 p-4 text-left outline-none transition-colors hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:cursor-default
|
|
31
|
+
'flex w-full min-w-0 cursor-pointer items-center gap-3 p-4 text-left outline-none transition-colors hover:bg-accent focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:cursor-default',
|
|
32
32
|
selectedName === row.name && 'bg-accent'
|
|
33
33
|
)}
|
|
34
34
|
>
|
|
@@ -185,7 +185,6 @@ export function useL2PluginCatalog(
|
|
|
185
185
|
const epoch = ++detailEpoch.current
|
|
186
186
|
setDetailLoading(true)
|
|
187
187
|
setDetailError(null)
|
|
188
|
-
setDetail(null)
|
|
189
188
|
setInstallErrors((current) => {
|
|
190
189
|
const next = { ...current }
|
|
191
190
|
delete next[item.name]
|
|
@@ -203,6 +202,9 @@ export function useL2PluginCatalog(
|
|
|
203
202
|
}
|
|
204
203
|
const openDetail = (item: L2PluginCatalogItem): void => {
|
|
205
204
|
setSelection(item)
|
|
205
|
+
setDetail(null)
|
|
206
|
+
setDownloadMode('default')
|
|
207
|
+
setDownloadRegistry('')
|
|
206
208
|
void loadDetail(item)
|
|
207
209
|
}
|
|
208
210
|
const closeDetail = (): void => {
|
|
@@ -21,7 +21,10 @@ interface PluginDownloadSourceState {
|
|
|
21
21
|
retry: () => Promise<void>
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function useL2PluginDownloadSource(
|
|
24
|
+
export function useL2PluginDownloadSource(
|
|
25
|
+
biz: L2PluginManagementBiz,
|
|
26
|
+
onSaved: () => void
|
|
27
|
+
): PluginDownloadSourceState {
|
|
25
28
|
const { t } = useTranslation('pluginManagement')
|
|
26
29
|
const [settings, setSettings] = useState<L2PluginCatalogSettings | null>(null)
|
|
27
30
|
const [mode, setMode] = useState<L2PluginDownloadSource['mode']>('auto')
|
|
@@ -85,6 +88,7 @@ export function useL2PluginDownloadSource(biz: L2PluginManagementBiz): PluginDow
|
|
|
85
88
|
if (mounted.current) {
|
|
86
89
|
setSettings(next)
|
|
87
90
|
setMode(next.downloadSource.mode)
|
|
91
|
+
onSaved()
|
|
88
92
|
}
|
|
89
93
|
} catch (cause) {
|
|
90
94
|
failedRequest.current = 'save'
|
|
@@ -396,14 +396,20 @@ function ToolParameters({ tool }: { tool: L2PluginManagementToolDetail }): React
|
|
|
396
396
|
) : (
|
|
397
397
|
<p className="text-xs text-muted-foreground">{t('noArguments')}</p>
|
|
398
398
|
)}
|
|
399
|
-
<
|
|
400
|
-
<
|
|
399
|
+
<Collapsible className="mt-3">
|
|
400
|
+
<CollapsibleTrigger className="group flex min-h-8 cursor-pointer items-center gap-2 text-sm text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
401
|
+
<ChevronRightIcon
|
|
402
|
+
aria-hidden="true"
|
|
403
|
+
className="size-3.5 shrink-0 transition-transform group-aria-expanded:rotate-90"
|
|
404
|
+
/>
|
|
401
405
|
{t('rawSchema')}
|
|
402
|
-
</
|
|
403
|
-
<
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
406
|
+
</CollapsibleTrigger>
|
|
407
|
+
<CollapsibleContent keepMounted>
|
|
408
|
+
<pre className="mt-2 whitespace-pre-wrap break-words rounded-lg bg-muted p-3 font-mono text-sm leading-[1.6]">
|
|
409
|
+
{JSON.stringify(tool.parameters, null, 2)}
|
|
410
|
+
</pre>
|
|
411
|
+
</CollapsibleContent>
|
|
412
|
+
</Collapsible>
|
|
407
413
|
</div>
|
|
408
414
|
</div>
|
|
409
415
|
)
|
|
@@ -416,9 +422,13 @@ function ToolsSection({ detail }: { detail: L2PluginManagementDetail }): React.J
|
|
|
416
422
|
<Section title={t('toolCapabilities')} description={t('toolDescription')}>
|
|
417
423
|
<div className="space-y-2">
|
|
418
424
|
{detail.tools.map((tool, index) => (
|
|
419
|
-
<
|
|
420
|
-
<
|
|
421
|
-
<
|
|
425
|
+
<Collapsible key={`${tool.name}:${index}`} className="border-b py-3 last:border-b-0">
|
|
426
|
+
<CollapsibleTrigger className="group w-full cursor-pointer text-left text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
427
|
+
<ChevronRightIcon
|
|
428
|
+
aria-hidden="true"
|
|
429
|
+
className="mr-2 inline-block size-3.5 shrink-0 transition-transform group-aria-expanded:rotate-90"
|
|
430
|
+
/>
|
|
431
|
+
<span className="inline-flex min-w-0 flex-wrap items-center gap-2">
|
|
422
432
|
<code className="font-semibold">{tool.name}</code>
|
|
423
433
|
{tool.label && tool.label !== tool.name ? (
|
|
424
434
|
<span className="text-xs text-muted-foreground">{tool.label}</span>
|
|
@@ -429,34 +439,36 @@ function ToolsSection({ detail }: { detail: L2PluginManagementDetail }): React.J
|
|
|
429
439
|
{t('overridesBuiltin')}
|
|
430
440
|
</span>
|
|
431
441
|
) : null}
|
|
432
|
-
</
|
|
433
|
-
<
|
|
442
|
+
</span>
|
|
443
|
+
<span className="mt-1.5 block break-words text-sm leading-5 text-muted-foreground">
|
|
434
444
|
{tool.description || t('noDescription')}
|
|
435
|
-
</
|
|
436
|
-
</
|
|
437
|
-
<
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
<div className="
|
|
441
|
-
<
|
|
442
|
-
|
|
443
|
-
|
|
445
|
+
</span>
|
|
446
|
+
</CollapsibleTrigger>
|
|
447
|
+
<CollapsibleContent keepMounted>
|
|
448
|
+
<ToolParameters tool={tool} />
|
|
449
|
+
{tool.promptSnippet || tool.promptGuidelines.length > 0 ? (
|
|
450
|
+
<div className="mt-3 space-y-2">
|
|
451
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
452
|
+
<p className="text-xs font-medium">{t('systemPrompts')}</p>
|
|
453
|
+
{tool.state !== 'active' ? (
|
|
454
|
+
<span className="text-sm text-muted-foreground">{t('inactivePrompt')}</span>
|
|
455
|
+
) : null}
|
|
456
|
+
</div>
|
|
457
|
+
{tool.promptSnippet ? (
|
|
458
|
+
<PromptTextBlock title="Available tools" text={tool.promptSnippet} />
|
|
459
|
+
) : null}
|
|
460
|
+
{tool.promptGuidelines.length > 0 ? (
|
|
461
|
+
<PromptTextBlock
|
|
462
|
+
title={`Guidelines · ${tool.promptGuidelines.length}`}
|
|
463
|
+
text={tool.promptGuidelines
|
|
464
|
+
.map((guideline, guidelineIndex) => `${guidelineIndex + 1}. ${guideline}`)
|
|
465
|
+
.join('\n\n')}
|
|
466
|
+
/>
|
|
444
467
|
) : null}
|
|
445
468
|
</div>
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
{tool.promptGuidelines.length > 0 ? (
|
|
450
|
-
<PromptTextBlock
|
|
451
|
-
title={`Guidelines · ${tool.promptGuidelines.length}`}
|
|
452
|
-
text={tool.promptGuidelines
|
|
453
|
-
.map((guideline, guidelineIndex) => `${guidelineIndex + 1}. ${guideline}`)
|
|
454
|
-
.join('\n\n')}
|
|
455
|
-
/>
|
|
456
|
-
) : null}
|
|
457
|
-
</div>
|
|
458
|
-
) : null}
|
|
459
|
-
</details>
|
|
469
|
+
) : null}
|
|
470
|
+
</CollapsibleContent>
|
|
471
|
+
</Collapsible>
|
|
460
472
|
))}
|
|
461
473
|
</div>
|
|
462
474
|
</Section>
|
|
@@ -474,11 +486,15 @@ function ResourceContent({
|
|
|
474
486
|
}): React.JSX.Element {
|
|
475
487
|
const { t } = useTranslation('pluginManagement')
|
|
476
488
|
return (
|
|
477
|
-
<
|
|
478
|
-
<
|
|
489
|
+
<Collapsible className="mt-3">
|
|
490
|
+
<CollapsibleTrigger className="group flex min-h-8 w-full cursor-pointer items-center gap-2 py-2 text-left text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
491
|
+
<ChevronRightIcon
|
|
492
|
+
aria-hidden="true"
|
|
493
|
+
className="size-3.5 shrink-0 transition-transform group-aria-expanded:rotate-90"
|
|
494
|
+
/>
|
|
479
495
|
{title}
|
|
480
|
-
</
|
|
481
|
-
<
|
|
496
|
+
</CollapsibleTrigger>
|
|
497
|
+
<CollapsibleContent keepMounted className="border-t">
|
|
482
498
|
<div className="flex justify-end px-2 pt-1">
|
|
483
499
|
<CopyTextButton text={content} label={t('copyNamed', { name: title })} />
|
|
484
500
|
</div>
|
|
@@ -486,8 +502,8 @@ function ResourceContent({
|
|
|
486
502
|
{content}
|
|
487
503
|
</pre>
|
|
488
504
|
{truncated ? <p className="py-2 text-sm text-status-warning">{t('truncated')}</p> : null}
|
|
489
|
-
</
|
|
490
|
-
</
|
|
505
|
+
</CollapsibleContent>
|
|
506
|
+
</Collapsible>
|
|
491
507
|
)
|
|
492
508
|
}
|
|
493
509
|
|
|
@@ -6,6 +6,7 @@ import type { L2PluginManagementItem } from '@common/l2_biz/plugin/l2-plugin-man
|
|
|
6
6
|
import { selectL4LocalizedText } from '@common/l4_foundation/locale/l4-localized-text'
|
|
7
7
|
import { useL4Region } from '@client/l4_foundation/locale/l4-region-provider'
|
|
8
8
|
import { Button } from '@client/l4_foundation/ui/shadcn/button'
|
|
9
|
+
import { Switch } from '@client/l4_foundation/ui/shadcn/switch'
|
|
9
10
|
|
|
10
11
|
export type L2PluginManagementControlAction =
|
|
11
12
|
'reload' | 'update' | 'remove' | 'retry' | 'retry-browser' | 'enable' | 'disable'
|
|
@@ -14,7 +15,6 @@ interface L2PluginManagementControlsProps {
|
|
|
14
15
|
plugin: L2PluginManagementItem
|
|
15
16
|
displayName: string
|
|
16
17
|
browserError: string | null
|
|
17
|
-
requestError?: string | null
|
|
18
18
|
disabled: boolean
|
|
19
19
|
pending: boolean
|
|
20
20
|
browserRetryPending: boolean
|
|
@@ -25,142 +25,115 @@ export function L2PluginManagementControls({
|
|
|
25
25
|
plugin,
|
|
26
26
|
displayName,
|
|
27
27
|
browserError,
|
|
28
|
-
requestError,
|
|
29
28
|
disabled,
|
|
30
29
|
pending,
|
|
31
30
|
browserRetryPending,
|
|
32
31
|
onAction
|
|
33
32
|
}: L2PluginManagementControlsProps): React.JSX.Element {
|
|
34
33
|
const { t } = useTranslation('pluginManagement')
|
|
35
|
-
const { locale } = useL4Region()
|
|
36
34
|
const phase = plugin.operation?.phase
|
|
37
|
-
const unfinished = Boolean(plugin.operation && phase !== 'failed')
|
|
38
35
|
const enabled = plugin.status !== 'disabled'
|
|
39
|
-
const failed = plugin.status === 'failed' || browserError !== null
|
|
40
|
-
const sourceError = plugin.error ? selectL4LocalizedText(plugin.error.message, locale) : null
|
|
41
|
-
const status = unfinished
|
|
42
|
-
? t(`phase_${phase}`)
|
|
43
|
-
: failed
|
|
44
|
-
? t('notLoaded')
|
|
45
|
-
: enabled
|
|
46
|
-
? t('enabled')
|
|
47
|
-
: t('disabled')
|
|
48
36
|
return (
|
|
49
|
-
<div className="
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
37
|
+
<div className="flex shrink-0 flex-wrap items-center gap-3">
|
|
38
|
+
{pending ? (
|
|
39
|
+
<span
|
|
40
|
+
role="status"
|
|
41
|
+
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground"
|
|
42
|
+
>
|
|
43
|
+
{phase !== 'waiting' ? <LoaderCircleIcon className="size-4 animate-spin" /> : null}
|
|
44
|
+
{t(phase && phase !== 'failed' ? `phase_${phase}` : 'submitting')}
|
|
45
|
+
</span>
|
|
46
|
+
) : plugin.updateAvailable && plugin.kind !== 'extension' && phase !== 'failed' ? (
|
|
47
|
+
<Button size="sm" disabled={disabled} onClick={() => onAction('update')}>
|
|
48
|
+
{t('update')}
|
|
49
|
+
</Button>
|
|
50
|
+
) : null}
|
|
51
|
+
{phase === 'failed' ? (
|
|
52
|
+
<Button
|
|
53
|
+
size="sm"
|
|
54
|
+
variant="outline"
|
|
55
|
+
disabled={disabled || pending}
|
|
56
|
+
onClick={() => onAction('retry')}
|
|
57
|
+
>
|
|
58
|
+
{t('retry')}
|
|
59
|
+
</Button>
|
|
60
|
+
) : browserError && plugin.pluginName ? (
|
|
65
61
|
<Button
|
|
66
|
-
type="button"
|
|
67
62
|
size="sm"
|
|
68
63
|
variant="outline"
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
disabled={disabled || browserRetryPending}
|
|
65
|
+
onClick={() => onAction('retry-browser')}
|
|
66
|
+
>
|
|
67
|
+
{t('retryView')}
|
|
68
|
+
</Button>
|
|
69
|
+
) : null}
|
|
70
|
+
<label className="flex min-h-8 cursor-pointer items-center gap-2 text-sm text-muted-foreground">
|
|
71
|
+
<Switch
|
|
72
|
+
checked={enabled}
|
|
71
73
|
aria-label={t('togglePlugin', { name: displayName })}
|
|
72
74
|
disabled={disabled || pending}
|
|
73
|
-
|
|
75
|
+
onCheckedChange={(checked) => onAction(checked ? 'enable' : 'disable')}
|
|
76
|
+
/>
|
|
77
|
+
{t(enabled ? 'enabled' : 'disabled')}
|
|
78
|
+
</label>
|
|
79
|
+
{plugin.kind !== 'extension' ? (
|
|
80
|
+
<Button
|
|
81
|
+
size="icon-sm"
|
|
82
|
+
variant="ghost"
|
|
83
|
+
className="text-muted-foreground hover:text-destructive"
|
|
84
|
+
aria-label={t('removeNamed', { name: displayName })}
|
|
85
|
+
title={t('remove')}
|
|
86
|
+
disabled={disabled || pending}
|
|
87
|
+
onClick={() => onAction('remove')}
|
|
74
88
|
>
|
|
75
|
-
<
|
|
76
|
-
aria-hidden="true"
|
|
77
|
-
className={`flex h-4 w-7 items-center rounded-full p-0.5 ${enabled ? 'bg-primary' : 'bg-muted-foreground'}`}
|
|
78
|
-
>
|
|
79
|
-
<span
|
|
80
|
-
className={`size-3 rounded-full bg-primary-foreground transition-transform ${enabled ? 'translate-x-3' : ''}`}
|
|
81
|
-
/>
|
|
82
|
-
</span>
|
|
83
|
-
{t(enabled ? 'enabled' : 'disabled')}
|
|
89
|
+
<Trash2Icon />
|
|
84
90
|
</Button>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
</Button>
|
|
116
|
-
) : null}
|
|
117
|
-
{browserError && plugin.pluginName ? (
|
|
118
|
-
<Button
|
|
119
|
-
size="sm"
|
|
120
|
-
variant="outline"
|
|
121
|
-
disabled={disabled || browserRetryPending}
|
|
122
|
-
onClick={() => onAction('retry-browser')}
|
|
123
|
-
>
|
|
124
|
-
{t('retryView')}
|
|
125
|
-
</Button>
|
|
126
|
-
) : null}
|
|
127
|
-
<details className="text-sm">
|
|
128
|
-
<summary className="cursor-pointer rounded-lg px-2 py-1.5 outline-none hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring">
|
|
129
|
-
{t('more')}
|
|
130
|
-
</summary>
|
|
131
|
-
<Button
|
|
132
|
-
size="sm"
|
|
133
|
-
variant="ghost"
|
|
134
|
-
className="mt-1"
|
|
135
|
-
disabled={disabled || (pending && phase !== 'waiting')}
|
|
136
|
-
aria-label={t('reloadSource', { name: displayName })}
|
|
137
|
-
onClick={() => onAction('reload')}
|
|
138
|
-
>
|
|
139
|
-
{t('reloadOne')}
|
|
140
|
-
</Button>
|
|
141
|
-
</details>
|
|
142
|
-
</div>
|
|
143
|
-
{plugin.operation?.message ? (
|
|
91
|
+
) : null}
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function L2PluginManagementFeedback({
|
|
97
|
+
plugin,
|
|
98
|
+
requestError,
|
|
99
|
+
browserError
|
|
100
|
+
}: {
|
|
101
|
+
plugin: L2PluginManagementItem
|
|
102
|
+
requestError?: string
|
|
103
|
+
browserError: string | null
|
|
104
|
+
}): React.JSX.Element | null {
|
|
105
|
+
const { t } = useTranslation('pluginManagement')
|
|
106
|
+
const { locale } = useL4Region()
|
|
107
|
+
const phase = plugin.operation?.phase
|
|
108
|
+
const messages = [
|
|
109
|
+
requestError,
|
|
110
|
+
phase === 'failed' ? plugin.operation?.message : null,
|
|
111
|
+
plugin.error ? selectL4LocalizedText(plugin.error.message, locale) : null,
|
|
112
|
+
browserError,
|
|
113
|
+
plugin.updateError ? t('itemCheckFailed', { error: plugin.updateError }) : null
|
|
114
|
+
].filter(
|
|
115
|
+
(message, index, all): message is string => Boolean(message) && all.indexOf(message) === index
|
|
116
|
+
)
|
|
117
|
+
if (!messages.length && phase !== 'waiting' && plugin.status !== 'failed') return null
|
|
118
|
+
return (
|
|
119
|
+
<div className="space-y-1">
|
|
120
|
+
{messages.map((message) => (
|
|
144
121
|
<p
|
|
145
|
-
|
|
122
|
+
key={message}
|
|
123
|
+
role="alert"
|
|
124
|
+
className="whitespace-pre-wrap text-sm text-destructive [overflow-wrap:anywhere]"
|
|
146
125
|
>
|
|
126
|
+
{message}
|
|
127
|
+
</p>
|
|
128
|
+
))}
|
|
129
|
+
{!messages.length && plugin.status === 'failed' ? (
|
|
130
|
+
<p className="text-sm text-destructive">{t('notLoaded')}</p>
|
|
131
|
+
) : null}
|
|
132
|
+
{phase === 'waiting' && plugin.operation?.message ? (
|
|
133
|
+
<p role="status" className="text-sm text-muted-foreground [overflow-wrap:anywhere]">
|
|
147
134
|
{plugin.operation.message}
|
|
148
135
|
</p>
|
|
149
136
|
) : null}
|
|
150
|
-
{[requestError, sourceError, browserError]
|
|
151
|
-
.filter(
|
|
152
|
-
(message, index, all) =>
|
|
153
|
-
message && all.indexOf(message) === index && message !== plugin.operation?.message
|
|
154
|
-
)
|
|
155
|
-
.map((message) => (
|
|
156
|
-
<p
|
|
157
|
-
key={message}
|
|
158
|
-
role="alert"
|
|
159
|
-
className="whitespace-pre-wrap text-sm text-destructive [overflow-wrap:anywhere]"
|
|
160
|
-
>
|
|
161
|
-
{message}
|
|
162
|
-
</p>
|
|
163
|
-
))}
|
|
164
137
|
</div>
|
|
165
138
|
)
|
|
166
139
|
}
|