@lemoncat7/dsh-knowledge 1.0.10 → 1.0.11
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/lib/api.js +22 -4
- package/lib/api.js.map +1 -1
- package/lib/client.js +1 -1
- package/lib/client.js.map +1 -1
- package/lib/domain.d.ts +8 -1
- package/lib/domain.d.ts.map +1 -1
- package/lib/domain.js +15 -2
- package/lib/domain.js.map +1 -1
- package/lib/extraction.d.ts.map +1 -1
- package/lib/extraction.js +11 -9
- package/lib/extraction.js.map +1 -1
- package/lib/local-provider.d.ts.map +1 -1
- package/lib/local-provider.js +75 -11
- package/lib/local-provider.js.map +1 -1
- package/lib/tools.js +1 -0
- package/lib/tools.js.map +1 -1
- package/package.json +1 -1
- package/web/app.js +65 -49
- package/web/styles.css +14 -41
package/web/app.js
CHANGED
|
@@ -61,6 +61,7 @@ const state = {
|
|
|
61
61
|
editor: null,
|
|
62
62
|
},
|
|
63
63
|
candidates: [],
|
|
64
|
+
candidateTargets: new Map(),
|
|
64
65
|
candidateStatus: 'pending',
|
|
65
66
|
settings: { writebackPolicy: 'conservative', updatedAt: '' },
|
|
66
67
|
settingsSaving: false,
|
|
@@ -93,6 +94,7 @@ function installHostThemeBridge() {
|
|
|
93
94
|
}
|
|
94
95
|
root.dataset.dshHostTheme = 'true'
|
|
95
96
|
root.style.colorScheme = message.colorScheme
|
|
97
|
+
root.style.setProperty('--dialog-surface', message.colorScheme === 'dark' ? '#1c1c1e' : '#ffffff')
|
|
96
98
|
document.querySelector('meta[name="color-scheme"]')?.setAttribute('content', message.colorScheme)
|
|
97
99
|
const background = root.style.getPropertyValue('--bg')
|
|
98
100
|
if (background) document.querySelector('meta[name="theme-color"]')?.setAttribute('content', background)
|
|
@@ -310,7 +312,7 @@ function renderLogin(message = '') {
|
|
|
310
312
|
|
|
311
313
|
function signOut() {
|
|
312
314
|
sessionStorage.removeItem(TOKEN_KEY)
|
|
313
|
-
Object.assign(state, { token: '', stats: null, overview: null, knowledgeBases: [], mounts: [], resolvedMounts: [], entries: [], documents: [], candidates: [], settings: { writebackPolicy: 'conservative', updatedAt: '' }, tokens: [] })
|
|
315
|
+
Object.assign(state, { token: '', stats: null, overview: null, knowledgeBases: [], mounts: [], resolvedMounts: [], entries: [], documents: [], candidates: [], candidateTargets: new Map(), settings: { writebackPolicy: 'conservative', updatedAt: '' }, tokens: [] })
|
|
314
316
|
if (AUTH_MODE === 'same-origin') void boot()
|
|
315
317
|
else renderLogin()
|
|
316
318
|
}
|
|
@@ -543,6 +545,12 @@ async function loadCandidates() {
|
|
|
543
545
|
ensureKnowledgeBases(),
|
|
544
546
|
])
|
|
545
547
|
state.candidates = candidates
|
|
548
|
+
const targetIds = [...new Set(candidates.map(candidate => candidate.targetId).filter(Boolean))]
|
|
549
|
+
const targets = await Promise.all(targetIds.map(async id => {
|
|
550
|
+
try { return [id, await api(`entries/${encodeURIComponent(id)}`)] }
|
|
551
|
+
catch { return [id, null] }
|
|
552
|
+
}))
|
|
553
|
+
state.candidateTargets = new Map(targets)
|
|
546
554
|
if (!state.stats) await refreshStats()
|
|
547
555
|
}
|
|
548
556
|
|
|
@@ -756,11 +764,11 @@ function renderKnowledgeBases() {
|
|
|
756
764
|
type: 'button', role: 'tab', class: 'tab', 'aria-selected': String(state.knowledgeBaseView === id),
|
|
757
765
|
onClick: () => { state.knowledgeBaseView = id; renderShell() },
|
|
758
766
|
}, element('span', {}, label), element('span', { class: 'tab-count' }, count)))),
|
|
759
|
-
|
|
767
|
+
actionButton('默认回写模型', () => openGlobalWritebackModelEditor(), 'ghost small'),
|
|
760
768
|
),
|
|
761
|
-
element('p', {}, state.settings.
|
|
762
|
-
?
|
|
763
|
-
: '
|
|
769
|
+
element('p', {}, state.settings.writebackProvider && state.settings.writebackModel
|
|
770
|
+
? `未单独指定模型的知识库使用 ${state.settings.writebackProvider} / ${state.settings.writebackModel}。`
|
|
771
|
+
: '未单独指定模型的知识库跟随当前会话模型。'),
|
|
764
772
|
)
|
|
765
773
|
return element('div', { class: 'bases-page' },
|
|
766
774
|
switcher,
|
|
@@ -810,41 +818,23 @@ function renderKnowledgeBases() {
|
|
|
810
818
|
)
|
|
811
819
|
}
|
|
812
820
|
|
|
813
|
-
function
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
async function updateWritebackPolicy(writebackPolicy) {
|
|
833
|
-
if (state.settingsSaving || state.settings.writebackPolicy === writebackPolicy) return
|
|
834
|
-
const previous = state.settings
|
|
835
|
-
state.settings = { ...state.settings, writebackPolicy }
|
|
836
|
-
state.settingsSaving = true
|
|
837
|
-
renderShell()
|
|
838
|
-
try {
|
|
839
|
-
state.settings = await api('settings', { method: 'PUT', body: { patch: { writebackPolicy } } })
|
|
840
|
-
showToast(`全局回写策略已切换为${writebackPolicy === 'conservative' ? '严谨' : '主动'}模式`)
|
|
841
|
-
} catch (error) {
|
|
842
|
-
state.settings = previous
|
|
843
|
-
showToast(friendlyError(error), 'error')
|
|
844
|
-
} finally {
|
|
845
|
-
state.settingsSaving = false
|
|
846
|
-
renderShell()
|
|
847
|
-
}
|
|
821
|
+
function openGlobalWritebackModelEditor() {
|
|
822
|
+
const custom = element('input', { type: 'checkbox', checked: Boolean(state.settings.writebackProvider && state.settings.writebackModel) })
|
|
823
|
+
const provider = formField('模型提供方(Provider)', 'input', state.settings.writebackProvider || '', { maxlength: 100 })
|
|
824
|
+
const model = formField('模型名称(Model)', 'input', state.settings.writebackModel || '', { maxlength: 200 })
|
|
825
|
+
const form = element('form', { class: 'form-grid' },
|
|
826
|
+
element('label', { class: 'check-option span-2' }, custom, element('span', {}, element('strong', {}, '使用全局默认模型'), element('small', {}, '关闭后,未单独配置的知识库跟随当前会话模型。'))),
|
|
827
|
+
provider.wrapper, model.wrapper,
|
|
828
|
+
)
|
|
829
|
+
const sync = () => { provider.input.disabled = model.input.disabled = !custom.checked; provider.input.required = model.input.required = custom.checked }
|
|
830
|
+
custom.addEventListener('change', sync); sync()
|
|
831
|
+
openSheet({ title: '默认回写模型', description: '知识库自己的模型优先于这里;这里留空则跟随当前会话。', body: form, primaryLabel: '保存', onPrimary: async () => {
|
|
832
|
+
if (!form.reportValidity()) return false
|
|
833
|
+
state.settings = await api('settings', { method: 'PUT', body: { patch: custom.checked
|
|
834
|
+
? { writebackProvider: provider.input.value.trim(), writebackModel: model.input.value.trim() }
|
|
835
|
+
: { writebackProvider: null, writebackModel: null } } })
|
|
836
|
+
showToast('默认回写模型已更新。'); renderShell(); return true
|
|
837
|
+
} })
|
|
848
838
|
}
|
|
849
839
|
|
|
850
840
|
function contextPill(label, value) {
|
|
@@ -869,7 +859,8 @@ function renderKnowledgeBaseCard(base) {
|
|
|
869
859
|
: element('span', { class: 'tag is-empty' }, '无默认标签'),
|
|
870
860
|
hiddenTagCount ? element('span', { class: 'tag' }, `+${hiddenTagCount}`) : null),
|
|
871
861
|
element('div', { class: 'base-card-meta' },
|
|
872
|
-
element('span', {}, element('strong', {}, '
|
|
862
|
+
element('span', {}, element('strong', {}, '回写策略'), base.writebackPolicy === 'proactive' ? '主动' : '严谨'),
|
|
863
|
+
element('span', {}, element('strong', {}, '回写模型'), base.writebackProvider && base.writebackModel ? `${base.writebackProvider} / ${base.writebackModel}` : state.settings.writebackProvider && state.settings.writebackModel ? `默认 · ${state.settings.writebackProvider} / ${state.settings.writebackModel}` : '跟随当前会话'),
|
|
873
864
|
base.extractionInstructions ? element('span', {}, element('strong', {}, '提取规则'), '已设置') : null,
|
|
874
865
|
),
|
|
875
866
|
element('div', { class: 'base-card-actions' },
|
|
@@ -1283,6 +1274,9 @@ function renderCandidates() {
|
|
|
1283
1274
|
|
|
1284
1275
|
function renderCandidateCard(candidate) {
|
|
1285
1276
|
const pending = candidate.status === 'pending'
|
|
1277
|
+
const conflictTarget = candidate.action === 'conflict' && candidate.targetId
|
|
1278
|
+
? state.candidateTargets.get(candidate.targetId)
|
|
1279
|
+
: null
|
|
1286
1280
|
const targetDocument = candidate.action === 'create'
|
|
1287
1281
|
? `新文档“${candidate.draft.title}”`
|
|
1288
1282
|
: `文档“${candidate.draft.title}”`
|
|
@@ -1298,8 +1292,13 @@ function renderCandidateCard(candidate) {
|
|
|
1298
1292
|
),
|
|
1299
1293
|
badge(STATUS_LABELS[candidate.status], candidate.status === 'approved' ? 'success' : candidate.status === 'rejected' ? 'danger' : 'warning'),
|
|
1300
1294
|
),
|
|
1301
|
-
element('div', { class:
|
|
1302
|
-
|
|
1295
|
+
element('div', { class: `candidate-body${candidate.action === 'conflict' ? ' is-conflict' : ''}` },
|
|
1296
|
+
candidate.action === 'conflict'
|
|
1297
|
+
? element('div', { class: 'candidate-comparison' },
|
|
1298
|
+
element('section', {}, element('strong', {}, '当前知识'), element('pre', {}, conflictTarget?.body || '目标文档不可用,请刷新后重试。')),
|
|
1299
|
+
element('section', {}, element('strong', {}, '候选内容'), element('pre', {}, candidate.draft.body)),
|
|
1300
|
+
)
|
|
1301
|
+
: element('p', { class: 'candidate-content' }, candidate.draft.body),
|
|
1303
1302
|
element('div', { class: 'candidate-reason' },
|
|
1304
1303
|
element('strong', {}, '文档变更'),
|
|
1305
1304
|
element('span', { class: 'candidate-target' }, `${candidate.action === 'create' ? '创建' : '更新'}${targetDocument}`),
|
|
@@ -1310,7 +1309,9 @@ function renderCandidateCard(candidate) {
|
|
|
1310
1309
|
pending ? element('div', {},
|
|
1311
1310
|
actionButton('拒绝', () => reviewCandidate(candidate, 'reject'), 'danger small'),
|
|
1312
1311
|
actionButton('编辑并通过', () => openEntryEditor(undefined, candidate), 'small'),
|
|
1313
|
-
|
|
1312
|
+
candidate.action === 'conflict'
|
|
1313
|
+
? actionButton('合并追加', () => reviewCandidate(candidate, 'approve', 'merge'), 'primary small')
|
|
1314
|
+
: actionButton('通过', () => reviewCandidate(candidate, 'approve'), 'primary small'),
|
|
1314
1315
|
) : null,
|
|
1315
1316
|
),
|
|
1316
1317
|
)
|
|
@@ -1399,12 +1400,16 @@ function parseTags(value) {
|
|
|
1399
1400
|
}
|
|
1400
1401
|
|
|
1401
1402
|
function openKnowledgeBaseEditor(base) {
|
|
1402
|
-
const source = base || { name: '', description: '', defaultTags: [], extractionInstructions: '' }
|
|
1403
|
+
const source = base || { name: '', description: '', defaultTags: [], extractionInstructions: '', writebackPolicy: 'conservative' }
|
|
1403
1404
|
const form = element('form', { class: 'form-grid' })
|
|
1404
1405
|
const name = formField('名称', 'input', source.name, { required: true, maxlength: 100, placeholder: '例如:项目规范' })
|
|
1405
1406
|
const description = formField('回写匹配描述', 'textarea', source.description, { maxlength: 2000, placeholder: '描述什么样的对话才属于这个库。例如:只记录 dsh-knowledge 项目的架构决策和部署规范' })
|
|
1406
1407
|
const tags = formField('默认标签', 'input', source.defaultTags.join(', '), { placeholder: 'project-rule, backend' })
|
|
1407
1408
|
const instructions = formField('提取要求', 'textarea', source.extractionInstructions, { maxlength: 4000, placeholder: '例如:只收录已确认、可跨会话复用的项目约定' })
|
|
1409
|
+
const policy = selectField('回写策略', [
|
|
1410
|
+
{ value: 'conservative', label: '严谨(高置信度直写)' },
|
|
1411
|
+
{ value: 'proactive', label: '主动(更积极沉淀)' },
|
|
1412
|
+
], source.writebackPolicy || 'conservative')
|
|
1408
1413
|
const customModel = element('input', { type: 'checkbox', checked: Boolean(source.writebackProvider && source.writebackModel) })
|
|
1409
1414
|
const provider = formField('模型提供方(Provider)', 'input', source.writebackProvider || '', { maxlength: 100, placeholder: '例如:cli' })
|
|
1410
1415
|
const model = formField('模型名称(Model)', 'input', source.writebackModel || '', { maxlength: 200, placeholder: '例如:kimi-k2.7-code' })
|
|
@@ -1419,7 +1424,7 @@ function openKnowledgeBaseEditor(base) {
|
|
|
1419
1424
|
updateModelFields()
|
|
1420
1425
|
for (const field of [name, description, tags, instructions]) field.wrapper.classList.add('span-2')
|
|
1421
1426
|
form.append(
|
|
1422
|
-
name.wrapper, description.wrapper, tags.wrapper, instructions.wrapper,
|
|
1427
|
+
name.wrapper, description.wrapper, tags.wrapper, instructions.wrapper, policy.wrapper,
|
|
1423
1428
|
element('label', { class: 'check-option span-2' }, customModel, element('span', {},
|
|
1424
1429
|
element('strong', {}, '为这个知识库指定回写模型'),
|
|
1425
1430
|
element('small', {}, '关闭时自动使用当前会话正在使用的模型。'),
|
|
@@ -1438,6 +1443,7 @@ function openKnowledgeBaseEditor(base) {
|
|
|
1438
1443
|
description: description.input.value.trim(),
|
|
1439
1444
|
defaultTags: parseTags(tags.input.value),
|
|
1440
1445
|
extractionInstructions: instructions.input.value.trim(),
|
|
1446
|
+
writebackPolicy: policy.input.value,
|
|
1441
1447
|
writebackProvider: customModel.checked ? provider.input.value.trim() : '',
|
|
1442
1448
|
writebackModel: customModel.checked ? model.input.value.trim() : '',
|
|
1443
1449
|
}
|
|
@@ -1710,7 +1716,9 @@ function openEntryEditor(entry, candidate) {
|
|
|
1710
1716
|
confidence: Number(confidenceInput.value),
|
|
1711
1717
|
...(source.source ? { source: source.source } : {}),
|
|
1712
1718
|
}
|
|
1713
|
-
if (candidate) await api(`candidates/${encodeURIComponent(candidate.id)}/review`, {
|
|
1719
|
+
if (candidate) await api(`candidates/${encodeURIComponent(candidate.id)}/review`, {
|
|
1720
|
+
method: 'POST', body: { decision: 'approve', draft, ...(candidate.action === 'conflict' ? { resolution: 'merge' } : {}) },
|
|
1721
|
+
})
|
|
1714
1722
|
else if (entry) await api(`entries/${encodeURIComponent(entry.id)}`, { method: 'PUT', body: { draft } })
|
|
1715
1723
|
else await api('entries', { method: 'POST', body: { draft } })
|
|
1716
1724
|
showToast(candidate ? '候选已通过并写入知识库。' : '知识已保存。')
|
|
@@ -1733,14 +1741,22 @@ function selectField(label, options, value) {
|
|
|
1733
1741
|
return { input, wrapper: element('div', { class: 'field' }, element('label', {}, label), input) }
|
|
1734
1742
|
}
|
|
1735
1743
|
|
|
1736
|
-
async function reviewCandidate(candidate, decision) {
|
|
1744
|
+
async function reviewCandidate(candidate, decision, resolution) {
|
|
1737
1745
|
const approve = decision === 'approve'
|
|
1738
1746
|
openConfirm({
|
|
1739
1747
|
title: approve ? '通过这条候选?' : '拒绝这条候选?',
|
|
1740
1748
|
message: approve ? '通过后内容会立即写入知识库,并参与后续对话召回。' : '拒绝后会保留审核记录,但不会进入知识库。',
|
|
1741
1749
|
confirmLabel: approve ? '确认通过' : '确认拒绝', danger: !approve,
|
|
1742
1750
|
onConfirm: async () => {
|
|
1743
|
-
await api(`candidates/${encodeURIComponent(candidate.id)}/review`, {
|
|
1751
|
+
const reviewed = await api(`candidates/${encodeURIComponent(candidate.id)}/review`, {
|
|
1752
|
+
method: 'POST', body: { decision, ...(resolution ? { resolution } : {}) },
|
|
1753
|
+
})
|
|
1754
|
+
if (approve && reviewed.status === 'pending' && reviewed.action === 'conflict') {
|
|
1755
|
+
showToast('审核期间知识已变化,候选已转为冲突项,请比较后重新确认。')
|
|
1756
|
+
state.stats = null
|
|
1757
|
+
await navigate('candidates')
|
|
1758
|
+
return
|
|
1759
|
+
}
|
|
1744
1760
|
showToast(approve ? '候选已通过。' : '候选已拒绝。')
|
|
1745
1761
|
state.stats = null
|
|
1746
1762
|
await navigate('candidates')
|
package/web/styles.css
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
--surface-raised: #ffffff;
|
|
8
8
|
--surface-soft: #f0f0f3;
|
|
9
9
|
--surface-hover: #f5f5f8;
|
|
10
|
+
--dialog-surface: #ffffff;
|
|
10
11
|
--text: #1d1d1f;
|
|
11
12
|
--text-secondary: #4c4c50;
|
|
12
13
|
--text-tertiary: #6e6e73;
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
--surface-raised: #242426;
|
|
35
36
|
--surface-soft: #2c2c2e;
|
|
36
37
|
--surface-hover: #303033;
|
|
38
|
+
--dialog-surface: #1c1c1e;
|
|
37
39
|
--text: #f5f5f7;
|
|
38
40
|
--text-secondary: #c7c7cc;
|
|
39
41
|
--text-tertiary: #98989d;
|
|
@@ -106,7 +108,7 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
106
108
|
border: 1px solid var(--border);
|
|
107
109
|
border-radius: 20px;
|
|
108
110
|
padding: 32px;
|
|
109
|
-
background: var(--surface
|
|
111
|
+
background: var(--dialog-surface);
|
|
110
112
|
box-shadow: var(--shadow);
|
|
111
113
|
}
|
|
112
114
|
|
|
@@ -127,11 +129,11 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
127
129
|
.login-card > p { margin: 0 0 24px; color: var(--text-secondary); }
|
|
128
130
|
|
|
129
131
|
.field { display: grid; gap: 7px; }
|
|
130
|
-
.field + .field { margin-top: 16px; }
|
|
131
132
|
.field label { color: var(--text-secondary); font-size: 13px; font-weight: 600; }
|
|
132
133
|
.field-hint { color: var(--text-tertiary); font-size: 12px; }
|
|
133
134
|
|
|
134
135
|
.input, .select, .textarea {
|
|
136
|
+
box-sizing: border-box;
|
|
135
137
|
width: 100%;
|
|
136
138
|
border: 1px solid var(--border-strong);
|
|
137
139
|
border-radius: 9px;
|
|
@@ -141,7 +143,7 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
141
143
|
transition: border-color .15s ease, box-shadow .15s ease;
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
.input, .select { min-height: 40px; }
|
|
146
|
+
.input, .select { height: 40px; min-height: 40px; font: inherit; line-height: 20px; }
|
|
145
147
|
.textarea { min-height: 132px; resize: vertical; }
|
|
146
148
|
.input::placeholder, .textarea::placeholder { color: var(--text-tertiary); }
|
|
147
149
|
.input:focus, .select:focus, .textarea:focus { border-color: var(--accent); }
|
|
@@ -169,6 +171,7 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
169
171
|
.button.ghost { border-color: transparent; background: transparent; color: var(--text-secondary); }
|
|
170
172
|
.button.small { min-height: 32px; padding: 5px 10px; font-size: 12px; }
|
|
171
173
|
.login-card .button { width: 100%; margin-top: 20px; }
|
|
174
|
+
.login-card .field + .field { margin-top: 16px; }
|
|
172
175
|
|
|
173
176
|
.app-shell { min-height: 100vh; min-height: 100dvh; display: grid; grid-template-columns: var(--sidebar-width) 6px minmax(0, 1fr); }
|
|
174
177
|
|
|
@@ -392,41 +395,6 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
392
395
|
.workspace-switcher p { max-width: 56ch; margin: 0; color: var(--text-tertiary); font-size: 12px; text-align: right; }
|
|
393
396
|
.workspace-tabs { flex: none; }
|
|
394
397
|
.workspace-tabs .tab { display: inline-flex; align-items: center; gap: 7px; }
|
|
395
|
-
.writeback-policy-control {
|
|
396
|
-
min-height: 36px;
|
|
397
|
-
display: inline-flex;
|
|
398
|
-
align-items: center;
|
|
399
|
-
gap: 7px;
|
|
400
|
-
border-left: 1px solid var(--border);
|
|
401
|
-
padding-left: 10px;
|
|
402
|
-
}
|
|
403
|
-
.writeback-policy-label { color: var(--text-tertiary); font-size: 11px; font-weight: 600; white-space: nowrap; }
|
|
404
|
-
.writeback-policy-segments {
|
|
405
|
-
display: inline-flex;
|
|
406
|
-
gap: 2px;
|
|
407
|
-
border: 1px solid var(--border);
|
|
408
|
-
border-radius: 9px;
|
|
409
|
-
padding: 2px;
|
|
410
|
-
background: color-mix(in srgb, var(--surface-soft) 72%, transparent);
|
|
411
|
-
}
|
|
412
|
-
.writeback-policy-option {
|
|
413
|
-
min-width: 48px;
|
|
414
|
-
min-height: 30px;
|
|
415
|
-
border: 0;
|
|
416
|
-
border-radius: 7px;
|
|
417
|
-
padding: 5px 10px;
|
|
418
|
-
background: transparent;
|
|
419
|
-
color: var(--text-tertiary);
|
|
420
|
-
font-size: 11px;
|
|
421
|
-
font-weight: 620;
|
|
422
|
-
transition: background .14s ease, color .14s ease, box-shadow .14s ease;
|
|
423
|
-
}
|
|
424
|
-
.writeback-policy-option:hover:not(:disabled) { background: var(--surface-hover); color: var(--text); }
|
|
425
|
-
.writeback-policy-option[aria-checked="true"] {
|
|
426
|
-
background: var(--surface-raised);
|
|
427
|
-
color: var(--text);
|
|
428
|
-
box-shadow: 0 1px 4px rgb(20 25 35 / 10%);
|
|
429
|
-
}
|
|
430
398
|
.tab-count { min-width: 20px; border-radius: 6px; padding: 1px 6px; background: color-mix(in srgb, var(--text-tertiary) 10%, transparent); color: var(--text-tertiary); font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
431
399
|
.tab[aria-selected="true"] .tab-count { background: var(--accent-soft); color: var(--accent); }
|
|
432
400
|
.library-management, .mount-section { min-width: 0; }
|
|
@@ -678,6 +646,11 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
678
646
|
.candidate-header h3 { margin: 8px 0 0; font-size: 15px; }
|
|
679
647
|
.candidate-body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(240px, .55fr); gap: 20px; padding: 0 18px 17px; }
|
|
680
648
|
.candidate-content { margin: 0; color: var(--text-secondary); white-space: pre-wrap; }
|
|
649
|
+
.candidate-body.is-conflict { grid-template-columns: 1fr; }
|
|
650
|
+
.candidate-comparison { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
651
|
+
.candidate-comparison section { min-width: 0; border: 1px solid var(--border); border-radius: 9px; padding: 12px; background: var(--surface-soft); }
|
|
652
|
+
.candidate-comparison strong { display: block; margin-bottom: 8px; color: var(--text); font-size: 12px; }
|
|
653
|
+
.candidate-comparison pre { overflow: auto; max-height: 280px; margin: 0; color: var(--text-secondary); font: inherit; line-height: 1.65; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
681
654
|
.candidate-reason { border-left: 2px solid var(--border-strong); padding-left: 13px; color: var(--text-secondary); font-size: 12px; }
|
|
682
655
|
.candidate-reason strong { display: block; margin-bottom: 5px; color: var(--text-tertiary); font-size: 10px; text-transform: uppercase; letter-spacing: .08em; }
|
|
683
656
|
.candidate-target { display: block; margin-bottom: 13px; border-radius: 7px; padding: 7px 9px; background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
@@ -727,7 +700,7 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
727
700
|
flex-direction: column;
|
|
728
701
|
border: 1px solid var(--border-strong);
|
|
729
702
|
border-radius: 18px;
|
|
730
|
-
background: var(--surface
|
|
703
|
+
background: var(--dialog-surface);
|
|
731
704
|
box-shadow: 0 28px 80px rgb(0 0 0 / 24%);
|
|
732
705
|
}
|
|
733
706
|
.dialog.narrow { width: min(100%, 460px); }
|
|
@@ -752,7 +725,7 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
752
725
|
.dialog-header p { margin: 4px 0 0; color: var(--text-tertiary); font-size: 12px; }
|
|
753
726
|
.dialog-body { overflow: auto; padding: 20px; }
|
|
754
727
|
.dialog-footer { display: flex; justify-content: flex-end; gap: 8px; border-top: 1px solid var(--border); padding: 13px 20px; }
|
|
755
|
-
.form-grid { display: grid; grid-template-columns:
|
|
728
|
+
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; gap: 16px; }
|
|
756
729
|
.form-grid .span-2 { grid-column: 1 / -1; }
|
|
757
730
|
.check-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
758
731
|
.check-option { display: flex; align-items: center; gap: 8px; border: 1px solid var(--border); border-radius: 8px; padding: 9px; }
|
|
@@ -805,6 +778,7 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
805
778
|
}
|
|
806
779
|
|
|
807
780
|
@media (max-width: 760px) {
|
|
781
|
+
.candidate-comparison { grid-template-columns: 1fr; }
|
|
808
782
|
:root { --sidebar-width: 0px; }
|
|
809
783
|
.app-shell { display: block; }
|
|
810
784
|
.app-sidebar-resizer { display: none; }
|
|
@@ -890,7 +864,6 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
890
864
|
.topbar-actions .button.primary::before { content: "+"; font-size: 18px; }
|
|
891
865
|
.workspace-tabs { width: 100%; }
|
|
892
866
|
.workspace-tabs .tab { min-width: 0; flex: 1; justify-content: center; padding-inline: 8px; }
|
|
893
|
-
.writeback-policy-control { width: 100%; justify-content: space-between; border-left: 0; padding-left: 0; }
|
|
894
867
|
.document-toolbar-actions { width: 100%; }
|
|
895
868
|
.document-toolbar-actions .button.primary { margin-left: auto; }
|
|
896
869
|
.form-grid { grid-template-columns: 1fr; }
|