@lincyaw/dsh 0.1.0 → 0.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/build/client.mjs +7 -1
- package/build/dist/client.js +1364 -64
- package/build/dist/client.json +225 -21
- package/build/ensure.mjs +11 -2
- package/cordis.patch.yml +9 -0
- package/entries/events.mjs +7 -0
- package/entries/load.mjs +11 -0
- package/entries/telemetry.mjs +7 -0
- package/entries/turns.mjs +7 -0
- package/package.json +32 -19
- package/src/client/RootLayout.tsx +332 -0
- package/src/client/agent-requests.ts +64 -0
- package/src/client/assistant-setup.tsx +185 -0
- package/src/client/author-settings.ts +57 -0
- package/src/client/brand.tsx +21 -14
- package/src/client/contract.ts +48 -1
- package/src/client/document-context.ts +39 -0
- package/src/client/dsh.d.ts +27 -1
- package/src/client/fallback.tsx +23 -19
- package/src/client/frame.css +280 -120
- package/src/client/index.tsx +72 -69
- package/src/client/launch-target.ts +89 -0
- package/src/client/layout.ts +90 -77
- package/src/client/mascot.tsx +37 -0
- package/src/client/messages.ts +142 -49
- package/src/client/{chrome.tsx → navigation-shell.tsx} +20 -25
- package/src/client/observable.ts +16 -0
- package/src/client/paths.ts +17 -0
- package/src/client/root-reload.ts +163 -0
- package/src/client/runtime-diagnostics.tsx +228 -0
- package/src/client/settings-opener.ts +26 -0
- package/src/client/{source.tsx → source-links.tsx} +16 -26
- package/src/client/speech-settings.tsx +7 -0
- package/src/client/styles.css +42 -0
- package/src/client/theme.ts +4 -2
- package/src/client/workbench-keys.ts +36 -0
- package/src/client/workspace-events.ts +114 -0
- package/src/node/bindings.ts +345 -0
- package/src/node/context.ts +131 -20
- package/src/node/{dsh.ts → dsh-types.ts} +33 -2
- package/src/node/fake-collector.ts +46 -0
- package/src/node/fake-model.ts +80 -0
- package/src/node/launch.ts +127 -10
- package/src/node/policy.ts +20 -23
- package/src/node/routes.ts +21 -11
- package/src/node/server-routes.ts +3 -0
- package/src/node/service.ts +58 -207
- package/src/node/skills.ts +9 -31
- package/src/node/telemetry.ts +126 -0
- package/src/node/tools.ts +34 -11
- package/src/node/{turns/index.ts → turns.ts} +48 -37
- package/src/node/workbench.ts +51 -6
- package/src/node/workspace-events.ts +167 -0
- package/src/node/workspaces.ts +2 -2
- package/src/workspace-events.ts +31 -0
- package/web.patch.yml +27 -56
- package/rows/load.mjs +0 -8
- package/rows/turns.mjs +0 -7
- package/skills/presmium-author/SKILL.md +0 -95
- package/skills/presmium-author/references/assistance.md +0 -27
- package/skills/presmium-author/references/code.md +0 -52
- package/skills/presmium-author/references/directives.md +0 -592
- package/skills/presmium-author/references/publishing.md +0 -60
- package/skills/presmium-author/references/writing.md +0 -72
- package/skills/presmium-present/SKILL.md +0 -69
- package/skills/presmium-present/references/classroom.md +0 -46
- package/skills/presmium-present/references/delivery.md +0 -63
- package/skills/presmium-present/references/lecture.md +0 -58
- package/src/client/Frame.tsx +0 -314
- /package/{rows → entries}/context.mjs +0 -0
- /package/{rows → entries}/policy.mjs +0 -0
- /package/{rows → entries}/routes.mjs +0 -0
- /package/{rows → entries}/service.mjs +0 -0
- /package/{rows → entries}/skills.mjs +0 -0
- /package/{rows → entries}/tools.mjs +0 -0
- /package/{rows → entries}/workbench.mjs +0 -0
- /package/{rows → entries}/workspaces.mjs +0 -0
package/src/client/brand.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { PmButton, WorkbenchProvider } from '@lincyaw/workbench'
|
|
2
2
|
import type { Workbench } from '@lincyaw/workbench'
|
|
3
|
-
import { useEffect
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
4
|
import { Modal } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
5
|
-
import {
|
|
6
|
-
import { BRAND_COPY,
|
|
7
|
-
import
|
|
5
|
+
import { BRAND_SLOTS, ONBOARDING } from './contract.ts'
|
|
6
|
+
import { BRAND_COPY, BRAND_LANGUAGES, useClientMessages } from './messages.ts'
|
|
7
|
+
import { rememberOpenSection } from './settings-opener.ts'
|
|
8
8
|
|
|
9
9
|
export function Logo({ size = 24, className }: { size?: number, className?: string }) {
|
|
10
10
|
return (
|
|
@@ -15,11 +15,6 @@ export function Logo({ size = 24, className }: { size?: number, className?: stri
|
|
|
15
15
|
)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function useFrameMessages(workbench: Workbench): FrameMessages {
|
|
19
|
-
const locale = useSyncExternalStore(workbench.subscribe, () => workbench.getState().locale)
|
|
20
|
-
return FRAME_MESSAGES[locale]
|
|
21
|
-
}
|
|
22
|
-
|
|
23
18
|
const WELCOMED = 'presmium.workbench.welcomed'
|
|
24
19
|
|
|
25
20
|
function welcomed(): boolean {
|
|
@@ -31,8 +26,9 @@ function welcomed(): boolean {
|
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
function Welcome({ workbench, complete, openSection }: { workbench: Workbench, complete?: () => void, openSection?: (id: string) => void }) {
|
|
34
|
-
const t =
|
|
29
|
+
const t = useClientMessages(workbench)
|
|
35
30
|
const read = welcomed()
|
|
31
|
+
useEffect(() => rememberOpenSection(openSection), [openSection])
|
|
36
32
|
useEffect(() => {
|
|
37
33
|
if (read) complete?.()
|
|
38
34
|
}, [read, complete])
|
|
@@ -45,7 +41,7 @@ function Welcome({ workbench, complete, openSection }: { workbench: Workbench, c
|
|
|
45
41
|
complete?.()
|
|
46
42
|
}
|
|
47
43
|
return (
|
|
48
|
-
<WorkbenchProvider workbench={workbench}
|
|
44
|
+
<WorkbenchProvider workbench={workbench}>
|
|
49
45
|
<Modal open title={t.welcomeTitle} onClose={done} headless>
|
|
50
46
|
<div className="pmw-welcome__card pm-ui" data-pmw-welcome="">
|
|
51
47
|
<Logo size={40} />
|
|
@@ -68,14 +64,16 @@ function Welcome({ workbench, complete, openSection }: { workbench: Workbench, c
|
|
|
68
64
|
)
|
|
69
65
|
}
|
|
70
66
|
|
|
71
|
-
function PassOver({ complete }: { complete?: () => void }) {
|
|
67
|
+
function PassOver({ complete, openSection }: { complete?: () => void, openSection?: (id: string) => void }) {
|
|
72
68
|
useEffect(() => {
|
|
69
|
+
rememberOpenSection(openSection)
|
|
73
70
|
complete?.()
|
|
74
|
-
}, [complete])
|
|
71
|
+
}, [complete, openSection])
|
|
75
72
|
return null
|
|
76
73
|
}
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
/** Puts Presmium's mark, name, first-use notice and languages in DeepSeek Harness's slots, in every layout. */
|
|
76
|
+
export function registerBrand(ctx: DshClientContext, workbench: Workbench): void {
|
|
79
77
|
const [mark, name, hero] = BRAND_SLOTS
|
|
80
78
|
ctx.effect(() => ctx.slots.inject(mark, () => ctx.slots.register({ name: mark }, ({ size }: { size?: number }) => <Logo size={size ?? 24} />)), 'presmium: sidebar brand mark')
|
|
81
79
|
ctx.effect(() => ctx.slots.inject(name, () => ctx.slots.register({ name }, () => <span className="pmw-brand-name">Presmium</span>)), 'presmium: sidebar brand name')
|
|
@@ -83,8 +81,17 @@ export function brand(ctx: DshClientContext, workbench: Workbench): void {
|
|
|
83
81
|
const [welcome, signIn] = ONBOARDING.steps
|
|
84
82
|
ctx.effect(() => ctx.slots.inject(ONBOARDING.slot, () => ctx.slots.register({ name: ONBOARDING.slot, id: welcome, priority: -1, order: -100 }, props => <Welcome workbench={workbench} {...props} />)), 'presmium: first-use notice')
|
|
85
83
|
ctx.effect(() => ctx.slots.inject(ONBOARDING.slot, () => ctx.slots.register({ name: ONBOARDING.slot, id: signIn, priority: -1, order: 0 }, PassOver)), 'presmium: no DeepSeek sign-in step')
|
|
84
|
+
// Harness's own `English` and `中文` carry its developer copy in the AI panel ("Describe what you want to build…"),
|
|
85
|
+
// and a language's dictionary cannot be registered twice; a choice of either becomes the same language with Presmium's
|
|
86
|
+
// copy. Only changes are followed: the page's first, provisional language may still give way to the stored one.
|
|
86
87
|
for (const language of BRAND_LANGUAGES) {
|
|
87
88
|
ctx.effect(() => ctx.locale.addLanguage({ id: language.id, label: language.label, fallback: language.fallback }), `presmium: language ${language.id}`)
|
|
88
89
|
for (const [ns, dict] of Object.entries(BRAND_COPY[language.id])) ctx.effect(() => ctx.locale.register(ns, language.id, dict), `presmium: ${ns} copy ${language.id}`)
|
|
89
90
|
}
|
|
91
|
+
ctx.effect(() => ctx.locale.subscribe(() => {
|
|
92
|
+
const active = ctx.locale.getLocale().active
|
|
93
|
+
const language = BRAND_LANGUAGES.find(candidate => candidate.fallback === active)
|
|
94
|
+
// The language may still be unregistered while its definition loads or after it is unloaded.
|
|
95
|
+
if (language && ctx.locale.getLocale().locales.some(locale => locale.id === language.id)) void ctx.locale.setLocale(language.id)
|
|
96
|
+
}), 'presmium: Harness\'s languages with Presmium\'s copy')
|
|
90
97
|
}
|
package/src/client/contract.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// What the client part assumes of DeepSeek Harness's client: the IDs it registers and looks for, the children of the
|
|
2
|
+
// root slot, the members of ctx.layout, the brand and first-use slots, the shell's module table and the plugin group
|
|
3
|
+
// of the plugins page. The build (build/client.mjs) and the upgrade gate (e2e/workbench.spec.ts) read these too.
|
|
1
4
|
export const CLIENT_ID = '@lincyaw/dsh'
|
|
2
5
|
|
|
3
6
|
export const ROOT_CHILDREN = {
|
|
@@ -14,6 +17,29 @@ export const LAYOUT_MEMBERS = ['panelInfo', 'selectPanel', 'beginNavigation', 't
|
|
|
14
17
|
export const BRAND_SLOTS = ['sidebar.brand.mark', 'sidebar.brand.name', 'conversation.hero.brand.mark'] as const
|
|
15
18
|
export const ONBOARDING = { slot: 'settings.onboarding', steps: ['welcome-notice', 'deepseek-official'] } as const
|
|
16
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Where the AI assistant explains a missing model and a failed turn: the composer's takeover chain, and the chat's
|
|
22
|
+
* `turn-error` node (its shipped cell is replaced, so every failed turn reads in the author's terms).
|
|
23
|
+
*/
|
|
24
|
+
export const ASSISTANT_SLOTS = ['conversation.composer', { name: 'conversation.chat.node', key: 'turn-error' }] as const
|
|
25
|
+
|
|
26
|
+
/** Where the kitten sits beside the conversation: an added entry (its own id) among the session header's actions. */
|
|
27
|
+
export const MASCOT_SLOT = { name: 'conversation.session.header.actions', id: 'presmium-mascot' } as const
|
|
28
|
+
|
|
29
|
+
/** Where the author turns the kitten on or off: an added row (its own id) in Settings → General. */
|
|
30
|
+
export const MASCOT_SETTING = { name: 'settings.general.item', id: 'presmium-mascot' } as const
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Settings → Models, and when the model catalog is read again: on the Host's events that change what can be routed,
|
|
34
|
+
* whenever the window regains focus, and on a slow poll while no model is usable (Settings is a modal over this page,
|
|
35
|
+
* so saving a key there does not refocus the window).
|
|
36
|
+
*/
|
|
37
|
+
export const MODEL_SETTINGS = {
|
|
38
|
+
section: 'models',
|
|
39
|
+
events: ['credentials/reference-updated', 'settings/document-updated', 'llm/adapters-updated'],
|
|
40
|
+
pollMs: 3000,
|
|
41
|
+
} as const
|
|
42
|
+
|
|
17
43
|
export const SHELL_MODULES = [
|
|
18
44
|
'react',
|
|
19
45
|
'react/jsx-runtime',
|
|
@@ -28,8 +54,29 @@ export const SHELL_MODULES = [
|
|
|
28
54
|
'@deepseek-ai/dsh-client-ui-dockkit',
|
|
29
55
|
] as const
|
|
30
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Harness's technical rows in Settings → General, by cell id, that stay out of an author's Settings while the coding
|
|
59
|
+
* view is off: the default permission mode and the display of the AI's work (work details and when they collapse, send
|
|
60
|
+
* behaviour while busy, performance and usage).
|
|
61
|
+
*/
|
|
62
|
+
export const TECHNICAL_SETTINGS = { slot: 'settings.general.item', ids: ['permission', 'transcript-view', 'composer-enter', 'performance-usage'] } as const
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The author's defaults for Harness's display preferences, by settings namespace (docs/studio.md 7.2):
|
|
66
|
+
* the coding view (trajectory, code diffs, every agent preset and the technical rows above) off, and the AI's work as
|
|
67
|
+
* compact, collapsed summaries without token statistics.
|
|
68
|
+
*/
|
|
69
|
+
export const AUTHOR_DEFAULTS: Record<string, Record<string, unknown>> = {
|
|
70
|
+
'ui-settings': { enabled: false },
|
|
71
|
+
'ui-chat': { transcriptView: 'compact', performanceUsage: 'compact' },
|
|
72
|
+
}
|
|
73
|
+
|
|
31
74
|
export const EXPERIMENTAL_GROUP = '[data-plugin-group="extensions"]'
|
|
32
75
|
|
|
33
76
|
export const UI_LAYOUT = '@deepseek-ai/dsh-client-ui-layout'
|
|
77
|
+
export const UI_SIDEBAR = '@deepseek-ai/dsh-client-ui-sidebar'
|
|
34
78
|
|
|
35
|
-
|
|
79
|
+
/** Whether DeepSeek Harness's boot graph has the plugin `id` (a running host keeps its roster until it restarts). */
|
|
80
|
+
export function isBootEntryLoaded(id: string): boolean {
|
|
81
|
+
return window.__DSH_BOOT__?.entries.some(entry => entry.id === id) ?? false
|
|
82
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { selectedDocument } from '@lincyaw/workbench'
|
|
2
|
+
import type { Workbench } from '@lincyaw/workbench'
|
|
3
|
+
|
|
4
|
+
/** The agent session the main view shows. */
|
|
5
|
+
export function mainViewAgentSession<T extends { retainedBy: { mainView?: number } }>(byId: Record<string, T>): T | undefined {
|
|
6
|
+
return Object.values(byId).find(session => (session.retainedBy.mainView ?? 0) > 0)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Tells the bundle which document the agent session works on (`null`: none, the workspace root). */
|
|
10
|
+
export function postDocumentContext(agentSessionId: string, document: string | null, signal?: AbortSignal): Promise<Response> {
|
|
11
|
+
// Relative to the page: Studio may be served below a path (`--public-url`).
|
|
12
|
+
return fetch('api/presmium/context', {
|
|
13
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
14
|
+
body: JSON.stringify({ session: agentSessionId, document }), ...signal ? { signal } : {},
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Keeps the main view's agent session on the document selected in the workbench, in every layout. */
|
|
19
|
+
export function syncDocumentContext(ctx: DshClientContext, workbench: Workbench): void {
|
|
20
|
+
ctx.inject(['sessions'], inner => inner.effect(() => {
|
|
21
|
+
let previous: string | undefined
|
|
22
|
+
let pending: AbortController | undefined
|
|
23
|
+
const sync = () => {
|
|
24
|
+
const session = mainViewAgentSession(inner.sessions.list.getSnapshot().byId)
|
|
25
|
+
if (!session) return
|
|
26
|
+
const document = selectedDocument(workbench.state)
|
|
27
|
+
const key = JSON.stringify([session.id, document])
|
|
28
|
+
if (key === previous) return
|
|
29
|
+
previous = key
|
|
30
|
+
pending?.abort()
|
|
31
|
+
pending = new AbortController()
|
|
32
|
+
void postDocumentContext(session.id, document, pending.signal).catch(() => {})
|
|
33
|
+
}
|
|
34
|
+
const offSession = inner.sessions.list.subscribe(sync)
|
|
35
|
+
const offDocument = workbench.subscribe(sync)
|
|
36
|
+
sync()
|
|
37
|
+
return () => { offSession(); offDocument(); pending?.abort() }
|
|
38
|
+
}, 'presmium: selected document context (all layouts)'))
|
|
39
|
+
}
|
package/src/client/dsh.d.ts
CHANGED
|
@@ -28,6 +28,12 @@ interface DshObservable<T> {
|
|
|
28
28
|
|
|
29
29
|
type DshSelectorHook<T> = <U>(select: (value: T) => U) => U
|
|
30
30
|
|
|
31
|
+
interface DshConfigForm {
|
|
32
|
+
getSnapshot(): { status: 'loading' | 'ready' | 'unavailable', user: unknown, revision: number | undefined, writable: boolean }
|
|
33
|
+
subscribe(listener: () => void): () => void
|
|
34
|
+
set(field: string, value: unknown): Promise<boolean>
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
interface DshSlotOptions {
|
|
32
38
|
name: string
|
|
33
39
|
key?: string
|
|
@@ -39,6 +45,8 @@ interface DshSlotOptions {
|
|
|
39
45
|
children?: Record<string, { kind: 'single' | 'keyed' | 'list', scope: 'root' }>
|
|
40
46
|
store?: unknown
|
|
41
47
|
inject?: unknown
|
|
48
|
+
/** A `chain` slot's election: the first non-null result in priority order renders. */
|
|
49
|
+
select?: (owner: unknown) => unknown
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
interface DshSlotEntry {
|
|
@@ -71,9 +79,14 @@ interface DshLocale {
|
|
|
71
79
|
addLanguage(language: { id: string, label: string, fallback?: string }): () => void
|
|
72
80
|
register(ns: string, locale: string | Record<string, Record<string, string>>, dict?: Record<string, string>): () => void
|
|
73
81
|
bind(ns: string): (key: string, values?: Record<string, string>) => string
|
|
82
|
+
setLocale(id: string): unknown
|
|
74
83
|
}
|
|
75
84
|
|
|
85
|
+
interface DshShortcutBinding { code: string, modifiers: string[] }
|
|
86
|
+
|
|
76
87
|
interface DshShortcuts {
|
|
88
|
+
/** A read-only row of the shortcut reference; its keys are handled by their owner and kept from editable commands. */
|
|
89
|
+
registerFixed(command: { id: string, label: () => string, keys: readonly string[], bindings: readonly [DshShortcutBinding, ...DshShortcutBinding[]], group: 'application' | 'input' | 'menus' | 'approval' }): () => void
|
|
77
90
|
register(command: {
|
|
78
91
|
id: string
|
|
79
92
|
label: () => string
|
|
@@ -107,7 +120,15 @@ interface DshSourceTabProps {
|
|
|
107
120
|
} }
|
|
108
121
|
}
|
|
109
122
|
|
|
123
|
+
interface DshSessionBinding {
|
|
124
|
+
session: {
|
|
125
|
+
beginSubmission(input: { mode: 'queue', text: string, attachments: [] }): { requestId: string, abandon(): void }
|
|
126
|
+
prompt(content: { type: 'text', text: string }[], mode: 'queue', signal: AbortSignal | undefined, requestId: string): Promise<{ ok: true, value: { accepted: true } } | { ok: false, error: { code: string, message: string } }>
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
110
130
|
interface DshClientContext {
|
|
131
|
+
remote: { session: { modelCatalog(): Promise<{ ok: boolean, value?: { routableProviders?: readonly string[] } }> } }
|
|
111
132
|
effect(effect: () => unknown, label?: string): void
|
|
112
133
|
on(event: string, listener: (...args: any[]) => void): () => void
|
|
113
134
|
inject(names: string[], apply: (ctx: DshClientContext) => void): void
|
|
@@ -119,9 +140,14 @@ interface DshClientContext {
|
|
|
119
140
|
sidebarRightTabs: DshSidebarRightTabs
|
|
120
141
|
sidebarRight: { active(): { id: string, kind: string, contentId: string } | undefined, close(id: string): void, isExpanded(): boolean, toggleExpanded(): void }
|
|
121
142
|
layout: { selectPanel(id: string | null): void }
|
|
122
|
-
sessions: { list: DshObservable<{ byId: Record<string, { id: string, cwd?: string, retainedBy: { mainView?: number } }> }
|
|
143
|
+
sessions: { list: DshObservable<{ byId: Record<string, { id: string, cwd?: string, retainedBy: { mainView?: number } }> }>, binding(id: string): DshSessionBinding | undefined }
|
|
123
144
|
uiWorkspace: { openWorkspace(id: string, beforeOpen?: (sessionId: string) => void): Promise<void>, startSession(id?: string): void }
|
|
124
145
|
workspaces: { list: DshObservable<{ items: readonly { workspaceId: string, path: string }[] }> }
|
|
146
|
+
configForms: {
|
|
147
|
+
developerTools: { enabled: DshObservable<boolean> }
|
|
148
|
+
get(entryId: string): DshConfigForm
|
|
149
|
+
whileServed(namespaces: readonly string[], register: (served: ReadonlySet<string>) => () => void): () => void
|
|
150
|
+
}
|
|
125
151
|
}
|
|
126
152
|
|
|
127
153
|
interface Window {
|
package/src/client/fallback.tsx
CHANGED
|
@@ -1,43 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DocumentView, HistoryPanel, HomePanel, PreviewPanel, useWorkbench, useWorkbenchState, WorkbenchProvider } from '@lincyaw/workbench'
|
|
2
2
|
import type { Workbench } from '@lincyaw/workbench'
|
|
3
|
+
import { useRef } from 'react'
|
|
3
4
|
import type { ComponentType } from 'react'
|
|
4
5
|
import { Logo } from './brand.tsx'
|
|
5
6
|
import { CLIENT_ID } from './contract.ts'
|
|
6
|
-
import {
|
|
7
|
+
import { CLIENT_MESSAGES } from './messages.ts'
|
|
8
|
+
import { RuntimeDiagnosticsNotice, WorkbenchRuntimeBoundary } from './runtime-diagnostics.tsx'
|
|
7
9
|
|
|
8
10
|
export const FALLBACK_PANEL = 'presmium'
|
|
9
11
|
|
|
10
12
|
function Centre() {
|
|
11
|
-
const
|
|
13
|
+
const workbench = useWorkbench()
|
|
14
|
+
const document_ = useWorkbenchState(state => state.document)
|
|
15
|
+
const retainedDocument = useRef(false)
|
|
16
|
+
if (document_ !== undefined) retainedDocument.current = true
|
|
12
17
|
return (
|
|
13
18
|
<div className="pmw-fallback pm-ui" data-pmw-fallback="">
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<DocumentView />
|
|
20
|
-
</div>
|
|
21
|
-
)}
|
|
19
|
+
<RuntimeDiagnosticsNotice workbench={workbench} />
|
|
20
|
+
{document_ === undefined && <HomePanel />}
|
|
21
|
+
{retainedDocument.current && <div className="pmw-fallback__doc" hidden={document_ === undefined}>
|
|
22
|
+
<DocumentView active={document_ !== undefined} />
|
|
23
|
+
</div>}
|
|
22
24
|
</div>
|
|
23
25
|
)
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
28
|
+
/** With DeepSeek Harness's own layout: the workbench is a global panel of `main`, the document and its history are rightbar tab types. */
|
|
29
|
+
export function registerFallbackLayout(ctx: DshClientContext, workbench: Workbench): void {
|
|
30
|
+
const t = () => CLIENT_MESSAGES[workbench.state.locale]
|
|
31
|
+
const provided = (Component: ComponentType, className?: string) => function PresmiumPanel() {
|
|
29
32
|
return (
|
|
30
|
-
<WorkbenchProvider workbench={workbench}
|
|
31
|
-
{className ? <div className={className}><Component /></div> : <Component />}
|
|
33
|
+
<WorkbenchProvider workbench={workbench}>
|
|
34
|
+
<WorkbenchRuntimeBoundary workbench={workbench}>{className ? <div className={className}><Component /></div> : <Component />}</WorkbenchRuntimeBoundary>
|
|
32
35
|
</WorkbenchProvider>
|
|
33
36
|
)
|
|
34
37
|
}
|
|
35
38
|
ctx.effect(() => ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({ name: 'sidebar.panellist', id: FALLBACK_PANEL, order: -10, label: () => t().product }, () => <Logo size={20} />)), 'presmium: the panel list entry')
|
|
36
|
-
ctx.effect(() => ctx.slots.register({ name: 'main', key: FALLBACK_PANEL }, provided(Centre
|
|
39
|
+
ctx.effect(() => ctx.slots.register({ name: 'main', key: FALLBACK_PANEL }, provided(Centre)), 'presmium: the workbench as a global panel')
|
|
37
40
|
ctx.inject(['sidebarRightTabs'], (inner) => {
|
|
41
|
+
const History = () => <HistoryPanel onClose={() => {}} />
|
|
38
42
|
const tabs = [
|
|
39
|
-
{ kind: 'presmium-preview', Panel: PreviewPanel, title: () => `Presmium · ${t().preview}` },
|
|
40
|
-
{ kind: 'presmium-
|
|
43
|
+
{ kind: 'presmium-preview', Panel: PreviewPanel as ComponentType, title: () => `Presmium · ${t().preview}` },
|
|
44
|
+
{ kind: 'presmium-history', Panel: History, title: () => `Presmium · ${t().history}` },
|
|
41
45
|
]
|
|
42
46
|
for (const { kind, Panel, title } of tabs) {
|
|
43
47
|
const id = `${CLIENT_ID}/${kind}`
|