@jetcrab/pi-desk 1.1.1-dev → 1.1.5-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/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/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/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-28e1d77d6b884657.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-a85b2f17c68cff60.js +0 -158
- package/temp/build/pi-desk/release/.next/static/css/56486914a8ba0a1b.css +0 -1
- /package/temp/build/pi-desk/release/.next/static/{AIPx4Oo1zqM8gixtv1I69 → IgHJ40fniC07-ioYUKobL}/_buildManifest.js +0 -0
- /package/temp/build/pi-desk/release/.next/static/{AIPx4Oo1zqM8gixtv1I69 → IgHJ40fniC07-ioYUKobL}/_ssgManifest.js +0 -0
|
@@ -18,137 +18,115 @@ import {
|
|
|
18
18
|
} from '../l2-use-plugin-management'
|
|
19
19
|
|
|
20
20
|
interface ManagementDialogProps {
|
|
21
|
-
open: boolean
|
|
22
|
-
title: string
|
|
23
21
|
confirmation: PluginManagementAction | null
|
|
24
22
|
onClose: () => void
|
|
25
23
|
onConfirm: (action: PluginManagementAction) => void
|
|
26
|
-
children: React.ReactNode
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export function L2PluginManagementDialog({
|
|
30
|
-
open,
|
|
31
|
-
title,
|
|
32
27
|
confirmation,
|
|
33
28
|
onClose,
|
|
34
|
-
onConfirm
|
|
35
|
-
children
|
|
29
|
+
onConfirm
|
|
36
30
|
}: ManagementDialogProps): React.JSX.Element {
|
|
37
31
|
const { t } = useTranslation('pluginManagement')
|
|
38
32
|
const action = confirmation ? t(pluginManagementActionLabel(confirmation)) : null
|
|
33
|
+
const removing =
|
|
34
|
+
confirmation?.kind === 'del' ||
|
|
35
|
+
(confirmation?.kind === 'batch' && confirmation.input.action === 'del')
|
|
36
|
+
const installing =
|
|
37
|
+
confirmation?.kind === 'add' ||
|
|
38
|
+
(confirmation?.kind === 'batch' && confirmation.input.action === 'add')
|
|
39
39
|
return (
|
|
40
40
|
<L4AppDialogRoot
|
|
41
|
-
open={
|
|
41
|
+
open={Boolean(confirmation)}
|
|
42
42
|
onOpenChange={(value) => {
|
|
43
43
|
if (!value) onClose()
|
|
44
44
|
}}
|
|
45
45
|
>
|
|
46
|
-
<L4AppDialogContent
|
|
47
|
-
finalFocus
|
|
48
|
-
className={confirmation ? 'max-w-lg' : 'h-[min(85dvh,52rem)] max-w-3xl'}
|
|
49
|
-
>
|
|
46
|
+
<L4AppDialogContent className="z-[150] max-w-lg" backdropClassName="z-[140]">
|
|
50
47
|
<L4AppDialogHeader className="p-4 pr-12">
|
|
51
|
-
<L4AppDialogTitle
|
|
52
|
-
{confirmation ? t('confirmAction', { action }) : title}
|
|
53
|
-
</L4AppDialogTitle>
|
|
48
|
+
<L4AppDialogTitle>{t('confirmAction', { action })}</L4AppDialogTitle>
|
|
54
49
|
<L4AppDialogDescription>
|
|
55
|
-
{
|
|
56
|
-
?
|
|
57
|
-
|
|
58
|
-
? t('
|
|
59
|
-
: confirmation
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
: 'restartDescription'
|
|
67
|
-
)
|
|
68
|
-
: t('confirmChange')
|
|
69
|
-
: t('pluginDetails')}
|
|
50
|
+
{removing
|
|
51
|
+
? t('removeDescription')
|
|
52
|
+
: installing
|
|
53
|
+
? t('thirdPartyDescription')
|
|
54
|
+
: confirmation?.kind === 'reload'
|
|
55
|
+
? t(
|
|
56
|
+
confirmation.mode === 'normal'
|
|
57
|
+
? 'restartNormalDescription'
|
|
58
|
+
: 'restartDescription'
|
|
59
|
+
)
|
|
60
|
+
: t('confirmChange')}
|
|
70
61
|
</L4AppDialogDescription>
|
|
71
62
|
</L4AppDialogHeader>
|
|
72
63
|
<L4AppDialogBody className="min-h-0">
|
|
73
64
|
<div className="min-w-0 p-4 pt-0">
|
|
74
|
-
{confirmation ? (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
) : confirmation.kind !== 'reload' ? (
|
|
124
|
-
<p className="text-sm font-medium [overflow-wrap:anywhere]">
|
|
125
|
-
{confirmation.source}
|
|
126
|
-
</p>
|
|
127
|
-
) : null
|
|
128
|
-
) : (
|
|
129
|
-
children
|
|
130
|
-
)}
|
|
65
|
+
{confirmation?.kind === 'batch' ? (
|
|
66
|
+
<ul className="space-y-2 text-sm">
|
|
67
|
+
{(confirmation.input.action === 'add'
|
|
68
|
+
? confirmation.input.items.map((item) => item.source)
|
|
69
|
+
: confirmation.input.sources
|
|
70
|
+
).map((source) => (
|
|
71
|
+
<li key={source} className="font-medium [overflow-wrap:anywhere]">
|
|
72
|
+
{source.startsWith('npm:') ? (pluginNpmSpec(source)?.name ?? source) : source}
|
|
73
|
+
</li>
|
|
74
|
+
))}
|
|
75
|
+
</ul>
|
|
76
|
+
) : confirmation?.kind === 'add' ? (
|
|
77
|
+
<dl className="space-y-3 text-sm">
|
|
78
|
+
<div>
|
|
79
|
+
<dt className="text-muted-foreground">{t('packageName')}</dt>
|
|
80
|
+
<dd className="mt-1 font-medium [overflow-wrap:anywhere]">
|
|
81
|
+
{confirmation.package?.name ?? confirmation.input.source}
|
|
82
|
+
</dd>
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<dt className="text-muted-foreground">{t('version')}</dt>
|
|
86
|
+
<dd className="[overflow-wrap:anywhere]">
|
|
87
|
+
{confirmation.package?.version ??
|
|
88
|
+
pluginNpmSpec(confirmation.input.source)?.version ??
|
|
89
|
+
t('fromSource')}
|
|
90
|
+
</dd>
|
|
91
|
+
</div>
|
|
92
|
+
<div>
|
|
93
|
+
<dt className="text-muted-foreground">{t('publisher')}</dt>
|
|
94
|
+
<dd className="[overflow-wrap:anywhere]">
|
|
95
|
+
{confirmation.package?.publisher ?? t('unknownPublisher')}
|
|
96
|
+
</dd>
|
|
97
|
+
</div>
|
|
98
|
+
<div>
|
|
99
|
+
<dt className="text-muted-foreground">{t('source')}</dt>
|
|
100
|
+
<dd className="[overflow-wrap:anywhere]">
|
|
101
|
+
{confirmation.input.registry ??
|
|
102
|
+
confirmation.package?.registry ??
|
|
103
|
+
confirmation.input.source}
|
|
104
|
+
</dd>
|
|
105
|
+
</div>
|
|
106
|
+
</dl>
|
|
107
|
+
) : confirmation && confirmation.kind !== 'reload' ? (
|
|
108
|
+
<p className="text-sm font-medium [overflow-wrap:anywhere]">
|
|
109
|
+
{confirmation.source.startsWith('npm:')
|
|
110
|
+
? (pluginNpmSpec(confirmation.source)?.name ?? confirmation.source)
|
|
111
|
+
: confirmation.source}
|
|
112
|
+
</p>
|
|
113
|
+
) : null}
|
|
131
114
|
</div>
|
|
132
115
|
</L4AppDialogBody>
|
|
133
|
-
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
>
|
|
148
|
-
{action}
|
|
149
|
-
</Button>
|
|
150
|
-
</L4AppDialogFooter>
|
|
151
|
-
) : null}
|
|
116
|
+
<L4AppDialogFooter>
|
|
117
|
+
<Button size="sm" variant="outline" onClick={onClose}>
|
|
118
|
+
{t('cancel')}
|
|
119
|
+
</Button>
|
|
120
|
+
<Button
|
|
121
|
+
size="sm"
|
|
122
|
+
variant={removing ? 'destructive' : 'default'}
|
|
123
|
+
onClick={() => {
|
|
124
|
+
if (confirmation) onConfirm(confirmation)
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
{action}
|
|
128
|
+
</Button>
|
|
129
|
+
</L4AppDialogFooter>
|
|
152
130
|
</L4AppDialogContent>
|
|
153
131
|
</L4AppDialogRoot>
|
|
154
132
|
)
|
|
@@ -8,6 +8,13 @@ import {
|
|
|
8
8
|
} from '@common/l4_foundation/locale/l4-localized-text'
|
|
9
9
|
import { useL4Region } from '@client/l4_foundation/locale/l4-region-provider'
|
|
10
10
|
import { MessageResponse } from '@client/l4_foundation/ui/ai-elements/message'
|
|
11
|
+
import {
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue
|
|
17
|
+
} from '@client/l4_foundation/ui/shadcn/select'
|
|
11
18
|
|
|
12
19
|
export function L2PluginReadme({ readme }: { readme: L4LocalizedText | null }): React.JSX.Element {
|
|
13
20
|
const { t } = useTranslation('pluginManagement')
|
|
@@ -26,21 +33,28 @@ export function L2PluginReadme({ readme }: { readme: L4LocalizedText | null }):
|
|
|
26
33
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
27
34
|
<h3 className="text-sm font-semibold">{t('readme')}</h3>
|
|
28
35
|
{languages.length > 0 ? (
|
|
29
|
-
<
|
|
30
|
-
{t('readmeLanguage')}
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
<div className="flex items-center gap-2 text-sm">
|
|
37
|
+
<span>{t('readmeLanguage')}</span>
|
|
38
|
+
<Select value={selected ?? ''} onValueChange={(value) => setLanguage(value || null)}>
|
|
39
|
+
<SelectTrigger aria-label={t('readmeLanguage')}>
|
|
40
|
+
<SelectValue>
|
|
41
|
+
{selected
|
|
42
|
+
? selected.startsWith('zh')
|
|
43
|
+
? '中文'
|
|
44
|
+
: 'English'
|
|
45
|
+
: t('defaultLanguage')}
|
|
46
|
+
</SelectValue>
|
|
47
|
+
</SelectTrigger>
|
|
48
|
+
<SelectContent positionerClassName="z-[160]">
|
|
49
|
+
<SelectItem value="">{t('defaultLanguage')}</SelectItem>
|
|
50
|
+
{languages.map((key) => (
|
|
51
|
+
<SelectItem key={key} value={key}>
|
|
52
|
+
{key.startsWith('zh') ? '中文' : 'English'}
|
|
53
|
+
</SelectItem>
|
|
54
|
+
))}
|
|
55
|
+
</SelectContent>
|
|
56
|
+
</Select>
|
|
57
|
+
</div>
|
|
44
58
|
) : null}
|
|
45
59
|
</div>
|
|
46
60
|
{content ? (
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
interface ProjectModelDefaultSettingsState {
|
|
25
25
|
directories: L2PiDirectoryListResponse['directories']
|
|
26
26
|
cwd: string
|
|
27
|
+
loadedCwd: string | null
|
|
27
28
|
models: L2ModelOption[]
|
|
28
29
|
selection: L2ModelSelection | null
|
|
29
30
|
fixed: boolean
|
|
@@ -66,9 +67,11 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
66
67
|
const [projectError, setProjectError] = useState<string | null>(null)
|
|
67
68
|
const [directoryAttempt, setDirectoryAttempt] = useState(0)
|
|
68
69
|
const [projectAttempt, setProjectAttempt] = useState(0)
|
|
69
|
-
const projectReady =
|
|
70
|
+
const projectReady =
|
|
71
|
+
loadedCwd === cwd && projectError === null && !projectLoading && !projectRefreshing
|
|
70
72
|
const projectDraftDirtyRef = useRef(false)
|
|
71
73
|
const projectCwdRef = useRef('')
|
|
74
|
+
const projectRequestRef = useRef(0)
|
|
72
75
|
const markDraftDirty = useCallback(
|
|
73
76
|
(dirty: boolean): void => {
|
|
74
77
|
projectDraftDirtyRef.current = dirty
|
|
@@ -137,12 +140,16 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
137
140
|
|
|
138
141
|
useEffect(() => {
|
|
139
142
|
let disposed = false
|
|
143
|
+
const request = ++projectRequestRef.current
|
|
144
|
+
function isCurrent(): boolean {
|
|
145
|
+
return !disposed && request === projectRequestRef.current
|
|
146
|
+
}
|
|
140
147
|
const projectChanged = projectCwdRef.current !== cwd
|
|
141
148
|
projectCwdRef.current = cwd
|
|
142
149
|
if (projectChanged) markDraftDirty(false)
|
|
143
150
|
if (!cwd) {
|
|
144
151
|
queueMicrotask((): void => {
|
|
145
|
-
if (
|
|
152
|
+
if (!isCurrent()) return
|
|
146
153
|
setModels([])
|
|
147
154
|
setSelection(null)
|
|
148
155
|
setFixed(false)
|
|
@@ -158,18 +165,13 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
158
165
|
|
|
159
166
|
const cached = readL2ProjectModelDefaultCache(cwd)
|
|
160
167
|
queueMicrotask((): void => {
|
|
161
|
-
if (
|
|
168
|
+
if (!isCurrent()) return
|
|
162
169
|
setProjectError(null)
|
|
163
170
|
// 同一项目重试或后台刷新时,缓存和远端结果都不能替换未保存草稿。
|
|
164
171
|
if (!projectDraftDirtyRef.current) {
|
|
165
172
|
if (cached) {
|
|
166
173
|
applyProjectResult(cached)
|
|
167
174
|
setLoadedCwd(cwd)
|
|
168
|
-
} else {
|
|
169
|
-
setModels([])
|
|
170
|
-
setSelection(null)
|
|
171
|
-
setFixed(false)
|
|
172
|
-
setLoadedCwd(null)
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
setProjectLoading(!cached && !projectDraftDirtyRef.current)
|
|
@@ -179,17 +181,17 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
179
181
|
void (async (): Promise<void> => {
|
|
180
182
|
try {
|
|
181
183
|
const result = await biz.getProjectDefault(cwd)
|
|
182
|
-
if (
|
|
184
|
+
if (!isCurrent()) return
|
|
183
185
|
if (!projectDraftDirtyRef.current) applyProjectResult(result)
|
|
184
186
|
setLoadedCwd(cwd)
|
|
185
187
|
} catch (error) {
|
|
186
|
-
if (
|
|
188
|
+
if (isCurrent()) {
|
|
187
189
|
const message = error instanceof Error ? error.message : t('projectDefaultFailed')
|
|
188
190
|
console.warn('[Pi Desk][ProjectModelDefault] 读取项目模型配置失败', { cwd, message })
|
|
189
191
|
setProjectError(message)
|
|
190
192
|
}
|
|
191
193
|
} finally {
|
|
192
|
-
if (
|
|
194
|
+
if (isCurrent()) {
|
|
193
195
|
setProjectLoading(false)
|
|
194
196
|
setProjectRefreshing(false)
|
|
195
197
|
}
|
|
@@ -213,16 +215,20 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
213
215
|
) {
|
|
214
216
|
return false
|
|
215
217
|
}
|
|
218
|
+
projectRequestRef.current += 1
|
|
219
|
+
setProjectRefreshing(true)
|
|
216
220
|
setCwd(nextCwd)
|
|
217
221
|
return true
|
|
218
222
|
}
|
|
219
223
|
|
|
220
224
|
function changeFixed(value: boolean): void {
|
|
225
|
+
if (!projectReady || saving) return
|
|
221
226
|
markDraftDirty(true)
|
|
222
227
|
setFixed(value)
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
function selectModel(model: L2ModelOption): void {
|
|
231
|
+
if (!projectReady || saving) return
|
|
226
232
|
markDraftDirty(true)
|
|
227
233
|
setSelection({
|
|
228
234
|
provider: model.provider,
|
|
@@ -232,6 +238,7 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
function selectThinking(value: string): void {
|
|
241
|
+
if (!projectReady || saving) return
|
|
235
242
|
const model = models.find(
|
|
236
243
|
(item): boolean => item.provider === selection?.provider && item.modelId === selection.modelId
|
|
237
244
|
)
|
|
@@ -242,6 +249,7 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
242
249
|
}
|
|
243
250
|
|
|
244
251
|
function applyPreset(preset: L2ModelPreset): void {
|
|
252
|
+
if (!projectReady || saving) return
|
|
245
253
|
markDraftDirty(true)
|
|
246
254
|
setSelection({
|
|
247
255
|
provider: preset.provider,
|
|
@@ -255,6 +263,8 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
255
263
|
}
|
|
256
264
|
|
|
257
265
|
function retryProject(): void {
|
|
266
|
+
projectRequestRef.current += 1
|
|
267
|
+
setProjectRefreshing(true)
|
|
258
268
|
setProjectAttempt((attempt): number => attempt + 1)
|
|
259
269
|
}
|
|
260
270
|
|
|
@@ -289,6 +299,7 @@ export function useL2ProjectModelDefaultSettings(
|
|
|
289
299
|
return {
|
|
290
300
|
directories,
|
|
291
301
|
cwd,
|
|
302
|
+
loadedCwd,
|
|
292
303
|
models,
|
|
293
304
|
selection,
|
|
294
305
|
fixed,
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
import { cn } from '@client/l4_foundation/lib/l4-utils'
|
|
40
40
|
import { Button } from '@client/l4_foundation/ui/shadcn/button'
|
|
41
41
|
import { L4BrandIcon } from '@client/l4_foundation/ui/l4-brand-icon'
|
|
42
|
-
import { useL4WindowReveal } from '@client/l4_foundation/ui/l4-window-reveal'
|
|
43
42
|
import {
|
|
44
43
|
Collapsible,
|
|
45
44
|
CollapsibleContent,
|
|
@@ -241,8 +240,6 @@ export function L2Settings({
|
|
|
241
240
|
activeSection.startsWith('plugin:') && !activePluginPage ? 'appearance' : activeSection
|
|
242
241
|
const activeBuiltinSection = SETTINGS_SECTIONS.find((section) => section.id === effectiveSection)
|
|
243
242
|
const ActiveBuiltinIcon = activeBuiltinSection?.icon
|
|
244
|
-
const contentRef = useRef<HTMLDivElement>(null)
|
|
245
|
-
useL4WindowReveal(contentRef, open && !activePluginPage ? effectiveSection : null)
|
|
246
243
|
|
|
247
244
|
const runAfterGuard = useCallback(async (action: () => void): Promise<void> => {
|
|
248
245
|
if (guardPendingRef.current) return
|
|
@@ -475,7 +472,7 @@ export function L2Settings({
|
|
|
475
472
|
/>
|
|
476
473
|
</div>
|
|
477
474
|
) : effectiveSection === 'skills' ? (
|
|
478
|
-
<div
|
|
475
|
+
<div className="min-h-0 flex-1 overflow-hidden">
|
|
479
476
|
{open && (
|
|
480
477
|
<L2SkillsSettings
|
|
481
478
|
projects={skillProjects}
|
|
@@ -485,7 +482,7 @@ export function L2Settings({
|
|
|
485
482
|
)}
|
|
486
483
|
</div>
|
|
487
484
|
) : effectiveSection === 'mcp-settings' ? (
|
|
488
|
-
<div
|
|
485
|
+
<div className="min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
489
486
|
{open && (
|
|
490
487
|
<ExternalSettingsSection
|
|
491
488
|
render={renderMcpSettings}
|
|
@@ -494,7 +491,7 @@ export function L2Settings({
|
|
|
494
491
|
)}
|
|
495
492
|
</div>
|
|
496
493
|
) : effectiveSection === 'capability-modes' ? (
|
|
497
|
-
<div
|
|
494
|
+
<div className="min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
498
495
|
{open && (
|
|
499
496
|
<ExternalSettingsSection
|
|
500
497
|
render={renderCapabilityModesSettings}
|
|
@@ -503,7 +500,7 @@ export function L2Settings({
|
|
|
503
500
|
)}
|
|
504
501
|
</div>
|
|
505
502
|
) : effectiveSection === 'models' ? (
|
|
506
|
-
<div
|
|
503
|
+
<div className="min-h-0 flex-1 overflow-hidden">
|
|
507
504
|
{open && (
|
|
508
505
|
<L2ModelSettingsView
|
|
509
506
|
connectionReady={connectionReady}
|
|
@@ -513,12 +510,10 @@ export function L2Settings({
|
|
|
513
510
|
)}
|
|
514
511
|
</div>
|
|
515
512
|
) : effectiveSection === 'plugins' ? (
|
|
516
|
-
<div
|
|
517
|
-
{renderPluginManagement()}
|
|
518
|
-
</div>
|
|
513
|
+
<div className="min-h-0 flex-1 overflow-hidden">{renderPluginManagement()}</div>
|
|
519
514
|
) : (
|
|
520
515
|
<L4AppDialogBody className="min-w-0 max-h-none">
|
|
521
|
-
<div
|
|
516
|
+
<div className="p-4 sm:p-6">
|
|
522
517
|
{effectiveSection === 'appearance' ? (
|
|
523
518
|
<L2AppearanceSettings />
|
|
524
519
|
) : effectiveSection === 'habits' ? (
|
|
@@ -76,7 +76,8 @@ export function L2ProjectModelDefaultSettings({
|
|
|
76
76
|
description: model.provider
|
|
77
77
|
})
|
|
78
78
|
)
|
|
79
|
-
const formDisabled = state.
|
|
79
|
+
const formDisabled = !state.projectReady || state.saving
|
|
80
|
+
const displayedCwd = state.loadedCwd ?? state.cwd
|
|
80
81
|
|
|
81
82
|
async function selectProject(cwd: string): Promise<void> {
|
|
82
83
|
if (await state.selectProject(cwd)) setMobileDetail(true)
|
|
@@ -198,17 +199,19 @@ export function L2ProjectModelDefaultSettings({
|
|
|
198
199
|
<div className="pi-desk-chat-scrollbar min-h-0 flex-1 overflow-y-auto p-4 @[48rem]/project-default:p-6">
|
|
199
200
|
<div className="max-w-[40rem] space-y-6">
|
|
200
201
|
<header className="space-y-1">
|
|
201
|
-
<h3 className="
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
202
|
+
<h3 className="flex items-center gap-2 text-lg font-semibold">
|
|
203
|
+
<span className="min-w-0 break-all">{projectName(displayedCwd)}</span>
|
|
204
|
+
<span className="inline-flex size-4 shrink-0">
|
|
205
|
+
{!state.projectReady && !state.projectError && (
|
|
206
|
+
<LoaderCircleIcon
|
|
207
|
+
role="status"
|
|
208
|
+
aria-label={t('projectLoading')}
|
|
209
|
+
className="size-4 animate-spin text-muted-foreground"
|
|
210
|
+
/>
|
|
211
|
+
)}
|
|
212
|
+
</span>
|
|
213
|
+
</h3>
|
|
214
|
+
<p className="break-all text-sm text-muted-foreground">{displayedCwd}</p>
|
|
212
215
|
</header>
|
|
213
216
|
|
|
214
217
|
{state.projectError ? (
|
|
@@ -221,11 +224,13 @@ export function L2ProjectModelDefaultSettings({
|
|
|
221
224
|
variant="outline"
|
|
222
225
|
size="sm"
|
|
223
226
|
onClick={state.retryProject}
|
|
227
|
+
disabled={state.projectLoading || state.projectRefreshing}
|
|
224
228
|
>
|
|
225
229
|
{t('retry', { ns: 'common' })}
|
|
226
230
|
</Button>
|
|
227
231
|
</div>
|
|
228
|
-
) :
|
|
232
|
+
) : null}
|
|
233
|
+
{state.loadedCwd !== null ? (
|
|
229
234
|
<fieldset disabled={formDisabled} className="min-w-0 space-y-6">
|
|
230
235
|
<div className="space-y-1">
|
|
231
236
|
<p className="text-sm font-medium">{t('projectDefaultSource')}</p>
|
|
@@ -289,6 +294,7 @@ export function L2ProjectModelDefaultSettings({
|
|
|
289
294
|
type="button"
|
|
290
295
|
variant="outline"
|
|
291
296
|
size="xs"
|
|
297
|
+
disabled={formDisabled}
|
|
292
298
|
className="h-auto min-h-7 max-w-full whitespace-normal text-left"
|
|
293
299
|
onClick={(): void => state.applyPreset(preset)}
|
|
294
300
|
>
|
|
@@ -309,7 +315,7 @@ export function L2ProjectModelDefaultSettings({
|
|
|
309
315
|
</div>
|
|
310
316
|
) : null}
|
|
311
317
|
</fieldset>
|
|
312
|
-
)}
|
|
318
|
+
) : null}
|
|
313
319
|
</div>
|
|
314
320
|
</div>
|
|
315
321
|
<div className="shrink-0 border-t px-4 py-3 @[48rem]/project-default:px-6">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useSyncExternalStore, type ReactNode } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useSyncExternalStore, type ReactNode } from 'react'
|
|
4
4
|
import { useTranslation } from 'react-i18next'
|
|
5
5
|
import { LoaderCircleIcon, TerminalIcon } from 'lucide-react'
|
|
6
6
|
import { preloadL4TerminalRenderer } from '@client/l4_foundation/terminal/l4-terminal-renderer'
|
|
@@ -51,12 +51,46 @@ export function L2TerminalWorkspace({
|
|
|
51
51
|
} = useL2TerminalLayout(biz, mobile, expanded)
|
|
52
52
|
const selected = state.terminals.find((item) => item.terminalId === state.selectedId)
|
|
53
53
|
const chatHidden = expanded && (mobile || maximized)
|
|
54
|
+
const handleSystemBack = useCallback(
|
|
55
|
+
(event: KeyboardEvent): void => {
|
|
56
|
+
// Android 通过合成 Escape 请求返回;真实键盘 Escape 仍交给 Shell。
|
|
57
|
+
if (
|
|
58
|
+
!mobile ||
|
|
59
|
+
!expanded ||
|
|
60
|
+
event.isTrusted ||
|
|
61
|
+
event.key !== 'Escape' ||
|
|
62
|
+
event.defaultPrevented ||
|
|
63
|
+
event.isComposing ||
|
|
64
|
+
event.altKey ||
|
|
65
|
+
event.ctrlKey ||
|
|
66
|
+
event.metaKey ||
|
|
67
|
+
event.shiftKey ||
|
|
68
|
+
root.current?.closest('[inert]')
|
|
69
|
+
)
|
|
70
|
+
return
|
|
71
|
+
event.preventDefault()
|
|
72
|
+
event.stopImmediatePropagation()
|
|
73
|
+
collapse()
|
|
74
|
+
},
|
|
75
|
+
[collapse, expanded, mobile, root]
|
|
76
|
+
)
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!mobile || !expanded) return
|
|
79
|
+
// 失焦时等菜单、弹窗先消费,再收起终端。
|
|
80
|
+
window.addEventListener('keydown', handleSystemBack)
|
|
81
|
+
return () => window.removeEventListener('keydown', handleSystemBack)
|
|
82
|
+
}, [expanded, handleSystemBack, mobile])
|
|
54
83
|
|
|
55
84
|
return (
|
|
56
85
|
<div
|
|
57
86
|
ref={root}
|
|
58
87
|
className={cn('relative flex size-full min-h-0 min-w-0', right ? 'flex-row' : 'flex-col')}
|
|
59
88
|
data-terminal-dock={mobile ? 'mobile' : preferences.dock}
|
|
89
|
+
onKeyDownCapture={(event) => {
|
|
90
|
+
if (event.target instanceof Node && event.currentTarget.contains(event.target)) {
|
|
91
|
+
handleSystemBack(event.nativeEvent)
|
|
92
|
+
}
|
|
93
|
+
}}
|
|
60
94
|
>
|
|
61
95
|
<ResizablePanelGroup
|
|
62
96
|
orientation={right ? 'horizontal' : 'vertical'}
|
|
@@ -36,6 +36,10 @@ export function L2WorkSessionFilePreview({
|
|
|
36
36
|
useSyncExternalStore(runtime.subscribe, runtime.getRevision, runtime.getRevision)
|
|
37
37
|
const workSession = workSessions.find((item) => item.workId === runtime.getExpandedWorkId())
|
|
38
38
|
if (!workSession) return null
|
|
39
|
+
const closePreview = (): void => {
|
|
40
|
+
if (mobile) runtime.hideWindow(workSession.workId)
|
|
41
|
+
else runtime.collapseExpandedWindow()
|
|
42
|
+
}
|
|
39
43
|
const switchToChat = (): void => {
|
|
40
44
|
const input = document.querySelector<HTMLElement>(
|
|
41
45
|
`[data-work-id="${CSS.escape(workSession.workId)}"] textarea`
|
|
@@ -49,7 +53,7 @@ export function L2WorkSessionFilePreview({
|
|
|
49
53
|
<L4AppDialogRoot
|
|
50
54
|
open
|
|
51
55
|
onOpenChange={(open) => {
|
|
52
|
-
if (!open)
|
|
56
|
+
if (!open) closePreview()
|
|
53
57
|
}}
|
|
54
58
|
>
|
|
55
59
|
<L4AppDialogContent
|
|
@@ -103,7 +107,7 @@ export function L2WorkSessionFilePreview({
|
|
|
103
107
|
onSwitchPane={switchToChat}
|
|
104
108
|
onOpenTerminal={onOpenTerminal}
|
|
105
109
|
onHide={() => runtime.hideWindow(workSession.workId)}
|
|
106
|
-
onExpand={
|
|
110
|
+
onExpand={closePreview}
|
|
107
111
|
/>
|
|
108
112
|
</L4AppDialogContent>
|
|
109
113
|
</L4AppDialogRoot>
|