@lemoncat7/dsh-knowledge 0.6.0-alpha.7 → 0.6.0-alpha.8
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 +11 -6
- package/cordis.patch.yml +1 -0
- package/docs/architecture.md +7 -5
- package/lib/api.d.ts +15 -1
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +57 -6
- package/lib/api.js.map +1 -1
- package/lib/client.js +1 -1
- package/lib/client.js.map +2 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +2 -5
- package/lib/config.js.map +1 -1
- package/lib/connection.d.ts +1 -1
- package/lib/connection.d.ts.map +1 -1
- package/lib/connection.js +2 -2
- package/lib/connection.js.map +1 -1
- package/lib/control.d.ts +1 -1
- package/lib/control.d.ts.map +1 -1
- package/lib/control.js +8 -4
- package/lib/control.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +80 -39
- package/lib/index.js.map +1 -1
- package/lib/local-provider.d.ts +1 -0
- package/lib/local-provider.d.ts.map +1 -1
- package/lib/local-provider.js +9 -0
- package/lib/local-provider.js.map +1 -1
- package/lib/service-settings.d.ts +7 -0
- package/lib/service-settings.d.ts.map +1 -0
- package/lib/service-settings.js +39 -0
- package/lib/service-settings.js.map +1 -0
- package/lib/web.d.ts +1 -1
- package/lib/web.d.ts.map +1 -1
- package/lib/web.js +2 -1
- package/lib/web.js.map +1 -1
- package/package.json +1 -1
- package/web/app.js +64 -4
- package/web/index.html +1 -0
- package/web/styles.css +10 -0
package/web/app.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const API_BASE = document.querySelector('meta[name="dsh-knowledge-api"]')?.content || '/knowledge-api/v1'
|
|
2
|
+
const AUTH_MODE = document.querySelector('meta[name="dsh-knowledge-auth-mode"]')?.content || 'bearer'
|
|
2
3
|
const TOKEN_KEY = 'dsh-knowledge.session-token'
|
|
3
4
|
const TYPES = ['preference', 'fact', 'decision', 'procedure', 'lesson']
|
|
4
5
|
const TYPE_LABELS = { preference: '偏好', fact: '事实', decision: '决策', procedure: '流程', lesson: '经验' }
|
|
@@ -51,6 +52,7 @@ const state = {
|
|
|
51
52
|
candidates: [],
|
|
52
53
|
candidateStatus: 'pending',
|
|
53
54
|
tokens: [],
|
|
55
|
+
service: { publicApiEnabled: false, publicApiPrefix: '/knowledge-api/v1' },
|
|
54
56
|
loading: false,
|
|
55
57
|
error: '',
|
|
56
58
|
}
|
|
@@ -132,6 +134,7 @@ function showToast(message, kind = '') {
|
|
|
132
134
|
|
|
133
135
|
async function api(path, options = {}) {
|
|
134
136
|
const headers = { accept: 'application/json', ...(options.body === undefined ? {} : { 'content-type': 'application/json' }) }
|
|
137
|
+
if (AUTH_MODE === 'same-origin') headers['x-dsh-knowledge-client'] = 'management-web'
|
|
135
138
|
if (state.token) headers.authorization = `Bearer ${state.token}`
|
|
136
139
|
const response = await fetch(`${API_BASE}/${path.replace(/^\/+/, '')}`, {
|
|
137
140
|
method: options.method || 'GET',
|
|
@@ -153,6 +156,11 @@ async function api(path, options = {}) {
|
|
|
153
156
|
}
|
|
154
157
|
|
|
155
158
|
async function boot() {
|
|
159
|
+
if (AUTH_MODE === 'same-origin') {
|
|
160
|
+
state.token = ''
|
|
161
|
+
await navigate('overview')
|
|
162
|
+
return
|
|
163
|
+
}
|
|
156
164
|
if (!state.token) {
|
|
157
165
|
renderLogin()
|
|
158
166
|
return
|
|
@@ -213,7 +221,8 @@ function renderLogin(message = '') {
|
|
|
213
221
|
function signOut() {
|
|
214
222
|
sessionStorage.removeItem(TOKEN_KEY)
|
|
215
223
|
Object.assign(state, { token: '', stats: null, overview: null, knowledgeBases: [], mounts: [], resolvedMounts: [], entries: [], documents: [], candidates: [], tokens: [] })
|
|
216
|
-
|
|
224
|
+
if (AUTH_MODE === 'same-origin') void boot()
|
|
225
|
+
else renderLogin()
|
|
217
226
|
}
|
|
218
227
|
|
|
219
228
|
async function navigate(view) {
|
|
@@ -329,7 +338,9 @@ async function loadCandidates() {
|
|
|
329
338
|
}
|
|
330
339
|
|
|
331
340
|
async function loadTokens() {
|
|
332
|
-
|
|
341
|
+
const [tokens, service] = await Promise.all([api('tokens'), api('service')])
|
|
342
|
+
state.tokens = tokens
|
|
343
|
+
state.service = service
|
|
333
344
|
if (!state.stats) await refreshStats()
|
|
334
345
|
}
|
|
335
346
|
|
|
@@ -448,7 +459,7 @@ function renderSidebar() {
|
|
|
448
459
|
id === 'candidates' && pending ? element('span', { class: 'nav-count', 'aria-label': `${pending} 条待审核` }, pending) : null))),
|
|
449
460
|
element('div', { class: 'sidebar-footer' },
|
|
450
461
|
element('div', { class: 'connection' }, element('span', { class: 'status-dot', 'aria-hidden': 'true' }), '知识库已连接'),
|
|
451
|
-
actionButton('退出当前会话', signOut, 'ghost small'),
|
|
462
|
+
AUTH_MODE === 'bearer' ? actionButton('退出当前会话', signOut, 'ghost small') : null,
|
|
452
463
|
),
|
|
453
464
|
)
|
|
454
465
|
}
|
|
@@ -1145,7 +1156,25 @@ function renderCandidateCard(candidate) {
|
|
|
1145
1156
|
}
|
|
1146
1157
|
|
|
1147
1158
|
function renderTokens() {
|
|
1159
|
+
const apiAddress = new URL(state.service.publicApiPrefix, location.origin).href.replace(/\/$/, '')
|
|
1148
1160
|
return element('section', { 'aria-labelledby': 'tokens-heading' },
|
|
1161
|
+
element('div', { class: 'api-access-card' },
|
|
1162
|
+
element('div', { class: 'api-access-heading' },
|
|
1163
|
+
element('div', {}, element('strong', {}, '远程知识库 API'), element('p', {}, state.service.publicApiEnabled
|
|
1164
|
+
? '其他 DSH 客户端可以使用下面的地址和客户端令牌连接。'
|
|
1165
|
+
: '当前仅能在本机管理;开启后其他 DSH 客户端才能连接。')),
|
|
1166
|
+
badge(state.service.publicApiEnabled ? '已开放' : '未开放', state.service.publicApiEnabled ? 'success' : 'warning'),
|
|
1167
|
+
),
|
|
1168
|
+
element('label', { class: 'api-address-label', for: 'knowledge-public-api-address' }, '客户端填写的服务器地址'),
|
|
1169
|
+
element('div', { class: 'api-address-row' },
|
|
1170
|
+
Object.assign(element('input', { id: 'knowledge-public-api-address', class: 'input', readonly: true, value: apiAddress }), { value: apiAddress }),
|
|
1171
|
+
actionButton('复制地址', () => copyText(apiAddress, 'API 地址已复制。'), 'small'),
|
|
1172
|
+
),
|
|
1173
|
+
element('div', { class: 'api-access-actions' },
|
|
1174
|
+
element('small', {}, state.service.publicApiEnabled ? '关闭后已有客户端会立即无法连接,本地管理不受影响。' : '开启远程 API 后,请为每台客户端创建独立令牌。'),
|
|
1175
|
+
actionButton(state.service.publicApiEnabled ? '关闭远程 API' : '开启远程 API', togglePublicApi, state.service.publicApiEnabled ? 'danger small' : 'primary small'),
|
|
1176
|
+
),
|
|
1177
|
+
),
|
|
1149
1178
|
element('div', { class: 'section-heading' },
|
|
1150
1179
|
element('div', {}, element('h2', { id: 'tokens-heading' }, '客户端访问令牌'), element('p', {}, '给其他 DSH 客户端分配最小必要权限;原始令牌只显示一次。')),
|
|
1151
1180
|
actionButton('+ 创建令牌', openTokenCreator, 'primary'),
|
|
@@ -1164,10 +1193,32 @@ function renderTokenRow(token) {
|
|
|
1164
1193
|
element('div', { class: 'permissions' }, token.permissions.map(permission => badge(permission))),
|
|
1165
1194
|
element('small', {}, `创建于 ${formatDate(token.createdAt)}${token.lastUsedAt ? ` · 最近使用 ${formatDate(token.lastUsedAt)}` : ' · 尚未使用'}`),
|
|
1166
1195
|
),
|
|
1167
|
-
revoked
|
|
1196
|
+
revoked
|
|
1197
|
+
? actionButton('永久删除', () => confirmDeleteToken(token), 'danger small')
|
|
1198
|
+
: actionButton('撤销', () => confirmRevokeToken(token), 'danger small'),
|
|
1168
1199
|
)
|
|
1169
1200
|
}
|
|
1170
1201
|
|
|
1202
|
+
async function copyText(value, successMessage) {
|
|
1203
|
+
try { await navigator.clipboard.writeText(value); showToast(successMessage) } catch { showToast('复制失败,请手动选择内容。', 'error') }
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function togglePublicApi() {
|
|
1207
|
+
const enabling = !state.service.publicApiEnabled
|
|
1208
|
+
openConfirm({
|
|
1209
|
+
title: enabling ? '开启远程知识库 API?' : '关闭远程知识库 API?',
|
|
1210
|
+
message: enabling
|
|
1211
|
+
? '开启后,持有有效令牌的其他 DSH 客户端可以连接这台中央知识库。'
|
|
1212
|
+
: '关闭后,所有远程客户端会立即断开;本地知识库和管理台不受影响。',
|
|
1213
|
+
confirmLabel: enabling ? '确认开启' : '确认关闭', danger: !enabling,
|
|
1214
|
+
onConfirm: async () => {
|
|
1215
|
+
state.service = await api('service', { method: 'PUT', body: { publicApiEnabled: enabling } })
|
|
1216
|
+
showToast(enabling ? '远程 API 已开启。' : '远程 API 已关闭。')
|
|
1217
|
+
renderShell()
|
|
1218
|
+
},
|
|
1219
|
+
})
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1171
1222
|
function emptyState(title, description, actionLabel, action) {
|
|
1172
1223
|
return element('div', { class: 'empty-state' }, element('div', {}, element('strong', {}, title), element('p', {}, description), actionLabel ? actionButton(actionLabel, action, 'primary') : null))
|
|
1173
1224
|
}
|
|
@@ -1605,6 +1656,15 @@ function confirmRevokeToken(token) {
|
|
|
1605
1656
|
} })
|
|
1606
1657
|
}
|
|
1607
1658
|
|
|
1659
|
+
function confirmDeleteToken(token) {
|
|
1660
|
+
openConfirm({ title: `永久删除“${token.name}”?`, message: '这条已撤销令牌记录会被永久删除,此操作无法撤销。', confirmLabel: '永久删除', danger: true, onConfirm: async () => {
|
|
1661
|
+
await api(`tokens/${encodeURIComponent(token.id)}`, { method: 'DELETE' })
|
|
1662
|
+
showToast('已撤销令牌已永久删除。')
|
|
1663
|
+
await loadTokens()
|
|
1664
|
+
renderShell()
|
|
1665
|
+
} })
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1608
1668
|
function openConfirm({ title, message, confirmLabel, danger, onConfirm }) {
|
|
1609
1669
|
return openModal({ title, body: element('p', {}, message), primaryLabel: confirmLabel, primaryVariant: danger ? 'danger' : 'primary', onPrimary: async () => { await onConfirm(); return true } })
|
|
1610
1670
|
}
|
package/web/index.html
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<meta name="color-scheme" content="light dark">
|
|
7
7
|
<meta name="theme-color" content="#f4f5f7">
|
|
8
8
|
<meta name="dsh-knowledge-api" content="__DSH_KNOWLEDGE_API_PREFIX__">
|
|
9
|
+
<meta name="dsh-knowledge-auth-mode" content="__DSH_KNOWLEDGE_AUTH_MODE__">
|
|
9
10
|
<meta name="dsh-knowledge-web" content="__DSH_KNOWLEDGE_WEB_PATH__">
|
|
10
11
|
<title>DSH 知识库</title>
|
|
11
12
|
<link rel="stylesheet" href="__DSH_KNOWLEDGE_WEB_PATH__/styles.css?v=__DSH_KNOWLEDGE_ASSET_VERSION__">
|
package/web/styles.css
CHANGED
|
@@ -628,6 +628,15 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
628
628
|
.token-row strong { display: block; }
|
|
629
629
|
.token-row small { color: var(--text-tertiary); }
|
|
630
630
|
.permissions { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
|
|
631
|
+
.api-access-card { display: grid; gap: 14px; margin-bottom: 20px; border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; background: var(--surface); }
|
|
632
|
+
.api-access-heading, .api-access-actions { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
|
|
633
|
+
.api-access-heading strong { font-size: 15px; }
|
|
634
|
+
.api-access-heading p { margin: 4px 0 0; color: var(--text-tertiary); }
|
|
635
|
+
.api-address-label { color: var(--text-secondary); font-size: 12px; font-weight: 600; }
|
|
636
|
+
.api-address-row { display: flex; align-items: center; gap: 8px; }
|
|
637
|
+
.api-address-row .input { min-width: 0; flex: 1; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
638
|
+
.api-access-actions { align-items: center; border-top: 1px solid var(--border); padding-top: 13px; }
|
|
639
|
+
.api-access-actions small { color: var(--text-tertiary); }
|
|
631
640
|
|
|
632
641
|
.dialog-backdrop { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 20px; background: rgb(8 10 15 / 52%); backdrop-filter: blur(4px); }
|
|
633
642
|
.dialog {
|
|
@@ -755,6 +764,7 @@ button:disabled { cursor: not-allowed; opacity: .56; }
|
|
|
755
764
|
}
|
|
756
765
|
|
|
757
766
|
@media (max-width: 520px) {
|
|
767
|
+
.api-access-heading, .api-access-actions, .api-address-row { align-items: stretch; flex-direction: column; }
|
|
758
768
|
body { font-size: 13px; }
|
|
759
769
|
.login-page { padding: 16px; }
|
|
760
770
|
.login-card { padding: 24px; }
|