@jcy2387/dsh-conversation-ui 0.1.0-rc.12
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/LICENSE +21 -0
- package/README.en.md +123 -0
- package/README.md +123 -0
- package/conversation-ui-off.yml +3 -0
- package/cordis.patch.yml +10 -0
- package/lib/client.js +3672 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +302 -0
- package/package.json +182 -0
- package/src/boot-config.ts +28 -0
- package/src/client/AnimatedDisclosure.tsx +89 -0
- package/src/client/ConversationCard.module.css +253 -0
- package/src/client/ConversationCard.tsx +122 -0
- package/src/client/DeliverablesCard.module.css +187 -0
- package/src/client/DeliverablesCard.tsx +142 -0
- package/src/client/FollowHost.tsx +25 -0
- package/src/client/ToolActivityGroup.tsx +341 -0
- package/src/client/TurnPreludeUserNodeView.tsx +258 -0
- package/src/client/TurnProcessMember.tsx +92 -0
- package/src/client/TypewriterAssistantNodeView.module.css +1260 -0
- package/src/client/TypewriterAssistantNodeView.tsx +390 -0
- package/src/client/TypewriterToolNodeView.tsx +916 -0
- package/src/client/conversation-ui-card-controller.ts +162 -0
- package/src/client/conversation-ui-settings-api.ts +65 -0
- package/src/client/deliverables.ts +263 -0
- package/src/client/index.ts +242 -0
- package/src/client/locales.ts +74 -0
- package/src/client/teleprompterGlide.ts +590 -0
- package/src/client/turnElapsed.ts +27 -0
- package/src/client/turnProcessFold.ts +224 -0
- package/src/client/useConversationContent.ts +471 -0
- package/src/client/useFpsGuard.ts +84 -0
- package/src/config.ts +44 -0
- package/src/css-modules.d.ts +9 -0
- package/src/index.ts +2 -0
- package/src/package-meta.ts +39 -0
- package/src/plugin.ts +155 -0
- package/src/profile-installation.ts +121 -0
- package/src/settings-api.ts +33 -0
- package/src/settings.ts +30 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { createElement, useSyncExternalStore, type ComponentType } from 'react'
|
|
2
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
3
|
+
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
4
|
+
// Type-only: the connection Context merge and the plugins section's SlotMap
|
|
5
|
+
// entry ('settings.plugin.item').
|
|
6
|
+
import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
|
|
7
|
+
import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
|
|
8
|
+
import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
9
|
+
import { TypewriterAssistantNodeView } from './TypewriterAssistantNodeView.tsx'
|
|
10
|
+
import { wrapFollowNodeView, type FollowWrapProps } from './TypewriterToolNodeView.tsx'
|
|
11
|
+
import { wrapTurnPreludeNodeView } from './TurnPreludeUserNodeView.tsx'
|
|
12
|
+
import { ConversationCard } from './ConversationCard.tsx'
|
|
13
|
+
import { ConversationCardController } from './conversation-ui-card-controller.ts'
|
|
14
|
+
import { DeliverablesCard } from './DeliverablesCard.tsx'
|
|
15
|
+
import { deliverablesDefinition, selectDeliverables } from './deliverables.ts'
|
|
16
|
+
import { createConversationSettingsApi } from './conversation-ui-settings-api.ts'
|
|
17
|
+
import { NS as SETTINGS_NS, en, zh } from './locales.ts'
|
|
18
|
+
import { DEFAULT_CONVERSATION_CONFIG, CONVERSATION_BOOT_GLOBAL, type ConversationConfig } from '../config.ts'
|
|
19
|
+
import { DEFAULT_CONVERSATION_SETTINGS } from '../settings.ts'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cordis services required by the browser half. Only `slots` is load-bearing
|
|
23
|
+
* for the stream itself; locale and Connection power the configuration card
|
|
24
|
+
* and are wired through `ctx.inject` below so a deployment without them still
|
|
25
|
+
* streams with defaults.
|
|
26
|
+
*/
|
|
27
|
+
export const inject = ['slots']
|
|
28
|
+
|
|
29
|
+
type AssistantProps = ChatNodeViewProps<'assistant-step'>
|
|
30
|
+
|
|
31
|
+
const CONVERSATION_MODES: readonly string[] = ['typewriter', 'teleprompter']
|
|
32
|
+
const CONVERSATION_PRESETS: readonly string[] = ['realtime', 'balanced', 'silky']
|
|
33
|
+
|
|
34
|
+
/** Steering/command rows stay untouched; `assistant-step` is replaced. */
|
|
35
|
+
const SKIP_WRAP = new Set(['assistant-step', 'steering', 'command-input'])
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read the Host-bridged boot config. The inline script is produced by this
|
|
39
|
+
* plugin's Host half from a schema-validated value, so only the structural
|
|
40
|
+
* guarantees that could break between the two halves are re-checked: the
|
|
41
|
+
* global is absent when the client runs without its Host entry (defaults
|
|
42
|
+
* apply), and any present-but-malformed value fails loudly instead of
|
|
43
|
+
* rendering a half-configured view.
|
|
44
|
+
* @returns The resolved configuration for the assistant node view.
|
|
45
|
+
*/
|
|
46
|
+
function readBootConfig(): ConversationConfig {
|
|
47
|
+
const raw = (globalThis as Record<string, unknown>)[CONVERSATION_BOOT_GLOBAL]
|
|
48
|
+
if (raw === undefined) {
|
|
49
|
+
console.info('[dsh-conversation-ui] no host config bridge; using defaults')
|
|
50
|
+
return DEFAULT_CONVERSATION_CONFIG
|
|
51
|
+
}
|
|
52
|
+
if (
|
|
53
|
+
typeof raw !== 'object' || raw === null
|
|
54
|
+
|| !CONVERSATION_MODES.includes((raw as ConversationConfig).mode)
|
|
55
|
+
|| !CONVERSATION_PRESETS.includes((raw as ConversationConfig).preset)
|
|
56
|
+
|| typeof (raw as ConversationConfig).revealCharsPerSec !== 'number'
|
|
57
|
+
|| typeof (raw as ConversationConfig).scrollSpeedPxPerSec !== 'number'
|
|
58
|
+
|| typeof (raw as ConversationConfig).maxScrollSpeedPxPerSec !== 'number'
|
|
59
|
+
) {
|
|
60
|
+
throw new Error(`[dsh-conversation-ui] malformed ${CONVERSATION_BOOT_GLOBAL} boot global: ${JSON.stringify(raw)}`)
|
|
61
|
+
}
|
|
62
|
+
return raw as ConversationConfig
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Wrap every keyed Chat row except `assistant-step` in place. A second
|
|
67
|
+
* register with the same `children` table throws because the child slot is
|
|
68
|
+
* already declared, and only the winning entry receives `renderSlot`;
|
|
69
|
+
* swapping `entry.component` keeps the original children, locale, and inject
|
|
70
|
+
* seats. `assistant-step` is replaced below so text and Think use the
|
|
71
|
+
* Codex-style Assistant presentation.
|
|
72
|
+
* @param ctx - Browser context carrying the slot registry.
|
|
73
|
+
* @returns Restorer that puts the original components back.
|
|
74
|
+
*/
|
|
75
|
+
function wrapGrowingChatRows(ctx: ClientContext, config: ConversationConfig): () => void {
|
|
76
|
+
const restores: Array<() => void> = []
|
|
77
|
+
const wrapped = new WeakSet<object>()
|
|
78
|
+
|
|
79
|
+
const wrapAll = (): void => {
|
|
80
|
+
for (const entry of ctx.slots.entries('conversation.chat.node')) {
|
|
81
|
+
const key = entry.options.key
|
|
82
|
+
if (key === undefined || SKIP_WRAP.has(key)) continue
|
|
83
|
+
const current = entry.component
|
|
84
|
+
if (
|
|
85
|
+
(typeof current !== 'function' && (typeof current !== 'object' || current === null))
|
|
86
|
+
|| wrapped.has(current)
|
|
87
|
+
) continue
|
|
88
|
+
const inner = current as ComponentType<FollowWrapProps>
|
|
89
|
+
const next = key === 'user'
|
|
90
|
+
? wrapTurnPreludeNodeView(inner)
|
|
91
|
+
: wrapFollowNodeView(inner, {
|
|
92
|
+
minSpeedPxPerSec: config.scrollSpeedPxPerSec,
|
|
93
|
+
maxSpeedPxPerSec: config.maxScrollSpeedPxPerSec,
|
|
94
|
+
})
|
|
95
|
+
wrapped.add(next)
|
|
96
|
+
entry.component = next
|
|
97
|
+
restores.push(() => {
|
|
98
|
+
if (entry.component === next) entry.component = inner
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
wrapAll()
|
|
104
|
+
const off = ctx.on('slots/changed', (key: string) => {
|
|
105
|
+
if (key === 'conversation.chat.node') wrapAll()
|
|
106
|
+
})
|
|
107
|
+
return () => {
|
|
108
|
+
off()
|
|
109
|
+
for (const restore of restores) restore()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A live preference cell read by `useSyncExternalStore`. It starts on the
|
|
115
|
+
* shared default and, once the plugin-owned settings controller is attached,
|
|
116
|
+
* tracks the resolved `thinkAutoExpand` value. A composition (or test) that
|
|
117
|
+
* ships no settings surface keeps the cell on the default.
|
|
118
|
+
*/
|
|
119
|
+
class PreferenceCell {
|
|
120
|
+
private readonly listeners = new Set<() => void>()
|
|
121
|
+
private card: ConversationCardController | undefined
|
|
122
|
+
private value = DEFAULT_CONVERSATION_SETTINGS.thinkAutoExpand
|
|
123
|
+
|
|
124
|
+
/** Re-point the cell at the plugin-owned settings controller. */
|
|
125
|
+
attach(card: ConversationCardController): () => void {
|
|
126
|
+
this.card = card
|
|
127
|
+
this.refresh()
|
|
128
|
+
const unsubscribe = card.subscribe(() => { this.refresh() })
|
|
129
|
+
return () => {
|
|
130
|
+
unsubscribe()
|
|
131
|
+
if (this.card !== card) return
|
|
132
|
+
this.card = undefined
|
|
133
|
+
this.refresh()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private read(): boolean {
|
|
138
|
+
return this.card?.getSnapshot().thinkAutoExpand ?? DEFAULT_CONVERSATION_SETTINGS.thinkAutoExpand
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private refresh(): void {
|
|
142
|
+
const next = this.read()
|
|
143
|
+
if (next === this.value) return
|
|
144
|
+
this.value = next
|
|
145
|
+
for (const listener of this.listeners) listener()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
readonly getSnapshot = (): boolean => this.value
|
|
149
|
+
|
|
150
|
+
readonly subscribe = (listener: () => void): () => void => {
|
|
151
|
+
this.listeners.add(listener)
|
|
152
|
+
return () => { this.listeners.delete(listener) }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Register the Codex-style renderer after the conversation package declares the
|
|
158
|
+
* keyed Chat node seat. A lower priority shadows the built-in assistant row;
|
|
159
|
+
* every other growing row is wrapped in place so Tool cards, retries, and
|
|
160
|
+
* workflow runs share conversation follow. The Host-bridged configuration
|
|
161
|
+
* selects immediate or typewriter reveal, smoothing preset, and glide speed; the
|
|
162
|
+
* plugin-owned settings RPC supplies the live auto-expand preference when the
|
|
163
|
+
* settings surface is composed.
|
|
164
|
+
* @param ctx - Browser context carrying the shared slot registry.
|
|
165
|
+
*/
|
|
166
|
+
export function apply(ctx: ClientContext): void {
|
|
167
|
+
const config = readBootConfig()
|
|
168
|
+
const preference = new PreferenceCell()
|
|
169
|
+
|
|
170
|
+
// The native deliverables package already publishes successful file paths,
|
|
171
|
+
// so the Codex card must register independently of our optional richer event
|
|
172
|
+
// accumulator. Keeping the slot inside the service injection left Web with
|
|
173
|
+
// only the built-in "产物" chips when that callback was not activated.
|
|
174
|
+
ctx.slots.inject('conversation.chat.turnTail', () => ctx.slots.register({
|
|
175
|
+
name: 'conversation.chat.turnTail',
|
|
176
|
+
priority: -100,
|
|
177
|
+
select: selectDeliverables,
|
|
178
|
+
registrant: 'dsh-conversation-ui',
|
|
179
|
+
}, DeliverablesCard))
|
|
180
|
+
ctx.inject(['conversationEvents'], (deliverablesCtx) => {
|
|
181
|
+
const events = deliverablesCtx.get('conversationEvents') as {
|
|
182
|
+
register: (definition: typeof deliverablesDefinition) => () => void
|
|
183
|
+
}
|
|
184
|
+
return events.register(deliverablesDefinition)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// The card talks to the plugin-owned loopback RPC, so the core settings
|
|
188
|
+
// namespace allowlist cannot make it disappear. The stream still applies
|
|
189
|
+
// with defaults when the optional Settings UI or Connection is absent.
|
|
190
|
+
ctx.inject(['slots', 'locale', 'connection'], (settingsCtx) => {
|
|
191
|
+
const card = new ConversationCardController(
|
|
192
|
+
// The shared Context augmentation also carries the Host-side Connection
|
|
193
|
+
// shape. This browser entry runs after the client provider installs its
|
|
194
|
+
// handle, so narrow through unknown to its client contract here.
|
|
195
|
+
createConversationSettingsApi(settingsCtx.get('connection') as unknown as ConnectionHandle),
|
|
196
|
+
)
|
|
197
|
+
const detachPreference = preference.attach(card)
|
|
198
|
+
card.start()
|
|
199
|
+
settingsCtx.effect(() => settingsCtx.locale.register(SETTINGS_NS, { zh, en }), 'dsh-conversation-ui: settings dictionaries')
|
|
200
|
+
settingsCtx.slots.inject('settings.plugin.item', () => settingsCtx.slots.register({
|
|
201
|
+
name: 'settings.plugin.item',
|
|
202
|
+
key: 'conversation-ui',
|
|
203
|
+
locale: SETTINGS_NS,
|
|
204
|
+
inject: () => card.inject(),
|
|
205
|
+
}, ConversationCard))
|
|
206
|
+
return () => {
|
|
207
|
+
card.stop()
|
|
208
|
+
detachPreference()
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const configured = function ConversationConfiguredView(props: AssistantProps) {
|
|
213
|
+
const thinkAutoExpand = useSyncExternalStore(
|
|
214
|
+
preference.subscribe,
|
|
215
|
+
preference.getSnapshot,
|
|
216
|
+
preference.getSnapshot,
|
|
217
|
+
)
|
|
218
|
+
return createElement(TypewriterAssistantNodeView, {
|
|
219
|
+
...props,
|
|
220
|
+
mode: config.mode,
|
|
221
|
+
preset: config.preset,
|
|
222
|
+
revealCharsPerSec: config.revealCharsPerSec,
|
|
223
|
+
scrollSpeedPxPerSec: config.scrollSpeedPxPerSec,
|
|
224
|
+
maxScrollSpeedPxPerSec: config.maxScrollSpeedPxPerSec,
|
|
225
|
+
thinkAutoExpand,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
ctx.slots.inject('conversation.chat.node', () => {
|
|
229
|
+
const unwrap = wrapGrowingChatRows(ctx, config)
|
|
230
|
+
const unshadow = ctx.slots.register({
|
|
231
|
+
name: 'conversation.chat.node',
|
|
232
|
+
key: 'assistant-step',
|
|
233
|
+
priority: -100,
|
|
234
|
+
locale: 'conversation',
|
|
235
|
+
registrant: 'dsh-conversation-ui',
|
|
236
|
+
}, configured)
|
|
237
|
+
return () => {
|
|
238
|
+
unshadow()
|
|
239
|
+
unwrap()
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Locale bundles for the conversation-ui plugin configuration card. */
|
|
2
|
+
|
|
3
|
+
/** Dictionary namespace owned by this plugin's settings card. */
|
|
4
|
+
export const NS = 'settings.conversationUi'
|
|
5
|
+
|
|
6
|
+
/** Locale keys the card renders. */
|
|
7
|
+
export type ConversationLocaleKey =
|
|
8
|
+
| 'title' | 'description'
|
|
9
|
+
| 'thinkAutoExpand' | 'thinkAutoExpandHint'
|
|
10
|
+
| 'readOnly' | 'loading' | 'unavailable' | 'retry'
|
|
11
|
+
| 'version' | 'developmentVersion'
|
|
12
|
+
| 'updates' | 'updateHint' | 'developmentBuild' | 'updateUnavailable'
|
|
13
|
+
| 'update' | 'updating' | 'restartRequired' | 'updateFailed'
|
|
14
|
+
| 'save' | 'saving' | 'discard' | 'unsaved' | 'saveFailed'
|
|
15
|
+
|
|
16
|
+
/** English copy. */
|
|
17
|
+
export const en: Record<ConversationLocaleKey, string> = {
|
|
18
|
+
title: 'Conversation UI',
|
|
19
|
+
description: 'Codex-style progress, tool activity, and final answers.',
|
|
20
|
+
thinkAutoExpand: 'Auto-expand thinking',
|
|
21
|
+
thinkAutoExpandHint: 'Open the thinking block while it streams. Turn off to keep it collapsed.',
|
|
22
|
+
readOnly: 'This deployment stores settings read-only.',
|
|
23
|
+
loading: 'Loading plugin settings…',
|
|
24
|
+
unavailable: 'Plugin settings are unavailable in this connection.',
|
|
25
|
+
retry: 'Retry',
|
|
26
|
+
version: 'Version {version}',
|
|
27
|
+
developmentVersion: 'Development version {version}',
|
|
28
|
+
updates: 'Updates',
|
|
29
|
+
updateHint: 'Install the newest npm version, then restart Harness.',
|
|
30
|
+
developmentBuild: 'Linked source; updates are managed in the checkout.',
|
|
31
|
+
updateUnavailable: 'Updates are available only for an npm profile installation.',
|
|
32
|
+
update: 'Update',
|
|
33
|
+
updating: 'Updating…',
|
|
34
|
+
restartRequired: 'Updated. Restart Harness to load the new version.',
|
|
35
|
+
updateFailed: 'The package update failed; your current version is unchanged.',
|
|
36
|
+
save: 'Save',
|
|
37
|
+
saving: 'Saving…',
|
|
38
|
+
discard: 'Discard',
|
|
39
|
+
unsaved: 'Unsaved',
|
|
40
|
+
saveFailed: 'The deployment did not accept these values; they were left for you to correct.',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Simplified Chinese copy. */
|
|
44
|
+
export const zh: Record<ConversationLocaleKey, string> = {
|
|
45
|
+
title: '对话界面增强',
|
|
46
|
+
description: '按 Codex 风格展示过程答复、工具活动和最终答案。',
|
|
47
|
+
thinkAutoExpand: '自动展开思考',
|
|
48
|
+
thinkAutoExpandHint: '思考块在流式时自动展开;关闭后保持折叠,可手动展开。',
|
|
49
|
+
readOnly: '本部署的设置为只读。',
|
|
50
|
+
loading: '正在加载插件设置…',
|
|
51
|
+
unavailable: '当前连接无法访问插件设置。',
|
|
52
|
+
retry: '重试',
|
|
53
|
+
version: '版本 {version}',
|
|
54
|
+
developmentVersion: '开发版本 {version}',
|
|
55
|
+
updates: '更新',
|
|
56
|
+
updateHint: '安装最新 npm 版本后重启 Harness。',
|
|
57
|
+
developmentBuild: '当前为本地链接版本,请在源码目录管理更新。',
|
|
58
|
+
updateUnavailable: '只有 profile 使用 npm 包时才能更新。',
|
|
59
|
+
update: '更新',
|
|
60
|
+
updating: '更新中…',
|
|
61
|
+
restartRequired: '已更新;重启 Harness 后加载新版本。',
|
|
62
|
+
updateFailed: '包更新失败,当前版本未改变。',
|
|
63
|
+
save: '保存',
|
|
64
|
+
saving: '保存中…',
|
|
65
|
+
discard: '放弃修改',
|
|
66
|
+
unsaved: '未保存',
|
|
67
|
+
saveFailed: '本部署没有接受这些值,已保留供你修改。',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
71
|
+
interface LocaleNamespaceMap {
|
|
72
|
+
'settings.conversationUi': ConversationLocaleKey
|
|
73
|
+
}
|
|
74
|
+
}
|