@ash-qw/dsh-theme-prts 2.1.1 → 2.2.0
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/CHANGELOG.md +11 -0
- package/lib/client.js +181 -21
- package/package.json +1 -1
- package/src/client/preferences.js +6 -1
- package/src/client/session-summary-preview.js +148 -16
- package/src/client/sidebar-control-adapter.js +1 -1
- package/src/client/theme-settings-workbench.js +30 -1
- package/src/index.js +21 -4
- package/src/styles/facility-vector.css +26 -4
- package/src/styles/theme-settings-workbench.css +10 -0
package/package.json
CHANGED
|
@@ -42,7 +42,9 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
42
42
|
particlePattern: 'orthogonal',
|
|
43
43
|
railDefaultHidden: false,
|
|
44
44
|
sessionPreview: true,
|
|
45
|
+
aiToolsEnabled: true,
|
|
45
46
|
previewOngoing: true,
|
|
47
|
+
previewTools: true,
|
|
46
48
|
previewWarning: true,
|
|
47
49
|
previewDone: true,
|
|
48
50
|
previewFollowOngoing: true,
|
|
@@ -101,7 +103,8 @@ export const PREFERENCE_GROUPS = Object.freeze({
|
|
|
101
103
|
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
|
|
102
104
|
material: Object.freeze(['glass', 'composerGlass']),
|
|
103
105
|
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor']),
|
|
104
|
-
|
|
106
|
+
aiTools: Object.freeze(['aiToolsEnabled']),
|
|
107
|
+
navigation: Object.freeze(['railDefaultHidden', 'sessionPreview', 'previewOngoing', 'previewTools', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']),
|
|
105
108
|
conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle', 'bakerGroupAvatars', 'bakerLoading', 'conversationEmblem']),
|
|
106
109
|
})
|
|
107
110
|
|
|
@@ -237,7 +240,9 @@ export function normalizePreferences(value) {
|
|
|
237
240
|
sessionFlowColorFront: sessionFlowColors.front,
|
|
238
241
|
sessionFlowSpeed: sessionFlowSpeedOr(input.sessionFlowSpeed, DEFAULT_PREFERENCES.sessionFlowSpeed),
|
|
239
242
|
sessionPreview: booleanOr(input.sessionPreview, DEFAULT_PREFERENCES.sessionPreview),
|
|
243
|
+
aiToolsEnabled: booleanOr(input.aiToolsEnabled, DEFAULT_PREFERENCES.aiToolsEnabled),
|
|
240
244
|
previewOngoing: booleanOr(input.previewOngoing, DEFAULT_PREFERENCES.previewOngoing),
|
|
245
|
+
previewTools: booleanOr(input.previewTools, DEFAULT_PREFERENCES.previewTools),
|
|
241
246
|
previewWarning: booleanOr(input.previewWarning, DEFAULT_PREFERENCES.previewWarning),
|
|
242
247
|
previewDone: booleanOr(input.previewDone, DEFAULT_PREFERENCES.previewDone),
|
|
243
248
|
previewReplyLimit: [0, 5, 10, '0', '5', '10'].includes(input.previewReplyLimit) ? Number(input.previewReplyLimit) : 5,
|
|
@@ -39,6 +39,60 @@ export function latestAssistantReply(snapshot) {
|
|
|
39
39
|
return ''
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// Call objects may be mutable on older hosts; compare raw arguments before reuse.
|
|
43
|
+
const toolSummaryCache = new WeakMap()
|
|
44
|
+
function toolSummary(call) {
|
|
45
|
+
const raw = call.argsRaw ?? call.call?.argsRaw ?? ''
|
|
46
|
+
const cached = toolSummaryCache.get(call)
|
|
47
|
+
if (cached?.raw === raw) return cached.text
|
|
48
|
+
let args
|
|
49
|
+
try { args = JSON.parse(raw) } catch {}
|
|
50
|
+
const summary = ['description', 'command', 'cmd', 'file_path', 'filePath', 'path', 'query', 'pattern', 'url']
|
|
51
|
+
.map(field => args?.[field]).find(value => typeof value === 'string' && value.trim())
|
|
52
|
+
const normalized = summary?.replace(/\s+/g, ' ').trim()
|
|
53
|
+
const text = normalized ? normalized.slice(0, 160) + (normalized.length > 160 ? '…' : '') : ''
|
|
54
|
+
toolSummaryCache.set(call, {raw, text})
|
|
55
|
+
return text
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Only calls after the latest settled prose belong to the current progress preview.
|
|
59
|
+
export function previewToolCalls(snapshot) {
|
|
60
|
+
const turn = snapshot?.timeline?.turnOrder?.at(-1)
|
|
61
|
+
const order = snapshot?.order ?? []
|
|
62
|
+
const calls = [], seen = new Set()
|
|
63
|
+
const visit = (call, key) => {
|
|
64
|
+
if (!call) return
|
|
65
|
+
const id = call.callId ?? key
|
|
66
|
+
if (!seen.has(id)) { seen.add(id); calls.push({call, key: id}) }
|
|
67
|
+
for (const [index, child] of (call.subCalls ?? []).entries()) visit(child, key + ':' + index)
|
|
68
|
+
}
|
|
69
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
70
|
+
const node = snapshot.nodes?.get?.(order[i])
|
|
71
|
+
if (['user', 'steering', 'turn-trigger'].includes(node?.kind)) break
|
|
72
|
+
if (turn !== undefined && node?.location?.turn?.turn !== turn) continue
|
|
73
|
+
if (node?.visibility === 'hidden') continue
|
|
74
|
+
if (node?.kind === 'assistant-step' && node.data?.status === 'settled'
|
|
75
|
+
&& node.data.blocks?.some(block => block.kind === 'text' && block.text?.trim())) break
|
|
76
|
+
if (node?.kind === 'tool-call') visit(node.data?.root, order[i])
|
|
77
|
+
}
|
|
78
|
+
return calls.sort((a, b) => (b.call.callTime ?? b.call.time ?? 0) - (a.call.callTime ?? a.call.time ?? 0)).map(({call, key}) => {
|
|
79
|
+
const text = toolSummary(call)
|
|
80
|
+
return {
|
|
81
|
+
key,
|
|
82
|
+
name: call.name || call.call?.name || '工具调用',
|
|
83
|
+
status: call.kind === 'tool-result' ? call.isError ? '失败' : '已完成'
|
|
84
|
+
: call.phase === 'preparing' ? '准备中' : '执行中',
|
|
85
|
+
summary: text ? text
|
|
86
|
+
: call.phase === 'preparing' ? '正在准备调用…' : '暂无操作摘要',
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function latestToolCall(snapshot) {
|
|
92
|
+
const records = previewToolCalls(snapshot)
|
|
93
|
+
return records.length ? {...records[0], records} : undefined
|
|
94
|
+
}
|
|
95
|
+
|
|
42
96
|
export function completedPreviewReplies(snapshot) {
|
|
43
97
|
const replies = []
|
|
44
98
|
const latestTurn = snapshot?.timeline?.turnOrder?.at(-1)
|
|
@@ -142,8 +196,8 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
142
196
|
active = undefined
|
|
143
197
|
if (!old) return
|
|
144
198
|
window.clearTimeout(old.leaveTimer)
|
|
145
|
-
window.
|
|
146
|
-
window.
|
|
199
|
+
window.cancelAnimationFrame(old.delay)
|
|
200
|
+
window.cancelAnimationFrame(old.refresh)
|
|
147
201
|
old.unsubscribe?.()
|
|
148
202
|
old.unsubscribeStatus?.()
|
|
149
203
|
if (keepWarm && old.reference && old.source) {
|
|
@@ -311,6 +365,39 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
311
365
|
body.setAttribute('data-prts-preview-body', '')
|
|
312
366
|
panel.querySelector('p').replaceWith(body)
|
|
313
367
|
}
|
|
368
|
+
panel.querySelector('[data-prts-preview-tool]')?.remove()
|
|
369
|
+
const toolSection = panel.ownerDocument.createElement('div')
|
|
370
|
+
toolSection.setAttribute('data-prts-preview-tool', '')
|
|
371
|
+
toolSection.hidden = true
|
|
372
|
+
body.before(toolSection)
|
|
373
|
+
let previousTool, currentTool
|
|
374
|
+
const toolEntry = panel.ownerDocument.createElement('button')
|
|
375
|
+
toolEntry.type = 'button'
|
|
376
|
+
toolEntry.setAttribute('data-prts-tool-entry', '')
|
|
377
|
+
const toolHeading = panel.ownerDocument.createElement('small')
|
|
378
|
+
const toolName = panel.ownerDocument.createElement('span')
|
|
379
|
+
toolEntry.append(toolHeading, toolName)
|
|
380
|
+
toolSection.append(toolEntry)
|
|
381
|
+
const openToolDetail = (refresh = false) => {
|
|
382
|
+
if (currentTool) showDetail({key: 'latest-tool', tool: currentTool}, 0, toolEntry, refresh)
|
|
383
|
+
}
|
|
384
|
+
toolEntry.addEventListener('pointermove', () => openToolDetail())
|
|
385
|
+
toolEntry.addEventListener('focus', () => openToolDetail())
|
|
386
|
+
toolEntry.addEventListener('click', () => openToolDetail())
|
|
387
|
+
const renderTool = tool => {
|
|
388
|
+
const signature = JSON.stringify(tool)
|
|
389
|
+
if (signature === previousTool) return
|
|
390
|
+
previousTool = signature
|
|
391
|
+
currentTool = tool
|
|
392
|
+
toolSection.hidden = !tool
|
|
393
|
+
if (!tool) {
|
|
394
|
+
if (detailKey === 'latest-tool') hideDetail()
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
toolHeading.textContent = '工具 · ' + tool.status + ' · ' + tool.records.length + ' 次'
|
|
398
|
+
toolName.textContent = tool.name
|
|
399
|
+
if (detailKey === 'latest-tool') openToolDetail(true)
|
|
400
|
+
}
|
|
314
401
|
const choices = panel.ownerDocument.createElement('div')
|
|
315
402
|
choices.setAttribute('data-prts-preview-choices', '')
|
|
316
403
|
panel.querySelector('[data-prts-preview-choices]')?.remove()
|
|
@@ -450,9 +537,9 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
450
537
|
detailEntry?.removeAttribute('data-prts-reply-active')
|
|
451
538
|
detailEntry = undefined
|
|
452
539
|
}
|
|
453
|
-
const showDetail = (reply, index, item) => {
|
|
540
|
+
const showDetail = (reply, index, item, refresh = false) => {
|
|
454
541
|
keep()
|
|
455
|
-
if (detailKey === reply.key && current.detail && !current.detail.hidden) return
|
|
542
|
+
if (!refresh && detailKey === reply.key && current.detail && !current.detail.hidden) return
|
|
456
543
|
if (!current.detail) {
|
|
457
544
|
const detail = panel.ownerDocument.createElement('section')
|
|
458
545
|
detail.setAttribute('data-prts-summary-reply', '')
|
|
@@ -472,10 +559,36 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
472
559
|
detailEntry?.removeAttribute('data-prts-reply-active')
|
|
473
560
|
detailEntry = item
|
|
474
561
|
item?.setAttribute('data-prts-reply-active', '')
|
|
475
|
-
detail.
|
|
562
|
+
detail.toggleAttribute('data-prts-tool-history', Boolean(reply.tool))
|
|
563
|
+
detail.querySelector('strong').textContent = reply.tool ? '本段工具记录 · ' + reply.tool.records.length + ' 次' : '回复 ' + (reply.ordinal ?? index + 1)
|
|
476
564
|
const content = detail.querySelector('[data-prts-preview-body]')
|
|
477
|
-
content.
|
|
478
|
-
|
|
565
|
+
const scrollTop = refresh ? content.scrollTop : 0
|
|
566
|
+
let anchorKey, anchorOffset
|
|
567
|
+
if (refresh && scrollTop > 0) {
|
|
568
|
+
const anchor = [...content.children].find(row => row.getBoundingClientRect().bottom > content.getBoundingClientRect().top)
|
|
569
|
+
anchorKey = anchor?.dataset.toolKey
|
|
570
|
+
anchorOffset = anchor?.getBoundingClientRect().top
|
|
571
|
+
}
|
|
572
|
+
if (reply.tool) {
|
|
573
|
+
content.replaceChildren(...reply.tool.records.map(record => {
|
|
574
|
+
const row = panel.ownerDocument.createElement('article')
|
|
575
|
+
row.setAttribute('data-prts-tool-record', '')
|
|
576
|
+
row.dataset.toolKey = record.key
|
|
577
|
+
const heading = panel.ownerDocument.createElement('div')
|
|
578
|
+
const name = panel.ownerDocument.createElement('b')
|
|
579
|
+
name.textContent = record.name
|
|
580
|
+
const status = panel.ownerDocument.createElement('small')
|
|
581
|
+
status.textContent = record.status
|
|
582
|
+
heading.append(name, status)
|
|
583
|
+
const summary = panel.ownerDocument.createElement('p')
|
|
584
|
+
summary.textContent = record.summary
|
|
585
|
+
row.append(heading, summary)
|
|
586
|
+
return row
|
|
587
|
+
}))
|
|
588
|
+
} else content.innerHTML = current.contentCache(reply.text).html
|
|
589
|
+
content.scrollTop = scrollTop
|
|
590
|
+
const retainedAnchor = anchorKey && [...content.children].find(row => row.dataset.toolKey === anchorKey)
|
|
591
|
+
if (retainedAnchor) content.scrollTop += retainedAnchor.getBoundingClientRect().top - anchorOffset
|
|
479
592
|
detail.hidden = false
|
|
480
593
|
const rect = panel.getBoundingClientRect()
|
|
481
594
|
const width = Math.min(340, window.innerWidth - 16)
|
|
@@ -548,11 +661,18 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
548
661
|
}))
|
|
549
662
|
}
|
|
550
663
|
choices.hidden = !labels.length
|
|
664
|
+
const tool = state === 'ongoing' && !question && getPreviewPreferences?.().previewTools !== false ? latestToolCall(source?.getSnapshot()) : undefined
|
|
665
|
+
renderTool(tool)
|
|
666
|
+
if (tool) label.textContent = '运行中 · 最新活动'
|
|
551
667
|
panel.setAttribute('aria-label', title + ':' + label.textContent)
|
|
552
668
|
if (state === 'ongoing' && source) {
|
|
553
669
|
const replies = selectReplies(source.getSnapshot(), replyLimit())
|
|
554
|
-
|
|
555
|
-
|
|
670
|
+
panel.toggleAttribute('data-prts-preview-empty', !replies.length && !tool)
|
|
671
|
+
if (replies === historyKeys) {
|
|
672
|
+
const emptyText = tool ? '' : '正在处理…'
|
|
673
|
+
if (!replies.length && body.textContent !== emptyText) body.textContent = emptyText
|
|
674
|
+
return
|
|
675
|
+
}
|
|
556
676
|
const previousTop = body.scrollTop
|
|
557
677
|
const scrollAnchor = previousTop > 0 ? [...body.children].find(entry => entry.getBoundingClientRect().bottom > body.getBoundingClientRect().top) : undefined
|
|
558
678
|
const anchorTop = scrollAnchor?.getBoundingClientRect().top
|
|
@@ -568,7 +688,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
568
688
|
}
|
|
569
689
|
}
|
|
570
690
|
if (!historyKeys.length) body.replaceChildren()
|
|
571
|
-
if (!replies.length) body.textContent = '
|
|
691
|
+
if (!replies.length) body.textContent = tool ? '' : '正在处理…'
|
|
572
692
|
for (let index = 0; index < replies.length; index++) {
|
|
573
693
|
const reply = replies[index]
|
|
574
694
|
if (retained.has(reply.key)) continue
|
|
@@ -580,6 +700,13 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
580
700
|
item.setAttribute('data-prts-reply-entry', '')
|
|
581
701
|
const heading = panel.ownerDocument.createElement('small')
|
|
582
702
|
heading.textContent = '回复 ' + reply.ordinal
|
|
703
|
+
const icon = panel.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
704
|
+
icon.setAttribute('viewBox', '0 0 16 16')
|
|
705
|
+
icon.setAttribute('aria-hidden', 'true')
|
|
706
|
+
const outline = panel.ownerDocument.createElementNS(icon.namespaceURI, 'path')
|
|
707
|
+
outline.setAttribute('d', 'M3 2.5h10a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H7l-4 2v-2a1 1 0 0 1-1-1v-7a1 1 0 0 1 1-1Z')
|
|
708
|
+
icon.append(outline)
|
|
709
|
+
heading.prepend(icon)
|
|
583
710
|
const summary = panel.ownerDocument.createElement('span')
|
|
584
711
|
summary.textContent = current.contentCache(reply.text).summary
|
|
585
712
|
item.append(heading, summary)
|
|
@@ -610,7 +737,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
610
737
|
? approval
|
|
611
738
|
? [pending.toolName, pending.displayReason?.['zh-CN'] || pending.displayReason?.zh || pending.reason || pending.displayReason?.en || '请进入会话查看操作详情'].filter(Boolean).join(':')
|
|
612
739
|
: first?.question || pendingQuestionPreview(pending)
|
|
613
|
-
: source ? excerpt || (state === 'ongoing' ? '
|
|
740
|
+
: source ? excerpt || (state === 'ongoing' ? '正在处理…' : '暂无正文回复,可进入会话查看执行记录。') : '正在读取…'
|
|
614
741
|
if (previousText !== text || previousQuestion !== question) {
|
|
615
742
|
const extendsReply = previousQuestion === question && previousText && text.startsWith(previousText)
|
|
616
743
|
if (question) body.textContent = text
|
|
@@ -631,7 +758,8 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
631
758
|
})
|
|
632
759
|
render()
|
|
633
760
|
body.scrollTop = 0
|
|
634
|
-
current.delay = window.
|
|
761
|
+
current.delay = window.requestAnimationFrame(async () => {
|
|
762
|
+
current.delay = undefined
|
|
635
763
|
try {
|
|
636
764
|
const service = getUiConversation?.()
|
|
637
765
|
const controller = getSessions?.() ?? sessions
|
|
@@ -643,11 +771,13 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
643
771
|
current.source = source
|
|
644
772
|
current.unsubscribe = source.subscribe(() => {
|
|
645
773
|
if (active !== current || current.refresh !== undefined) return
|
|
646
|
-
current.refresh = window.
|
|
774
|
+
current.refresh = window.requestAnimationFrame(() => {
|
|
647
775
|
current.refresh = undefined
|
|
648
|
-
if (current.state === 'ongoing' && !current.editor
|
|
776
|
+
if (current.state === 'ongoing' && !current.editor
|
|
777
|
+
&& selectReplies(source.getSnapshot(), replyLimit()) === historyKeys
|
|
778
|
+
&& JSON.stringify(getPreviewPreferences?.().previewTools === false ? undefined : latestToolCall(source.getSnapshot())) === previousTool) return
|
|
649
779
|
render()
|
|
650
|
-
}
|
|
780
|
+
})
|
|
651
781
|
})
|
|
652
782
|
render()
|
|
653
783
|
} catch {
|
|
@@ -659,7 +789,7 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
659
789
|
render()
|
|
660
790
|
if (body.textContent.trim() === '正在读取…') body.textContent = '暂时无法读取回复,可点击会话查看'
|
|
661
791
|
}
|
|
662
|
-
}
|
|
792
|
+
})
|
|
663
793
|
}
|
|
664
794
|
function leave() {
|
|
665
795
|
if (!active || active.sticky) return
|
|
@@ -673,6 +803,8 @@ export function createSessionSummaryPreview({ document, window, sessions, getSes
|
|
|
673
803
|
if (!active) return
|
|
674
804
|
const anchor = [...root.querySelectorAll('[data-prts-session-summary-item]')]
|
|
675
805
|
.find(item => item.getAttribute('data-prts-session-summary-item') === active.id)
|
|
806
|
+
// A read completion (or removed session) has no status row to own a preview.
|
|
807
|
+
if (!anchor) { stop(); return }
|
|
676
808
|
active.group?.removeAttribute('data-prts-preview-open')
|
|
677
809
|
active.anchor?.removeAttribute('data-prts-preview-active')
|
|
678
810
|
active.anchor = anchor
|
|
@@ -976,7 +976,7 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
976
976
|
|
|
977
977
|
return {
|
|
978
978
|
start(preferences = {}) {
|
|
979
|
-
const keys = ['sessionPreview','previewOngoing','previewWarning','previewDone','previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']
|
|
979
|
+
const keys = ['sessionPreview','previewOngoing','previewTools','previewWarning','previewDone','previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning', 'previewReplyLimit']
|
|
980
980
|
if (keys.some(key => previewPreferences[key] !== preferences[key])) preview.stop()
|
|
981
981
|
previewPreferences = Object.fromEntries(keys.map(key => [key, preferences[key]]))
|
|
982
982
|
if (!document) return
|
|
@@ -59,7 +59,7 @@ function settingButtons(key, label, options) {
|
|
|
59
59
|
: key === 'sessionFlowPalette'
|
|
60
60
|
? '<small data-prts-setting-note>同时适配日间与夜间模式</small>'
|
|
61
61
|
: ''
|
|
62
|
-
if (['composerGlass', 'bakerLoading', 'motion', 'cursor', 'railDefaultHidden', 'sessionFlow', 'sessionGlow', 'bakerGroupAvatars', 'conversationEmblem', 'sessionPreview', 'previewOngoing', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning'].includes(key)) return `<div data-prts-setting-row="${key}">
|
|
62
|
+
if (['composerGlass', 'bakerLoading', 'motion', 'cursor', 'railDefaultHidden', 'sessionFlow', 'sessionGlow', 'bakerGroupAvatars', 'conversationEmblem', 'sessionPreview', 'aiToolsEnabled', 'previewOngoing', 'previewTools', 'previewWarning', 'previewDone', 'previewDoubleClick', 'previewFollowOngoing', 'previewFollowWarning'].includes(key)) return `<div data-prts-setting-row="${key}">
|
|
63
63
|
<span><strong>${label}</strong>${note}</span>
|
|
64
64
|
<button type="button" role="switch" aria-label="${label}" aria-checked="false" data-prts-setting-switch="${key}" ${key === 'cursor' ? 'data-prts-cursor-switch' : ''}><span aria-hidden="true"></span></button>
|
|
65
65
|
</div>`
|
|
@@ -127,6 +127,7 @@ export function themeSettingsMarkup() {
|
|
|
127
127
|
<button type="button" data-prts-settings-jump="flow" role="tab" id="prts-tab-flow" aria-controls="prts-page-flow" aria-selected="false" tabindex="-1">会话流线</button>
|
|
128
128
|
<button type="button" data-prts-settings-jump="particles" role="tab" id="prts-tab-particles" aria-controls="prts-page-particles" aria-selected="false" tabindex="-1">粒子徽记</button>
|
|
129
129
|
<button type="button" data-prts-settings-jump="assistance" role="tab" id="prts-tab-assistance" aria-controls="prts-page-assistance" aria-selected="false" tabindex="-1">辅助功能</button>
|
|
130
|
+
<button type="button" data-prts-settings-jump="ai-tools" role="tab" id="prts-tab-ai-tools" aria-controls="prts-page-ai-tools" aria-selected="false" tabindex="-1">AI 工具</button>
|
|
130
131
|
</nav>
|
|
131
132
|
<footer data-prts-settings-actions aria-label="全局主题设置操作">
|
|
132
133
|
<div data-prts-persistence-error hidden role="alert">
|
|
@@ -167,6 +168,33 @@ export function themeSettingsMarkup() {
|
|
|
167
168
|
${COMMON_CONTROLS.filter(([key]) => ['cursor', 'railDefaultHidden', 'motion'].includes(key)).map(([key, label, options]) => settingButtons(key, label, options)).join('')}
|
|
168
169
|
</div>
|
|
169
170
|
</section>
|
|
171
|
+
<section data-prts-settings-section="ai-tools" data-prts-ai-tools role="tabpanel" id="prts-page-ai-tools" aria-labelledby="prts-tab-ai-tools" tabindex="0" hidden>
|
|
172
|
+
<div data-prts-preview-settings><div data-prts-preview-master>
|
|
173
|
+
${previewSetting('aiToolsEnabled', '允许 AI 定制主题', '默认开启 · 共 5 个工具 · 影响此 DSH 实例的所有会话')}
|
|
174
|
+
</div></div>
|
|
175
|
+
<p>开启后,AI 可以通过工具调整粒子徽记和 Baker 头像。关闭后,后续模型请求不再提供这些工具,手动设置不受影响;已有对话中的工具记录不会删除。</p>
|
|
176
|
+
<section>
|
|
177
|
+
<h3>粒子徽记 <small>3 个工具</small></h3>
|
|
178
|
+
<p>将会话中的图片制作成粒子徽记,用于新会话页面或正文轮换。</p>
|
|
179
|
+
<ol><li>上传图片,或让 AI 读取已生成的图片。</li><li>让 AI 制作草稿,调整轮廓、位置和缩放,先查看预览。</li><li>满意后明确要求应用到新会话徽记,或加入正文轮换。</li></ol>
|
|
180
|
+
<blockquote>“把这张图片做成粒子徽记,先给我看看效果。”</blockquote>
|
|
181
|
+
<details><summary>工具说明与限制</summary><dl>
|
|
182
|
+
<dt><code>prts_emblem_inspect</code></dt><dd>查看徽记配置、版本与当前会话的可用图片,不修改设置。</dd>
|
|
183
|
+
<dt><code>prts_emblem_preview</code></dt><dd>制作或调整粒子徽记草稿,只生成预览,不直接应用。</dd>
|
|
184
|
+
<dt><code>prts_emblem_apply</code></dt><dd>经你确认后,替换新会话徽记,或添加、替换正文轮换徽记。</dd>
|
|
185
|
+
</dl><p>单张来源图片最大 8 MB。徽记设置跨工作区和会话共享。粒子预览不等同于原图;配置发生变化时,AI 需要重新核对后再应用。</p></details>
|
|
186
|
+
</section>
|
|
187
|
+
<section>
|
|
188
|
+
<h3>Baker 头像 <small>2 个工具</small></h3>
|
|
189
|
+
<p>为用户和 AI 设置头像,调整圆形裁剪,或恢复默认头像。仅在 Baker 会话样式下显示。</p>
|
|
190
|
+
<ol><li>上传图片,说明要修改用户头像还是 AI 头像。</li><li>让 AI 查看当前配置,再按你的要求替换、裁剪或恢复默认。</li></ol>
|
|
191
|
+
<blockquote>“把这张图片设为 AI 头像,居中裁剪。”</blockquote>
|
|
192
|
+
<details><summary>工具说明与限制</summary><dl>
|
|
193
|
+
<dt><code>prts_avatar_inspect</code></dt><dd>查看头像配置、原图尺寸、裁剪参数及会话图片,不修改设置。</dd>
|
|
194
|
+
<dt><code>prts_avatar_apply</code></dt><dd>替换用户或 AI 头像、基于现有原图重新裁剪,或恢复默认头像。</dd>
|
|
195
|
+
</dl><p>单图最大 5 MB、2500 万像素;裁剪支持 1~4 倍缩放。修改立即影响此实例所有会话,不会自动切换会话样式。替换后不保留旧原图;恢复默认会删除自定义原图。</p></details>
|
|
196
|
+
</section>
|
|
197
|
+
</section>
|
|
170
198
|
<section data-prts-settings-section="assistance" role="tabpanel" id="prts-page-assistance" aria-labelledby="prts-tab-assistance" tabindex="0" hidden>
|
|
171
199
|
<section data-prts-preview-settings>
|
|
172
200
|
<div data-prts-preview-master>
|
|
@@ -177,6 +205,7 @@ export function themeSettingsMarkup() {
|
|
|
177
205
|
<header><strong>悬停预览</strong><small>选择哪些状态显示详细浮窗</small></header>
|
|
178
206
|
${previewSetting('previewOngoing', '运行中预览', '逐轮查看已完成的回复,最新内容置顶')}
|
|
179
207
|
<div data-prts-preview-details="previewOngoing">
|
|
208
|
+
${previewSetting('previewTools', '显示工具调用', '显示工具进度,悬停查看最新正文之后的调用记录')}
|
|
180
209
|
<label data-prts-setting-row="previewReplyLimit"><span><strong>回复条数</strong><small data-prts-preview-setting-description>仅保留最新回复</small></span><select data-prts-preview-reply-limit aria-label="最多显示回复"><option value="5">最新 5 条</option><option value="10">最新 10 条</option><option value="0">不限制</option></select></label>
|
|
181
210
|
${previewSetting('previewFollowOngoing', '运行结束后跟随', '转为待处理或已完成时,自动切换预览')}
|
|
182
211
|
</div>
|
package/src/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export const Config = Schema.object(Object.fromEntries([
|
|
|
19
19
|
['preferencesInitialized', Schema.boolean().default(false).volatile()],
|
|
20
20
|
]))
|
|
21
21
|
|
|
22
|
-
export async function apply(ctx) {
|
|
22
|
+
export async function apply(ctx, config = {}) {
|
|
23
23
|
ctx.inject(['settings'], settingsCtx => {
|
|
24
24
|
settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber))
|
|
25
25
|
})
|
|
@@ -27,7 +27,24 @@ export async function apply(ctx) {
|
|
|
27
27
|
const service=await createEmblemService(directory)
|
|
28
28
|
const backgrounds=await createBackgroundService(join(directory,'..','backgrounds'))
|
|
29
29
|
const avatars=await createAvatarService(join(directory,'..','avatars'))
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const definitions=[...emblemTools(service,ctx.attachments),...avatarTools(avatars,ctx.attachments)]
|
|
31
|
+
let toolDisposers=[]
|
|
32
|
+
const syncTools=()=>{
|
|
33
|
+
const enabled=(config.aiToolsEnabled?.get?.() ?? config.aiToolsEnabled) !== false
|
|
34
|
+
if(enabled && !toolDisposers.length) {
|
|
35
|
+
for(const tool of definitions)toolDisposers.push(ctx.tools.register(tool))
|
|
36
|
+
} else if(!enabled) {
|
|
37
|
+
for(const dispose of toolDisposers.splice(0).reverse())dispose()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
syncTools()
|
|
41
|
+
// Host volatile fields are updated before this notification; they expose get(), not subscribe().
|
|
42
|
+
const stopConfig=ctx.on('app-boot/config-reload',syncTools)
|
|
43
|
+
const disposers=[registerEmblemApi(ctx,service),registerAvatarApi(ctx,avatars),registerBackgroundApi(ctx,backgrounds)]
|
|
44
|
+
ctx.effect(()=>async()=>{
|
|
45
|
+
stopConfig()
|
|
46
|
+
for(const dispose of toolDisposers.splice(0).reverse())dispose()
|
|
47
|
+
for(const dispose of disposers.reverse())dispose()
|
|
48
|
+
await service.dispose();await backgrounds.dispose()
|
|
49
|
+
})
|
|
33
50
|
}
|
|
@@ -776,10 +776,10 @@ html[data-dsh-prts] [data-prts-preview-body] a { color:var(--prts-cyan);text-dec
|
|
|
776
776
|
html[data-dsh-prts] [data-prts-preview-message] + [data-prts-preview-message] { margin-top:5px; }
|
|
777
777
|
html[data-dsh-prts] [data-prts-preview-message] > small { display:block;margin-bottom:6px;color:var(--prts-muted);font:10px/1.5 var(--prts-mono); }
|
|
778
778
|
|
|
779
|
-
html[data-dsh-prts] [data-prts-reply-entry] { display:flex;align-items:flex-start;gap:8px;width:100%;padding:6px 8px;text-align:left;border:1px solid var(--prts-line);border-radius:5px;background:transparent;color:var(--prts-ink);cursor:pointer; }
|
|
780
|
-
html[data-dsh-prts] [data-prts-reply-entry]:is(:hover,:focus-visible,[data-prts-reply-active]) { background:color-mix(in srgb,var(--prts-ink) 7%,transparent);border-color:var(--prts-muted); }
|
|
781
|
-
html[data-dsh-prts] [data-prts-reply-entry] small { flex:0 0 auto;white-space:nowrap;padding-top:2px;color:var(--prts-muted);font:10px/1.5 var(--prts-mono); }
|
|
782
|
-
html[data-dsh-prts] [data-prts-reply-entry] span { min-width:0;flex:1;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;font:12px/1.6 var(--prts-body);overflow-wrap:anywhere; }
|
|
779
|
+
html[data-dsh-prts] :is([data-prts-reply-entry],[data-prts-tool-entry]) { display:flex;align-items:flex-start;gap:8px;width:100%;padding:6px 8px;text-align:left;border:1px solid var(--prts-line);border-radius:5px;background:transparent;color:var(--prts-ink);cursor:pointer; }
|
|
780
|
+
html[data-dsh-prts] :is([data-prts-reply-entry],[data-prts-tool-entry]):is(:hover,:focus-visible,[data-prts-reply-active]) { background:color-mix(in srgb,var(--prts-ink) 7%,transparent);border-color:var(--prts-muted); }
|
|
781
|
+
html[data-dsh-prts] :is([data-prts-reply-entry],[data-prts-tool-entry]) small { flex:0 0 auto;white-space:nowrap;padding-top:2px;color:var(--prts-muted);font:10px/1.5 var(--prts-mono); }
|
|
782
|
+
html[data-dsh-prts] :is([data-prts-reply-entry],[data-prts-tool-entry]) span { min-width:0;flex:1;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;font:12px/1.6 var(--prts-body);overflow-wrap:anywhere; }
|
|
783
783
|
html[data-dsh-prts] [data-prts-reply-detail]:not([data-prts-preview-question]) > [data-prts-preview-body] { max-height:190px; }
|
|
784
784
|
|
|
785
785
|
html[data-dsh-prts] [data-prts-preview-marker] { display:none;flex:none;margin-left:auto;color:var(--prts-muted);font:600 9px/1.4 var(--prts-body); }
|
|
@@ -864,3 +864,25 @@ html[data-dsh-prts] [data-prts-summary-dismissed] [data-prts-session-summary-pop
|
|
|
864
864
|
pointer-events: none !important;
|
|
865
865
|
transition: none;
|
|
866
866
|
}
|
|
867
|
+
|
|
868
|
+
html[data-dsh-prts] [data-prts-preview-tool] { margin-top:8px; }
|
|
869
|
+
html[data-dsh-prts] [data-prts-preview-tool][hidden] { display:none; }
|
|
870
|
+
html[data-dsh-prts] [data-prts-preview-tool] > button { box-sizing:border-box;height:32px;align-items:center;gap:7px;padding:4px 8px;overflow:hidden;border-left:2px solid var(--prts-cyan);background:color-mix(in srgb,var(--prts-cyan) 7%,transparent); }
|
|
871
|
+
html[data-dsh-prts] [data-prts-tool-entry]::before { content:">_";flex:none;color:var(--prts-cyan);font:600 11px var(--prts-mono); }
|
|
872
|
+
html[data-dsh-prts] [data-prts-preview-tool] small { padding:0;color:var(--prts-cyan); }
|
|
873
|
+
html[data-dsh-prts] [data-prts-preview-tool] span { display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:11px/1.6 var(--prts-mono); }
|
|
874
|
+
|
|
875
|
+
html[data-dsh-prts] [data-prts-summary-reply] > [data-prts-preview-body]:empty { display:none;margin:0; }
|
|
876
|
+
|
|
877
|
+
html[data-dsh-prts] [data-prts-preview-message] > [data-prts-reply-entry] { border-color:transparent;border-radius:3px; }
|
|
878
|
+
html[data-dsh-prts] [data-prts-reply-entry] small { display:flex;align-items:center;gap:4px;color:#8aaee0; }
|
|
879
|
+
html[data-dsh-prts] [data-prts-reply-entry] svg { width:14px;height:14px;flex:none;fill:none;stroke:currentColor;stroke-width:1.2; }
|
|
880
|
+
|
|
881
|
+
html[data-dsh-prts][data-prts-scheme="light"] [data-prts-reply-entry] small { color:#476b9c; }
|
|
882
|
+
|
|
883
|
+
html[data-dsh-prts] [data-prts-summary-reply][data-prts-tool-history] > [data-prts-preview-body] { height:190px;max-height:190px; }
|
|
884
|
+
html[data-dsh-prts] [data-prts-tool-record] { padding:8px 0;border-bottom:1px solid var(--prts-line); }
|
|
885
|
+
html[data-dsh-prts] [data-prts-tool-record] > div { display:flex;align-items:baseline;gap:8px; }
|
|
886
|
+
html[data-dsh-prts] [data-prts-tool-record] b { flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--prts-cyan);font:11px/1.6 var(--prts-mono); }
|
|
887
|
+
html[data-dsh-prts] [data-prts-tool-record] small { flex:none;color:var(--prts-muted);font-size:10px; }
|
|
888
|
+
html[data-dsh-prts] [data-prts-tool-record] p { display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;margin:4px 0 0; }
|
|
@@ -919,3 +919,13 @@ html[data-dsh-prts] [data-prts-preview-settings] {
|
|
|
919
919
|
& [data-prts-setting-row] { gap:12px; }
|
|
920
920
|
}
|
|
921
921
|
}
|
|
922
|
+
|
|
923
|
+
html[data-dsh-prts] [data-prts-theme-settings] [data-prts-ai-tools] { padding:0 20px 20px;font:12px/1.75 var(--prts-body);overflow-wrap:anywhere; }
|
|
924
|
+
html[data-dsh-prts] [data-prts-ai-tools] > section { padding:16px 0;border-top:1px solid var(--prts-line); }
|
|
925
|
+
html[data-dsh-prts] [data-prts-ai-tools] h3 { margin:0 0 8px;font-size:14px; }
|
|
926
|
+
html[data-dsh-prts] [data-prts-ai-tools] :is(p,dd,small) { color:var(--prts-muted); }
|
|
927
|
+
html[data-dsh-prts] [data-prts-ai-tools] h3 small { margin-left:8px;font-size:11px;font-weight:400; }
|
|
928
|
+
html[data-dsh-prts] [data-prts-ai-tools] blockquote { margin:12px 0;padding:8px 12px;border-left:2px solid var(--prts-cyan);background:color-mix(in srgb,var(--prts-cyan) 5%,transparent); }
|
|
929
|
+
html[data-dsh-prts] [data-prts-ai-tools] summary { cursor:pointer; }
|
|
930
|
+
html[data-dsh-prts] [data-prts-ai-tools] dt { margin-top:12px; }
|
|
931
|
+
html[data-dsh-prts] [data-prts-ai-tools] dd { margin:3px 0 0; }
|