@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
|
@@ -26,6 +26,7 @@ export type PluginManagementAction =
|
|
|
26
26
|
| { kind: 'reload'; mode?: 'normal' | 'basic' }
|
|
27
27
|
|
|
28
28
|
interface PluginManagementOptions {
|
|
29
|
+
basicMode?: boolean
|
|
29
30
|
biz: L2PluginManagementBiz
|
|
30
31
|
snapshot: L2PluginManagementSnapshot | null
|
|
31
32
|
onSnapshot: (snapshot: L2PluginManagementSnapshot) => void
|
|
@@ -104,6 +105,7 @@ interface PluginManagementState {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
export function useL2PluginManagement({
|
|
108
|
+
basicMode = false,
|
|
107
109
|
biz,
|
|
108
110
|
snapshot,
|
|
109
111
|
onSnapshot,
|
|
@@ -172,21 +174,24 @@ export function useL2PluginManagement({
|
|
|
172
174
|
const available = targets.filter((source) => !busySource(source))
|
|
173
175
|
busy.forEach((source) => setError(pluginRequestKey(source), t('sourceBusy')))
|
|
174
176
|
if (!available.length) return
|
|
175
|
-
if (busy.length && available.length > 32) {
|
|
176
|
-
setError('list', t('batchLimit'))
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
177
|
const keys = available.map(pluginRequestKey)
|
|
180
178
|
setRequest('check', true)
|
|
181
179
|
keys.forEach((key) => setRequest(key, true))
|
|
182
180
|
setLoading(true)
|
|
183
181
|
setError('list', null)
|
|
184
182
|
try {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
183
|
+
if (sources || busy.length) {
|
|
184
|
+
for (let offset = 0; offset < available.length; offset += 32) {
|
|
185
|
+
await readSnapshot({
|
|
186
|
+
checkUpdates: true,
|
|
187
|
+
sources: available.slice(offset, offset + 32),
|
|
188
|
+
...(tag ? { tag } : {})
|
|
189
|
+
})
|
|
190
|
+
if (!mounted.current) break
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
await readSnapshot({ checkUpdates: true, ...(tag ? { tag } : {}) })
|
|
194
|
+
}
|
|
190
195
|
} catch (cause) {
|
|
191
196
|
setError('list', cause instanceof Error ? cause.message : t('updateCheckFailed'))
|
|
192
197
|
} finally {
|
|
@@ -204,8 +209,21 @@ export function useL2PluginManagement({
|
|
|
204
209
|
setLoading(true)
|
|
205
210
|
void biz
|
|
206
211
|
.list(false)
|
|
207
|
-
.then((next) => {
|
|
208
|
-
if (active
|
|
212
|
+
.then(async (next) => {
|
|
213
|
+
if (!active || request !== listRequest.current) return
|
|
214
|
+
onSnapshot(next)
|
|
215
|
+
const unchecked = next.plugins.some(
|
|
216
|
+
(plugin) =>
|
|
217
|
+
plugin.kind === 'package' &&
|
|
218
|
+
/^(npm:|git:|https?:|git@)/.test(plugin.source) &&
|
|
219
|
+
plugin.updateAvailable === null &&
|
|
220
|
+
!plugin.updateError &&
|
|
221
|
+
!plugin.operation
|
|
222
|
+
)
|
|
223
|
+
if (!basicMode && unchecked) {
|
|
224
|
+
const checked = await biz.list(true)
|
|
225
|
+
if (active && request === listRequest.current) onSnapshot(checked)
|
|
226
|
+
}
|
|
209
227
|
})
|
|
210
228
|
.catch((cause: unknown) => {
|
|
211
229
|
if (active)
|
|
@@ -221,7 +239,7 @@ export function useL2PluginManagement({
|
|
|
221
239
|
detailRequest.current += 1
|
|
222
240
|
listRequest.current += 1
|
|
223
241
|
}
|
|
224
|
-
}, [biz, onSnapshot, t])
|
|
242
|
+
}, [biz, onSnapshot, t, basicMode])
|
|
225
243
|
|
|
226
244
|
const executeBatch = async (input: L2PluginManagementBatchRequest): Promise<void> => {
|
|
227
245
|
const sources = input.action === 'add' ? input.items.map((item) => item.source) : input.sources
|
|
@@ -236,19 +254,51 @@ export function useL2PluginManagement({
|
|
|
236
254
|
setError(key, null)
|
|
237
255
|
})
|
|
238
256
|
setConfirmation(null)
|
|
257
|
+
let remaining = [...available]
|
|
239
258
|
try {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
259
|
+
let current = await biz.list()
|
|
260
|
+
const deadline = Date.now() + 15 * 60_000
|
|
261
|
+
while (remaining.length) {
|
|
262
|
+
const pending = current.plugins.filter(
|
|
263
|
+
(plugin) => plugin.operation && plugin.operation.phase !== 'failed'
|
|
264
|
+
)
|
|
265
|
+
const capacity = input.action === 'tag' ? 32 : Math.max(0, 32 - pending.length)
|
|
266
|
+
if (!capacity) {
|
|
267
|
+
if (
|
|
268
|
+
pending.some((plugin) => plugin.operation?.phase === 'waiting') ||
|
|
269
|
+
Date.now() >= deadline
|
|
270
|
+
) {
|
|
271
|
+
remaining.forEach((source) => setError(pluginRequestKey(source), t('batchWaiting')))
|
|
272
|
+
break
|
|
273
|
+
}
|
|
274
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 750))
|
|
275
|
+
current = await biz.list()
|
|
276
|
+
if (mounted.current) {
|
|
277
|
+
listRequest.current += 1
|
|
278
|
+
onSnapshot(current)
|
|
279
|
+
}
|
|
280
|
+
continue
|
|
281
|
+
}
|
|
282
|
+
const chunk = remaining.slice(0, capacity)
|
|
283
|
+
const request =
|
|
284
|
+
input.action === 'add'
|
|
285
|
+
? { ...input, items: input.items.filter((item) => chunk.includes(item.source)) }
|
|
286
|
+
: { ...input, sources: chunk }
|
|
287
|
+
const response = await biz.batch(request)
|
|
288
|
+
current = response.snapshot
|
|
289
|
+
if (mounted.current) {
|
|
290
|
+
listRequest.current += 1
|
|
291
|
+
onSnapshot(current)
|
|
292
|
+
}
|
|
293
|
+
response.results.forEach((result) =>
|
|
294
|
+
setError(pluginRequestKey(result.source), result.error)
|
|
295
|
+
)
|
|
296
|
+
chunk.forEach((source) => setRequest(pluginRequestKey(source), false))
|
|
297
|
+
remaining = remaining.slice(chunk.length)
|
|
298
|
+
}
|
|
249
299
|
} catch (cause) {
|
|
250
300
|
const message = cause instanceof Error ? cause.message : t('batchFailed')
|
|
251
|
-
|
|
301
|
+
remaining.forEach((source) => setError(pluginRequestKey(source), message))
|
|
252
302
|
} finally {
|
|
253
303
|
keys.forEach((key) => setRequest(key, false))
|
|
254
304
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { ChevronDownIcon } from 'lucide-react'
|
|
3
4
|
import { useTranslation } from 'react-i18next'
|
|
4
5
|
import type {
|
|
5
6
|
L2PluginCatalogDetail,
|
|
@@ -8,8 +9,21 @@ import type {
|
|
|
8
9
|
import { selectL4LocalizedText } from '@common/l4_foundation/locale/l4-localized-text'
|
|
9
10
|
import { useL4Region } from '@client/l4_foundation/locale/l4-region-provider'
|
|
10
11
|
import { Button } from '@client/l4_foundation/ui/shadcn/button'
|
|
12
|
+
import {
|
|
13
|
+
Collapsible,
|
|
14
|
+
CollapsibleContent,
|
|
15
|
+
CollapsibleTrigger
|
|
16
|
+
} from '@client/l4_foundation/ui/shadcn/collapsible'
|
|
17
|
+
import {
|
|
18
|
+
Select,
|
|
19
|
+
SelectContent,
|
|
20
|
+
SelectItem,
|
|
21
|
+
SelectTrigger,
|
|
22
|
+
SelectValue
|
|
23
|
+
} from '@client/l4_foundation/ui/shadcn/select'
|
|
11
24
|
import { L2PluginReadme } from './l2-plugin-readme'
|
|
12
25
|
import { L2PluginDownloadSourceSelector } from './l2-plugin-download-source'
|
|
26
|
+
import { L2PluginManagementChannel } from './l2-plugin-management-channel'
|
|
13
27
|
|
|
14
28
|
interface CatalogDetailProps {
|
|
15
29
|
detail: L2PluginCatalogDetail
|
|
@@ -20,6 +34,8 @@ interface CatalogDetailProps {
|
|
|
20
34
|
updateAvailable: boolean
|
|
21
35
|
error: string | null
|
|
22
36
|
operationMessage?: string | null
|
|
37
|
+
channel: string
|
|
38
|
+
onChannel: (channel: string) => void
|
|
23
39
|
downloadMode: L2PluginDownloadSource['mode'] | 'default'
|
|
24
40
|
downloadRegistry: string
|
|
25
41
|
onDownloadMode: (mode: CatalogDetailProps['downloadMode']) => void
|
|
@@ -46,60 +62,57 @@ export function L2PluginCatalogDetailView(props: CatalogDetailProps): React.JSX.
|
|
|
46
62
|
const selectedVersionInstalled = props.installed && props.installedVersion === detail.version
|
|
47
63
|
const homepage = safeLink(detail.homepage)
|
|
48
64
|
const repository = safeLink(detail.repository)
|
|
65
|
+
const versions = [...new Set([detail.version, ...detail.versions])]
|
|
49
66
|
return (
|
|
50
67
|
<div className="min-w-0 space-y-5">
|
|
51
68
|
<div className="space-y-3">
|
|
52
69
|
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
|
53
|
-
{detail.
|
|
54
|
-
<span className="rounded-md bg-secondary px-2 py-1 text-secondary-foreground">
|
|
55
|
-
{t('official')}
|
|
56
|
-
</span>
|
|
57
|
-
) : null}
|
|
70
|
+
<span>v{detail.version}</span>
|
|
58
71
|
<span>{t(`kind_${detail.kind}`)}</span>
|
|
59
|
-
|
|
60
|
-
{detail.publisher
|
|
61
|
-
|
|
72
|
+
{detail.publisher ? (
|
|
73
|
+
<span className="[overflow-wrap:anywhere]">{detail.publisher}</span>
|
|
74
|
+
) : null}
|
|
62
75
|
</div>
|
|
63
|
-
|
|
64
|
-
{detail.description
|
|
65
|
-
? selectL4LocalizedText(detail.description, locale)
|
|
66
|
-
: t('noPluginDescription')}
|
|
67
|
-
</p>
|
|
68
|
-
<label className="flex flex-wrap items-center gap-2 text-sm">
|
|
69
|
-
{t('version')}
|
|
70
|
-
<select
|
|
71
|
-
value={detail.version}
|
|
72
|
-
onChange={(event) => props.onVersion(event.target.value)}
|
|
73
|
-
className="min-h-8 min-w-0 max-w-full rounded-lg border border-input bg-background px-2 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
74
|
-
>
|
|
75
|
-
{[...new Set([detail.version, ...detail.versions])].map((version) => (
|
|
76
|
-
<option key={version} value={version}>
|
|
77
|
-
{version}
|
|
78
|
-
</option>
|
|
79
|
-
))}
|
|
80
|
-
</select>
|
|
81
|
-
</label>
|
|
82
|
-
<p className="text-sm text-muted-foreground">
|
|
83
|
-
{t(
|
|
84
|
-
detail.compatible === true
|
|
85
|
-
? 'compatible'
|
|
86
|
-
: detail.compatible === false
|
|
87
|
-
? 'incompatible'
|
|
88
|
-
: 'compatibilityUnknown'
|
|
89
|
-
)}
|
|
90
|
-
</p>
|
|
91
|
-
{detail.compatibilityNote ? (
|
|
76
|
+
{detail.description ? (
|
|
92
77
|
<p className="text-sm text-muted-foreground [overflow-wrap:anywhere]">
|
|
93
|
-
{detail.
|
|
78
|
+
{selectL4LocalizedText(detail.description, locale)}
|
|
79
|
+
</p>
|
|
80
|
+
) : null}
|
|
81
|
+
{detail.compatible === false ? (
|
|
82
|
+
<p role="alert" className="text-sm text-destructive">
|
|
83
|
+
{t('incompatible')}
|
|
84
|
+
{detail.compatibilityNote ? ` · ${detail.compatibilityNote}` : ''}
|
|
94
85
|
</p>
|
|
95
86
|
) : null}
|
|
96
|
-
<div className="flex flex-wrap gap-3
|
|
87
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
88
|
+
{props.pending ? (
|
|
89
|
+
<span role="status" className="text-sm text-muted-foreground">
|
|
90
|
+
{props.operationMessage ?? t('submitting')}
|
|
91
|
+
</span>
|
|
92
|
+
) : selectedVersionInstalled ? (
|
|
93
|
+
<>
|
|
94
|
+
<span className="text-sm text-muted-foreground">{t('alreadyInstalled')}</span>
|
|
95
|
+
{props.updateAvailable ? (
|
|
96
|
+
<Button size="sm" disabled={props.disabled} onClick={props.onUpdate}>
|
|
97
|
+
{t('update')}
|
|
98
|
+
</Button>
|
|
99
|
+
) : null}
|
|
100
|
+
</>
|
|
101
|
+
) : (
|
|
102
|
+
<Button
|
|
103
|
+
size="sm"
|
|
104
|
+
disabled={props.disabled || detail.compatible === false}
|
|
105
|
+
onClick={props.onInstall}
|
|
106
|
+
>
|
|
107
|
+
{t(props.installed ? 'installVersion' : 'install')}
|
|
108
|
+
</Button>
|
|
109
|
+
)}
|
|
97
110
|
{homepage ? (
|
|
98
111
|
<a
|
|
99
112
|
href={homepage}
|
|
100
113
|
target="_blank"
|
|
101
114
|
rel="noopener noreferrer"
|
|
102
|
-
className="underline underline-offset-4"
|
|
115
|
+
className="text-sm underline underline-offset-4"
|
|
103
116
|
>
|
|
104
117
|
{t('homepage')}
|
|
105
118
|
</a>
|
|
@@ -109,53 +122,68 @@ export function L2PluginCatalogDetailView(props: CatalogDetailProps): React.JSX.
|
|
|
109
122
|
href={repository}
|
|
110
123
|
target="_blank"
|
|
111
124
|
rel="noopener noreferrer"
|
|
112
|
-
className="underline underline-offset-4"
|
|
125
|
+
className="text-sm underline underline-offset-4"
|
|
113
126
|
>
|
|
114
127
|
{t('repository')}
|
|
115
128
|
</a>
|
|
116
129
|
) : null}
|
|
117
130
|
</div>
|
|
118
|
-
{!selectedVersionInstalled ? (
|
|
119
|
-
<L2PluginDownloadSourceSelector
|
|
120
|
-
id="plugin-detail-download"
|
|
121
|
-
mode={props.downloadMode}
|
|
122
|
-
registry={props.downloadRegistry}
|
|
123
|
-
override
|
|
124
|
-
onMode={props.onDownloadMode}
|
|
125
|
-
onRegistry={props.onDownloadRegistry}
|
|
126
|
-
/>
|
|
127
|
-
) : null}
|
|
128
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
129
|
-
{props.pending ? (
|
|
130
|
-
<span role="status" className="text-sm text-muted-foreground">
|
|
131
|
-
{props.operationMessage ?? t('submitting')}
|
|
132
|
-
</span>
|
|
133
|
-
) : selectedVersionInstalled ? (
|
|
134
|
-
<>
|
|
135
|
-
<span className="text-sm text-muted-foreground">{t('alreadyInstalled')}</span>
|
|
136
|
-
{props.updateAvailable ? (
|
|
137
|
-
<Button
|
|
138
|
-
size="sm"
|
|
139
|
-
variant="outline"
|
|
140
|
-
disabled={props.disabled}
|
|
141
|
-
onClick={props.onUpdate}
|
|
142
|
-
>
|
|
143
|
-
{t('update')}
|
|
144
|
-
</Button>
|
|
145
|
-
) : null}
|
|
146
|
-
</>
|
|
147
|
-
) : (
|
|
148
|
-
<Button size="sm" disabled={props.disabled} onClick={props.onInstall}>
|
|
149
|
-
{t(props.installed ? 'installVersion' : 'install')}
|
|
150
|
-
</Button>
|
|
151
|
-
)}
|
|
152
|
-
</div>
|
|
153
131
|
{props.error ? (
|
|
154
132
|
<p role="alert" className="text-sm text-destructive [overflow-wrap:anywhere]">
|
|
155
133
|
{props.error}
|
|
156
134
|
</p>
|
|
157
135
|
) : null}
|
|
158
136
|
</div>
|
|
137
|
+
<Collapsible className="rounded-lg border">
|
|
138
|
+
<CollapsibleTrigger className="group flex min-h-9 w-full items-center justify-between px-3 text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
139
|
+
{t('advancedOptions')}
|
|
140
|
+
<ChevronDownIcon className="size-4 transition-transform group-aria-expanded:rotate-180" />
|
|
141
|
+
</CollapsibleTrigger>
|
|
142
|
+
<CollapsibleContent keepMounted className="hidden data-open:block">
|
|
143
|
+
<div className="space-y-3 px-3 pb-3">
|
|
144
|
+
<div className="flex flex-wrap items-center gap-2 text-sm">
|
|
145
|
+
<span>{t('installChannel')}</span>
|
|
146
|
+
<L2PluginManagementChannel
|
|
147
|
+
value={props.channel}
|
|
148
|
+
label={t('installChannel')}
|
|
149
|
+
placeholder={t('defaultChannel')}
|
|
150
|
+
disabled={props.pending}
|
|
151
|
+
onChange={props.onChannel}
|
|
152
|
+
/>
|
|
153
|
+
<Select
|
|
154
|
+
value={detail.version}
|
|
155
|
+
items={versions.map((value) => ({ value, label: value }))}
|
|
156
|
+
onOpenChange={(open, details) => {
|
|
157
|
+
if (open && props.pending) details.cancel()
|
|
158
|
+
}}
|
|
159
|
+
onValueChange={(value) => {
|
|
160
|
+
if (value && !props.pending) props.onVersion(value)
|
|
161
|
+
}}
|
|
162
|
+
>
|
|
163
|
+
<SelectTrigger aria-label={t('version')} aria-disabled={props.pending}>
|
|
164
|
+
<SelectValue />
|
|
165
|
+
</SelectTrigger>
|
|
166
|
+
<SelectContent positionerClassName="z-[160]">
|
|
167
|
+
{versions.map((value) => (
|
|
168
|
+
<SelectItem key={value} value={value} disabled={props.pending}>
|
|
169
|
+
{value}
|
|
170
|
+
</SelectItem>
|
|
171
|
+
))}
|
|
172
|
+
</SelectContent>
|
|
173
|
+
</Select>
|
|
174
|
+
</div>
|
|
175
|
+
<L2PluginDownloadSourceSelector
|
|
176
|
+
id="plugin-detail-download"
|
|
177
|
+
mode={props.downloadMode}
|
|
178
|
+
registry={props.downloadRegistry}
|
|
179
|
+
override
|
|
180
|
+
saving={props.pending}
|
|
181
|
+
onMode={props.onDownloadMode}
|
|
182
|
+
onRegistry={props.onDownloadRegistry}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
</CollapsibleContent>
|
|
186
|
+
</Collapsible>
|
|
159
187
|
<div className="border-t pt-4">
|
|
160
188
|
<L2PluginReadme key={`${detail.name}@${detail.version}`} readme={detail.readme} />
|
|
161
189
|
</div>
|
|
@@ -10,6 +10,13 @@ import { selectL4LocalizedText } from '@common/l4_foundation/locale/l4-localized
|
|
|
10
10
|
import { useL4Region } from '@client/l4_foundation/locale/l4-region-provider'
|
|
11
11
|
import { Button } from '@client/l4_foundation/ui/shadcn/button'
|
|
12
12
|
import { Input } from '@client/l4_foundation/ui/shadcn/input'
|
|
13
|
+
import {
|
|
14
|
+
Select,
|
|
15
|
+
SelectContent,
|
|
16
|
+
SelectItem,
|
|
17
|
+
SelectTrigger,
|
|
18
|
+
SelectValue
|
|
19
|
+
} from '@client/l4_foundation/ui/shadcn/select'
|
|
13
20
|
import { pluginNpmSpec, pluginRequestKey } from '../l2-use-plugin-management'
|
|
14
21
|
|
|
15
22
|
interface CatalogSearchProps {
|
|
@@ -39,6 +46,7 @@ interface CatalogSearchProps {
|
|
|
39
46
|
onUpdate: (source: string) => void
|
|
40
47
|
onMore: () => void
|
|
41
48
|
onRetry: () => void
|
|
49
|
+
onDirectInstall: () => void
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
export function L2PluginCatalogSearch(props: CatalogSearchProps): React.JSX.Element {
|
|
@@ -51,7 +59,7 @@ export function L2PluginCatalogSearch(props: CatalogSearchProps): React.JSX.Elem
|
|
|
51
59
|
)
|
|
52
60
|
return (
|
|
53
61
|
<section className="min-w-0 space-y-4" aria-label={t('searchPlugins')}>
|
|
54
|
-
<div className="
|
|
62
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
55
63
|
<label htmlFor="plugin-catalog-query" className="sr-only">
|
|
56
64
|
{t('searchPlugins')}
|
|
57
65
|
</label>
|
|
@@ -59,40 +67,61 @@ export function L2PluginCatalogSearch(props: CatalogSearchProps): React.JSX.Elem
|
|
|
59
67
|
id="plugin-catalog-query"
|
|
60
68
|
value={props.query}
|
|
61
69
|
placeholder={t('searchPlaceholder')}
|
|
70
|
+
className="min-w-40 flex-1 basis-56"
|
|
62
71
|
maxLength={200}
|
|
63
72
|
onChange={(event) => props.onQuery(event.target.value)}
|
|
64
73
|
onCompositionStart={props.onCompositionStart}
|
|
65
74
|
onCompositionEnd={props.onCompositionEnd}
|
|
66
75
|
/>
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
76
|
+
<Select
|
|
77
|
+
value={props.searchSource}
|
|
78
|
+
items={[
|
|
79
|
+
{ value: 'public', label: t('publicNpm') },
|
|
80
|
+
{ value: 'custom', label: t('customSearchSource') }
|
|
81
|
+
]}
|
|
82
|
+
onValueChange={(value) => {
|
|
83
|
+
if (value) props.onSearchSource(value)
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
<SelectTrigger aria-label={t('searchSource')}>
|
|
87
|
+
<SelectValue />
|
|
88
|
+
</SelectTrigger>
|
|
89
|
+
<SelectContent positionerClassName="z-[160]">
|
|
90
|
+
<SelectItem value="public">{t('publicNpm')}</SelectItem>
|
|
91
|
+
<SelectItem value="custom">{t('customSearchSource')}</SelectItem>
|
|
92
|
+
</SelectContent>
|
|
93
|
+
</Select>
|
|
94
|
+
<Select
|
|
95
|
+
value={props.kind}
|
|
96
|
+
items={(['all', 'desk', 'pi'] as const).map((value) => ({
|
|
97
|
+
value,
|
|
98
|
+
label: t(`kind_${value}`)
|
|
99
|
+
}))}
|
|
100
|
+
onValueChange={(value) => {
|
|
101
|
+
if (value) props.onKind(value)
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<SelectTrigger aria-label={t('pluginKind')}>
|
|
105
|
+
<SelectValue />
|
|
106
|
+
</SelectTrigger>
|
|
107
|
+
<SelectContent positionerClassName="z-[160]">
|
|
108
|
+
{(['all', 'desk', 'pi'] as const).map((value) => (
|
|
109
|
+
<SelectItem key={value} value={value}>
|
|
110
|
+
{t(`kind_${value}`)}
|
|
111
|
+
</SelectItem>
|
|
112
|
+
))}
|
|
113
|
+
</SelectContent>
|
|
114
|
+
</Select>
|
|
115
|
+
<Button
|
|
116
|
+
size="sm"
|
|
117
|
+
variant="outline"
|
|
118
|
+
disabled={props.disabled}
|
|
119
|
+
onClick={props.onDirectInstall}
|
|
120
|
+
>
|
|
121
|
+
{t('directInstall')}
|
|
122
|
+
</Button>
|
|
94
123
|
{props.searchSource === 'custom' ? (
|
|
95
|
-
<div className="space-y-1">
|
|
124
|
+
<div className="w-full space-y-1">
|
|
96
125
|
<label htmlFor="plugin-search-registry" className="block text-sm">
|
|
97
126
|
{t('searchRegistryUrl')}
|
|
98
127
|
</label>
|
|
@@ -115,7 +144,7 @@ export function L2PluginCatalogSearch(props: CatalogSearchProps): React.JSX.Elem
|
|
|
115
144
|
</div>
|
|
116
145
|
) : null}
|
|
117
146
|
{props.items.length ? (
|
|
118
|
-
<div className="divide-y rounded-lg border bg-card">
|
|
147
|
+
<div className="@container/catalog divide-y rounded-lg border bg-card">
|
|
119
148
|
{props.items.map((item) => {
|
|
120
149
|
const plugin = installed.get(item.name)
|
|
121
150
|
const key = pluginRequestKey(`npm:${item.name}`)
|
|
@@ -129,32 +158,42 @@ export function L2PluginCatalogSearch(props: CatalogSearchProps): React.JSX.Elem
|
|
|
129
158
|
props.errors[key] ??
|
|
130
159
|
(plugin?.error ? selectL4LocalizedText(plugin.error.message, locale) : null)
|
|
131
160
|
return (
|
|
132
|
-
<article
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
{
|
|
161
|
+
<article
|
|
162
|
+
key={item.name}
|
|
163
|
+
aria-label={item.name}
|
|
164
|
+
className="grid min-w-0 gap-x-4 gap-y-2 px-4 py-3 @xl/catalog:grid-cols-[minmax(0,1fr)_auto]"
|
|
165
|
+
>
|
|
166
|
+
<div className="min-w-0 space-y-1">
|
|
167
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
168
|
+
<Button
|
|
169
|
+
variant="link"
|
|
170
|
+
className="h-auto min-h-8 min-w-0 max-w-full p-0 text-left text-sm font-semibold whitespace-normal no-underline hover:underline [overflow-wrap:anywhere]"
|
|
171
|
+
onClick={() => props.onDetail(item)}
|
|
172
|
+
>
|
|
173
|
+
{item.name}
|
|
174
|
+
</Button>
|
|
175
|
+
<span className="text-xs text-muted-foreground">
|
|
176
|
+
v{healthy && plugin.version ? plugin.version : item.version}
|
|
145
177
|
</span>
|
|
146
|
-
|
|
147
|
-
|
|
178
|
+
{healthy && plugin.updateAvailable ? (
|
|
179
|
+
<span className="rounded-md bg-status-success/10 px-2 py-0.5 text-xs font-medium text-status-success">
|
|
180
|
+
{plugin.availableVersion
|
|
181
|
+
? t('newVersion', { version: plugin.availableVersion })
|
|
182
|
+
: t('updateAvailable')}
|
|
183
|
+
</span>
|
|
184
|
+
) : null}
|
|
185
|
+
<span className="text-xs text-muted-foreground">{t(`kind_${item.kind}`)}</span>
|
|
186
|
+
{item.publisher ? (
|
|
187
|
+
<span className="text-xs text-muted-foreground">{item.publisher}</span>
|
|
188
|
+
) : null}
|
|
189
|
+
</div>
|
|
190
|
+
<p className="line-clamp-2 text-sm text-muted-foreground [overflow-wrap:anywhere]">
|
|
191
|
+
{description
|
|
192
|
+
? selectL4LocalizedText(description, locale)
|
|
193
|
+
: t('noPluginDescription')}
|
|
194
|
+
</p>
|
|
148
195
|
</div>
|
|
149
|
-
<
|
|
150
|
-
{description
|
|
151
|
-
? selectL4LocalizedText(description, locale)
|
|
152
|
-
: t('noPluginDescription')}
|
|
153
|
-
</p>
|
|
154
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
155
|
-
<span className="min-w-0 text-xs text-muted-foreground [overflow-wrap:anywhere]">
|
|
156
|
-
{item.publisher ?? t('unknownPublisher')}
|
|
157
|
-
</span>
|
|
196
|
+
<div className="flex min-h-8 items-center self-start @xl/catalog:justify-end">
|
|
158
197
|
{pending ? (
|
|
159
198
|
<span role="status" className="text-sm text-muted-foreground">
|
|
160
199
|
{unfinished
|
|
@@ -189,12 +228,18 @@ export function L2PluginCatalogSearch(props: CatalogSearchProps): React.JSX.Elem
|
|
|
189
228
|
)}
|
|
190
229
|
</div>
|
|
191
230
|
{error ? (
|
|
192
|
-
<p
|
|
231
|
+
<p
|
|
232
|
+
role="alert"
|
|
233
|
+
className="text-sm text-destructive [overflow-wrap:anywhere] @xl/catalog:col-span-2"
|
|
234
|
+
>
|
|
193
235
|
{error}
|
|
194
236
|
</p>
|
|
195
237
|
) : null}
|
|
196
238
|
{plugin?.operation?.phase === 'failed' && plugin.operation.message ? (
|
|
197
|
-
<p
|
|
239
|
+
<p
|
|
240
|
+
role="alert"
|
|
241
|
+
className="text-sm text-destructive [overflow-wrap:anywhere] @xl/catalog:col-span-2"
|
|
242
|
+
>
|
|
198
243
|
{plugin.operation.message}
|
|
199
244
|
</p>
|
|
200
245
|
) : null}
|