@shikeai/knowledge 0.8.0 → 0.8.2
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/dist/index.es.js +13071 -12144
- package/lib/client.js +1755 -431
- package/lib/client.js.map +1 -1
- package/lib/index.js +4 -1
- package/package.json +1 -4
package/lib/client.js
CHANGED
|
@@ -4511,8 +4511,9 @@ window.__ModuleLoader__.load({
|
|
|
4511
4511
|
chunkSize: uploadChunk.chunk_size || "",
|
|
4512
4512
|
chunkOverlap: uploadChunk.chunk_overlap || "",
|
|
4513
4513
|
folderId: uploadFolderId
|
|
4514
|
-
}).then(() => {
|
|
4514
|
+
}).then((data) => {
|
|
4515
4515
|
toast("文档已上传,开始解析", "success");
|
|
4516
|
+
if (data?.warning) String(data.warning).split("\n").forEach((m) => toast(m, "warning", 6e3));
|
|
4516
4517
|
setUploading(false);
|
|
4517
4518
|
setPickedFile(null);
|
|
4518
4519
|
setUploadTags([]);
|
|
@@ -6001,13 +6002,65 @@ window.__ModuleLoader__.load({
|
|
|
6001
6002
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Markdown, { text });
|
|
6002
6003
|
}
|
|
6003
6004
|
//#endregion
|
|
6004
|
-
//#region src/api/
|
|
6005
|
+
//#region src/api/modelconfig.js
|
|
6006
|
+
/**
|
|
6007
|
+
* 模型服务配置 REST(对应后端 /kb/model-config*;仅管理员)
|
|
6008
|
+
*
|
|
6009
|
+
* 覆盖向量化(embedding)/ 重排(rerank)/ OCR 视觉识别(ocr)三类模型服务的
|
|
6010
|
+
* 设置页覆盖配置;api_key 语义(后端约定):传空串或原样传回掩码串(含 ****)
|
|
6011
|
+
* = 沿用当前生效密钥,传新值 = 更新密钥
|
|
6012
|
+
*/
|
|
6013
|
+
/**
|
|
6014
|
+
* 读取三类模型服务当前生效配置(含来源 source: db=设置页覆盖 / env=环境变量 / unset=未配置)
|
|
6015
|
+
* @returns {Promise<{ embedding: { source: string, enabled: boolean, config: object }, rerank: { source: string, enabled: boolean, config: object }, ocr: { source: string, enabled: boolean, config: object } }>}
|
|
6016
|
+
*/
|
|
6017
|
+
function getModelConfig() {
|
|
6018
|
+
return request.get("/model-config");
|
|
6019
|
+
}
|
|
6020
|
+
/**
|
|
6021
|
+
* 保存单项配置(config=null 表示恢复默认:清除设置页覆盖并回落部署环境变量配置)
|
|
6022
|
+
* @param {'embedding'|'rerank'|'ocr'} item
|
|
6023
|
+
* @param {object|null} config
|
|
6024
|
+
* @returns {Promise<object>} 保存成功返回同 GET 结构的全量配置
|
|
6025
|
+
*/
|
|
6026
|
+
function saveModelConfig(item, config) {
|
|
6027
|
+
return request.put("/model-config", {
|
|
6028
|
+
item,
|
|
6029
|
+
config
|
|
6030
|
+
});
|
|
6031
|
+
}
|
|
6032
|
+
/**
|
|
6033
|
+
* 连通性测试(用传入 config 实际请求模型服务商)
|
|
6034
|
+
* @param {'embedding'|'rerank'|'ocr'} item
|
|
6035
|
+
* @param {object} config
|
|
6036
|
+
* @returns {Promise<{ message: string }>} 成功 message='连通性测试通过';失败抛 ApiError(msg 为中文错误原因)
|
|
6037
|
+
*/
|
|
6038
|
+
function testModelConfig(item, config) {
|
|
6039
|
+
return request.post("/model-config/test", {
|
|
6040
|
+
item,
|
|
6041
|
+
config
|
|
6042
|
+
});
|
|
6043
|
+
}
|
|
6044
|
+
//#endregion
|
|
6045
|
+
//#region src/api/health.js
|
|
6005
6046
|
/**
|
|
6006
|
-
*
|
|
6047
|
+
* 服务器健康探测:GET <base>/kb/health(免鉴权)→ { status, app, version }
|
|
6048
|
+
*
|
|
6049
|
+
* 直连 fetch(不经 request.js:request 固定使用当前激活服务器基址并附带插件 Token),
|
|
6050
|
+
* 设置页用它逐个探测「已添加」的每台服务器,校验其部署的应用 id 与版本和插件是否匹配
|
|
6051
|
+
* (app / version 由后端 main.go 注入:版本经发布脚本 --build-arg VERSION → ldflags 写入)。
|
|
6007
6052
|
*/
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6053
|
+
function getHealthByBase(base) {
|
|
6054
|
+
const ctrl = new AbortController();
|
|
6055
|
+
const timer = setTimeout(() => ctrl.abort(), 5e3);
|
|
6056
|
+
const url = `${String(base || "").replace(/\/+$/, "")}/kb/health`;
|
|
6057
|
+
return fetch(url, { signal: ctrl.signal }).catch((error) => {
|
|
6058
|
+
if (error && error.name === "AbortError") throw new Error("请求超时");
|
|
6059
|
+
throw new Error("网络请求失败");
|
|
6060
|
+
}).then((res) => res.json()).then((body) => {
|
|
6061
|
+
if (!body || body.code !== 0) throw new Error(body && body.msg || "响应异常");
|
|
6062
|
+
return body.data || {};
|
|
6063
|
+
}).finally(() => clearTimeout(timer));
|
|
6011
6064
|
}
|
|
6012
6065
|
//#endregion
|
|
6013
6066
|
//#region src/manifest.js
|
|
@@ -6022,7 +6075,7 @@ window.__ModuleLoader__.load({
|
|
|
6022
6075
|
/** npm 作用域包名 */
|
|
6023
6076
|
packageName: "@shikeai/knowledge",
|
|
6024
6077
|
/** 版本:单一来源 frontend/package.json,构建时(tsdown/vite)自动内联,禁止在此手写第二份 */
|
|
6025
|
-
version: "0.8.
|
|
6078
|
+
version: "0.8.2",
|
|
6026
6079
|
/** 功能描述 */
|
|
6027
6080
|
description: "时刻AI平台桌面端知识库插件:知识管理(右侧 Sidebar 标签页:首页/知识库详情/文档详情/设置)、成员权限、MCP RAG 检索服务",
|
|
6028
6081
|
/** 作者 */
|
|
@@ -6214,173 +6267,1294 @@ window.__ModuleLoader__.load({
|
|
|
6214
6267
|
});
|
|
6215
6268
|
}
|
|
6216
6269
|
//#endregion
|
|
6217
|
-
//#region src/components/settings/
|
|
6270
|
+
//#region src/components/settings/ModelServiceModal.jsx
|
|
6218
6271
|
/**
|
|
6219
|
-
*
|
|
6272
|
+
* 模型服务编辑弹窗(设置页「模型服务」区块):向量化 / 重排 / OCR 单项配置表单
|
|
6220
6273
|
*
|
|
6221
|
-
* -
|
|
6222
|
-
*
|
|
6223
|
-
*
|
|
6224
|
-
* -
|
|
6225
|
-
*
|
|
6226
|
-
*
|
|
6227
|
-
*
|
|
6274
|
+
* - 服务商预设:常用国内服务商(按服务项收录可用者:向量化=硅基流动/阿里百炼/百度千帆,
|
|
6275
|
+
* 重排=硅基流动/智谱,OCR=DeepSeek/火山方舟/硅基流动/阿里百炼/智谱/Kimi/腾讯混元)+
|
|
6276
|
+
* 本地部署 Infinity(仅向量化与重排,对应 dev/docker-compose.yml 的 bge 容器)/
|
|
6277
|
+
* 本地部署(ollama · glm-ocr容器)(仅 OCR 项显示)/ 自定义;选预设回填地址与协议,
|
|
6278
|
+
* 模型为空或仍是预设默认值时回填推荐模型,ollama 预设另在提示词为空时回填官方触发
|
|
6279
|
+
* 前缀 Text Recognition:(GLM-OCR 为任务专用模型),不动已填密钥;本地部署预设的
|
|
6280
|
+
* 地址主机默认取当前 API 地址的
|
|
6281
|
+
* IP/主机名(API 为 http://127.0.0.1:8081 → http://127.0.0.1:7997),未配置回退 localhost。
|
|
6282
|
+
* 收录约束:向量化仅 1024 维模型(库列 vector(1024) 且
|
|
6283
|
+
* 后端不透传 dimensions 参数);重排仅 Cohere 兼容 {base}/rerank 端点(百炼/千帆
|
|
6284
|
+
* 端点格式不同暂不收录)
|
|
6285
|
+
* - api_key 语义(后端约定):留空或原样传回掩码串(含 ****)= 沿用当前生效密钥,
|
|
6286
|
+
* 传新值 = 更新密钥;表单回显后端掩码串
|
|
6287
|
+
* - 数字字段:留空不提交该字段(后端 0/缺省 = 沿用默认,OCR max_pages 0=不限制)
|
|
6288
|
+
* - 测试连接:以当前表单值调后端连通性测试,成功/失败经全局 Toast 透出后端返回信息
|
|
6289
|
+
* - footer:恢复默认(清除设置页覆盖,回落部署环境变量配置,经确认弹窗二次确认)/ 测试连接 / 保存;
|
|
6290
|
+
* 保存成功回调 onSaved(data),data 为保存后同 GET /kb/model-config 结构的全量配置
|
|
6228
6291
|
*/
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
badge: "skai-kb-badge-amber"
|
|
6235
|
-
},
|
|
6236
|
-
1: {
|
|
6237
|
-
text: "正常",
|
|
6238
|
-
badge: "skai-kb-badge-green"
|
|
6239
|
-
},
|
|
6240
|
-
2: {
|
|
6241
|
-
text: "禁用",
|
|
6242
|
-
badge: "skai-kb-badge-red"
|
|
6243
|
-
}
|
|
6292
|
+
/** 编辑项 → 展示名(与设置页「模型服务」面板行一致) */
|
|
6293
|
+
const ITEM_NAMES = {
|
|
6294
|
+
embedding: "向量化(Embedding)",
|
|
6295
|
+
rerank: "重排(Rerank)",
|
|
6296
|
+
ocr: "OCR(视觉识别)"
|
|
6244
6297
|
};
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6298
|
+
/**
|
|
6299
|
+
* 国内服务商预设(按服务项收录,选后回填地址与推荐模型)。选型约束见文件头注释:
|
|
6300
|
+
* - 向量化:库列固定 vector(1024) 且后端不透传 dimensions 参数,仅收录原生 1024 维模型
|
|
6301
|
+
* (豆包 embedding 2048 维、智谱 embedding-3 默认 2048 维暂不收录)
|
|
6302
|
+
* - 重排:后端拼接 {base}/rerank(Cohere 兼容),仅收录支持该端点的服务商
|
|
6303
|
+
* (百炼为原生 text-rerank、千帆为 /v2/rerankers,路径不兼容暂不收录)
|
|
6304
|
+
* - OCR:OpenAI 兼容 /chat/completions 视觉模型(DeepSeek 无向量化/重排,仅 OCR 项)
|
|
6305
|
+
*/
|
|
6306
|
+
const PROVIDER_PRESETS = {
|
|
6307
|
+
embedding: [
|
|
6308
|
+
{
|
|
6309
|
+
value: "siliconflow",
|
|
6310
|
+
label: "硅基流动 SiliconFlow(推荐)",
|
|
6311
|
+
base: "https://api.siliconflow.cn/v1",
|
|
6312
|
+
model: "BAAI/bge-large-zh-v1.5",
|
|
6313
|
+
hint: "推荐模型:BAAI/bge-large-zh-v1.5(1024 维)"
|
|
6314
|
+
},
|
|
6315
|
+
{
|
|
6316
|
+
value: "dashscope",
|
|
6317
|
+
label: "阿里云百炼(通义)",
|
|
6318
|
+
base: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
6319
|
+
model: "text-embedding-v4",
|
|
6320
|
+
hint: "推荐模型:text-embedding-v4(默认 1024 维,与库内向量列一致)"
|
|
6321
|
+
},
|
|
6322
|
+
{
|
|
6323
|
+
value: "qianfan",
|
|
6324
|
+
label: "百度千帆",
|
|
6325
|
+
base: "https://qianfan.baidubce.com/v2",
|
|
6326
|
+
model: "bge-large-zh",
|
|
6327
|
+
hint: "推荐模型:bge-large-zh(1024 维)"
|
|
6328
|
+
}
|
|
6329
|
+
],
|
|
6330
|
+
rerank: [{
|
|
6331
|
+
value: "siliconflow",
|
|
6332
|
+
label: "硅基流动 SiliconFlow(推荐)",
|
|
6333
|
+
base: "https://api.siliconflow.cn/v1",
|
|
6334
|
+
model: "BAAI/bge-reranker-v2-m3",
|
|
6335
|
+
hint: "推荐模型:BAAI/bge-reranker-v2-m3"
|
|
6336
|
+
}, {
|
|
6337
|
+
value: "zhipu",
|
|
6338
|
+
label: "智谱 AI",
|
|
6339
|
+
base: "https://open.bigmodel.cn/api/paas/v4",
|
|
6340
|
+
model: "rerank",
|
|
6341
|
+
hint: "推荐模型:rerank(智谱 Cohere 兼容重排端点)"
|
|
6342
|
+
}],
|
|
6343
|
+
ocr: [
|
|
6344
|
+
{
|
|
6345
|
+
value: "deepseek",
|
|
6346
|
+
label: "DeepSeek",
|
|
6347
|
+
base: "https://api.deepseek.com",
|
|
6348
|
+
model: "deepseek-flash",
|
|
6349
|
+
hint: "推荐模型:deepseek-flash(V4.1 Flash 原生多模态,支持 image_url 传图;DeepSeek 无向量化/重排服务)"
|
|
6350
|
+
},
|
|
6351
|
+
{
|
|
6352
|
+
value: "ark",
|
|
6353
|
+
label: "火山方舟(字节跳动 · 豆包)",
|
|
6354
|
+
base: "https://ark.cn-beijing.volces.com/api/v3",
|
|
6355
|
+
model: "doubao-seed-2-1-pro-260915",
|
|
6356
|
+
hint: "推荐模型:doubao-seed-2-1-pro-260915(旗舰多模态,支持模型名直调;旧 doubao-seed-1.6 已于 2026-09 停服)"
|
|
6357
|
+
},
|
|
6358
|
+
{
|
|
6359
|
+
value: "siliconflow",
|
|
6360
|
+
label: "硅基流动 SiliconFlow",
|
|
6361
|
+
base: "https://api.siliconflow.cn/v1",
|
|
6362
|
+
model: "Qwen/Qwen2.5-VL-32B-Instruct",
|
|
6363
|
+
hint: "推荐模型:Qwen/Qwen2.5-VL-32B-Instruct 等视觉模型"
|
|
6364
|
+
},
|
|
6365
|
+
{
|
|
6366
|
+
value: "dashscope",
|
|
6367
|
+
label: "阿里云百炼(通义)",
|
|
6368
|
+
base: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
6369
|
+
model: "qwen-vl-max",
|
|
6370
|
+
hint: "推荐模型:qwen-vl-max(或 qwen3-vl 系列)"
|
|
6371
|
+
},
|
|
6372
|
+
{
|
|
6373
|
+
value: "zhipu",
|
|
6374
|
+
label: "智谱 AI",
|
|
6375
|
+
base: "https://open.bigmodel.cn/api/paas/v4",
|
|
6376
|
+
model: "glm-4.6v-flash",
|
|
6377
|
+
hint: "推荐模型:glm-4.6v-flash(免费)/ glm-4.6v / GLM-OCR"
|
|
6378
|
+
},
|
|
6379
|
+
{
|
|
6380
|
+
value: "moonshot",
|
|
6381
|
+
label: "月之暗面 Kimi",
|
|
6382
|
+
base: "https://api.moonshot.cn/v1",
|
|
6383
|
+
model: "kimi-k3.1-pro",
|
|
6384
|
+
hint: "推荐模型:kimi-k3.1-pro(原生视觉;kimi-latest 等旧视觉模型名已下线)"
|
|
6385
|
+
},
|
|
6386
|
+
{
|
|
6387
|
+
value: "hunyuan",
|
|
6388
|
+
label: "腾讯混元",
|
|
6389
|
+
base: "https://api.hunyuan.cloud.tencent.com/v1",
|
|
6390
|
+
model: "hunyuan-vision",
|
|
6391
|
+
hint: "推荐模型:hunyuan-vision"
|
|
6392
|
+
}
|
|
6393
|
+
]
|
|
6394
|
+
};
|
|
6395
|
+
/** 按值查找服务项的服务商预设 */
|
|
6396
|
+
const findPreset = (item, value) => (PROVIDER_PRESETS[item] || []).find((p) => p.value === value);
|
|
6397
|
+
/**
|
|
6398
|
+
* 本地部署预设(dev/docker-compose.yml 的 bge 容器:Infinity 镜像,端口 7997,
|
|
6399
|
+
* 同时加载向量化与重排两个模型,无需 API 密钥)。Infinity 端点无 /v1 前缀,
|
|
6400
|
+
* 向量化与重排均用根路径(后端分别拼 {base}/embeddings、{base}/rerank,Cohere 兼容)。
|
|
6401
|
+
* 主机默认取当前 API 地址的 IP/主机名(本地模型与后端 API 同机部署,
|
|
6402
|
+
* 如 API 为 http://127.0.0.1:8081 则取 127.0.0.1),未配置或解析失败回退 localhost
|
|
6403
|
+
*/
|
|
6404
|
+
function localModelHost() {
|
|
6405
|
+
try {
|
|
6406
|
+
const apiBase = getApiBase();
|
|
6407
|
+
if (!apiBase) return window.location.hostname || "localhost";
|
|
6408
|
+
return new URL(apiBase, window.location.origin).hostname || "localhost";
|
|
6409
|
+
} catch {
|
|
6410
|
+
return "localhost";
|
|
6261
6411
|
}
|
|
6412
|
+
}
|
|
6413
|
+
/** 本地部署 bge 容器地址(Infinity 端点无 /v1 前缀,向量化与重排均用根路径) */
|
|
6414
|
+
const localBase = () => `http://${localModelHost()}:7997`;
|
|
6415
|
+
/** 本地部署标准模型名(与 compose INFINITY_MODEL_ID 一致) */
|
|
6416
|
+
const LOCAL_MODEL = {
|
|
6417
|
+
embedding: "BAAI/bge-large-zh-v1.5",
|
|
6418
|
+
rerank: "BAAI/bge-reranker-v2-m3"
|
|
6419
|
+
};
|
|
6420
|
+
/** 本地部署预设提示 */
|
|
6421
|
+
const LOCAL_HINT = {
|
|
6422
|
+
embedding: "本地 Infinity(bge 容器,端口 7997,向量维度 1024):无需 API 密钥;默认主机与 API 地址一致;同 Docker 网络容器内可改用 http://bge:7997",
|
|
6423
|
+
rerank: "本地 Infinity(bge 容器,端口 7997):无需 API 密钥;默认主机与 API 地址一致;同 Docker 网络容器内可改用 http://bge:7997"
|
|
6424
|
+
};
|
|
6425
|
+
/**
|
|
6426
|
+
* 本地部署 Ollama 预设(dev/docker-compose.yml 的 ollama 容器:端口 11434,启动自动拉取
|
|
6427
|
+
* glm-ocr:q8_0,无需 API 密钥)。GLM-OCR 为任务专用模型,识别提示词需用官方触发前缀
|
|
6428
|
+
* (全文 Text Recognition: / 表格 Table Recognition: / 图 Figure Recognition:),
|
|
6429
|
+
* 自然语言提示词在无文字图上会输出退化内容;预设回填提示词仅在为空时写入(保留自定义)
|
|
6430
|
+
*/
|
|
6431
|
+
const OLLAMA_OCR = {
|
|
6432
|
+
model: "glm-ocr:q8_0",
|
|
6433
|
+
prompt: "Text Recognition:"
|
|
6434
|
+
};
|
|
6435
|
+
/** 本地部署 Ollama 预设提示 */
|
|
6436
|
+
const OLLAMA_HINT = "本地部署 Ollama(glm-ocr 容器,端口 11434):无需 API 密钥;GLM-OCR 为任务专用模型,已回填官方识别前缀 Text Recognition:(表格用 Table Recognition:、图用 Figure Recognition:)";
|
|
6437
|
+
/** 地址域名特征 → 预设值(按已有配置自动识别服务商预设,仅识别当前服务项收录的预设) */
|
|
6438
|
+
const BASE_MARKS = [
|
|
6439
|
+
["siliconflow", "siliconflow"],
|
|
6440
|
+
["volces.com", "ark"],
|
|
6441
|
+
["dashscope.aliyuncs.com", "dashscope"],
|
|
6442
|
+
["qianfan.baidubce.com", "qianfan"],
|
|
6443
|
+
["bigmodel.cn", "zhipu"],
|
|
6444
|
+
["api.moonshot.cn", "moonshot"],
|
|
6445
|
+
["hunyuan.cloud.tencent.com", "hunyuan"],
|
|
6446
|
+
["deepseek.com", "deepseek"]
|
|
6262
6447
|
];
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
const
|
|
6267
|
-
|
|
6268
|
-
const [
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6448
|
+
/** 按已有配置自动识别服务商预设(OCR 协议 / 本地端口 / 地址域名),未命中返回 custom */
|
|
6449
|
+
function detectPreset(item, apiBase, protocol) {
|
|
6450
|
+
if (item === "ocr" && (protocol || "") === "ollama") return "ollama";
|
|
6451
|
+
const base = String(apiBase || "");
|
|
6452
|
+
if ((item === "embedding" || item === "rerank") && base.includes(":7997")) return "local";
|
|
6453
|
+
for (const [mark, value] of BASE_MARKS) if (base.includes(mark) && findPreset(item, value)) return value;
|
|
6454
|
+
return "custom";
|
|
6455
|
+
}
|
|
6456
|
+
/** 全部预设默认模型名:当前模型为空或仍是任一预设默认值时切换服务商才覆盖回填(保留自定义值) */
|
|
6457
|
+
const PRESET_MODELS = [.../* @__PURE__ */ new Set([
|
|
6458
|
+
...Object.values(PROVIDER_PRESETS).flat().map((p) => p.model),
|
|
6459
|
+
...Object.values(LOCAL_MODEL),
|
|
6460
|
+
OLLAMA_OCR.model
|
|
6461
|
+
])];
|
|
6462
|
+
const isPresetModel = (m) => {
|
|
6463
|
+
const s = String(m ?? "").trim();
|
|
6464
|
+
return s === "" || PRESET_MODELS.includes(s);
|
|
6465
|
+
};
|
|
6466
|
+
/** 全部服务商预设默认地址:切换到本地部署等预设时,地址为空或仍是预设默认值才覆盖回填 */
|
|
6467
|
+
const PRESET_BASES = [...new Set(Object.values(PROVIDER_PRESETS).flat().map((p) => p.base))];
|
|
6468
|
+
/** 数字输入 → number(空串/非法值返回 undefined,不提交该字段) */
|
|
6469
|
+
function toNumber(value) {
|
|
6470
|
+
const s = String(value ?? "").trim();
|
|
6471
|
+
if (s === "") return void 0;
|
|
6472
|
+
const n = Number(s);
|
|
6473
|
+
return Number.isFinite(n) ? n : void 0;
|
|
6474
|
+
}
|
|
6475
|
+
function ModelServiceModal({ item, open, config, onClose, onSaved }) {
|
|
6476
|
+
const cfg = config || {};
|
|
6477
|
+
const [preset, setPreset] = (0, react.useState)(() => detectPreset(item, cfg.api_base, cfg.protocol));
|
|
6478
|
+
const [apiBase, setApiBase] = (0, react.useState)(cfg.api_base || "");
|
|
6479
|
+
const [apiKey, setApiKey] = (0, react.useState)(cfg.api_key || "");
|
|
6480
|
+
const [model, setModel] = (0, react.useState)(cfg.model || "");
|
|
6481
|
+
const [protocol, setProtocol] = (0, react.useState)(item === "ocr" ? cfg.protocol || "openai" : "");
|
|
6482
|
+
const [prompt, setPrompt] = (0, react.useState)(cfg.prompt || "");
|
|
6483
|
+
const [dimension, setDimension] = (0, react.useState)(cfg.dimension ?? "");
|
|
6484
|
+
const [batchSize, setBatchSize] = (0, react.useState)(cfg.batch_size ?? "");
|
|
6485
|
+
const [timeoutSec, setTimeoutSec] = (0, react.useState)(cfg.timeout_sec ?? "");
|
|
6486
|
+
const [topN, setTopN] = (0, react.useState)(cfg.top_n ?? "");
|
|
6487
|
+
const [maxPages, setMaxPages] = (0, react.useState)(cfg.max_pages ?? "");
|
|
6488
|
+
const [fieldErrors, setFieldErrors] = (0, react.useState)({});
|
|
6489
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
6490
|
+
const [testing, setTesting] = (0, react.useState)(false);
|
|
6491
|
+
const [resetting, setResetting] = (0, react.useState)(false);
|
|
6492
|
+
const [confirmReset, setConfirmReset] = (0, react.useState)(false);
|
|
6493
|
+
const busy = saving || testing || resetting;
|
|
6494
|
+
/** 按编辑项差异组装提交体(公共 + 各项专属字段;数字留空不提交) */
|
|
6495
|
+
const buildPayload = () => {
|
|
6496
|
+
const payload = {
|
|
6497
|
+
api_base: apiBase.trim(),
|
|
6498
|
+
api_key: apiKey.trim(),
|
|
6499
|
+
model: model.trim()
|
|
6500
|
+
};
|
|
6501
|
+
const put = (key, value) => {
|
|
6502
|
+
const n = toNumber(value);
|
|
6503
|
+
if (n !== void 0) payload[key] = n;
|
|
6504
|
+
};
|
|
6505
|
+
if (item === "embedding") {
|
|
6506
|
+
put("dimension", dimension);
|
|
6507
|
+
put("batch_size", batchSize);
|
|
6508
|
+
put("timeout_sec", timeoutSec);
|
|
6509
|
+
} else if (item === "rerank") {
|
|
6510
|
+
put("top_n", topN);
|
|
6511
|
+
put("timeout_sec", timeoutSec);
|
|
6512
|
+
} else {
|
|
6513
|
+
payload.protocol = protocol || "openai";
|
|
6514
|
+
payload.prompt = prompt.trim();
|
|
6515
|
+
put("timeout_sec", timeoutSec);
|
|
6516
|
+
put("max_pages", maxPages);
|
|
6517
|
+
}
|
|
6518
|
+
return payload;
|
|
6519
|
+
};
|
|
6520
|
+
/** 必填校验:API 地址与模型名,行内错误提示 */
|
|
6521
|
+
const validate = () => {
|
|
6522
|
+
const errs = {};
|
|
6523
|
+
if (!apiBase.trim()) errs.api_base = "请填写 API 地址";
|
|
6524
|
+
if (!model.trim()) errs.model = "请填写模型名";
|
|
6525
|
+
setFieldErrors(errs);
|
|
6526
|
+
return Object.keys(errs).length === 0;
|
|
6527
|
+
};
|
|
6528
|
+
const applyPreset = (next) => {
|
|
6529
|
+
setPreset(next);
|
|
6530
|
+
if (next === "local" && (item === "embedding" || item === "rerank")) {
|
|
6531
|
+
setApiBase(localBase());
|
|
6532
|
+
if (isPresetModel(model)) setModel(LOCAL_MODEL[item]);
|
|
6279
6533
|
return;
|
|
6280
6534
|
}
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
status,
|
|
6295
|
-
keyword,
|
|
6296
|
-
page
|
|
6297
|
-
]);
|
|
6298
|
-
(0, react.useEffect)(reload, [reload]);
|
|
6299
|
-
(0, react.useEffect)(() => {
|
|
6300
|
-
if (!isAdmin) return;
|
|
6301
|
-
getReviewConfig().then((res) => setMode(Number(res?.mode) === 1 ? 1 : 0)).catch(() => setMode(0));
|
|
6302
|
-
}, [isAdmin]);
|
|
6303
|
-
const switchMode = (next) => {
|
|
6304
|
-
if (modeBusy || next === mode) return;
|
|
6305
|
-
setModeBusy(true);
|
|
6306
|
-
setReviewConfig(next).then(() => {
|
|
6307
|
-
setMode(next);
|
|
6308
|
-
toast(next === 1 ? "已开启需审核:新用户首次进入知识库将处于待审核状态" : "已关闭审核:新用户首次进入即为正常状态", "success");
|
|
6309
|
-
}).catch(toastError).finally(() => setModeBusy(false));
|
|
6535
|
+
if (next === "ollama" && item === "ocr") {
|
|
6536
|
+
setProtocol("ollama");
|
|
6537
|
+
const cur = apiBase.trim();
|
|
6538
|
+
if (cur === "" || PRESET_BASES.includes(cur)) setApiBase(`http://${localModelHost()}:11434`);
|
|
6539
|
+
if (isPresetModel(model)) setModel(OLLAMA_OCR.model);
|
|
6540
|
+
if (prompt.trim() === "") setPrompt(OLLAMA_OCR.prompt);
|
|
6541
|
+
return;
|
|
6542
|
+
}
|
|
6543
|
+
const p = findPreset(item, next);
|
|
6544
|
+
if (!p) return;
|
|
6545
|
+
setApiBase(p.base);
|
|
6546
|
+
if (item === "ocr") setProtocol("openai");
|
|
6547
|
+
if (isPresetModel(model)) setModel(p.model);
|
|
6310
6548
|
};
|
|
6311
|
-
const
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
toast("操作成功", "success");
|
|
6316
|
-
setConfirm(null);
|
|
6317
|
-
reload();
|
|
6318
|
-
}).catch(toastError).finally(() => setBusy(false));
|
|
6549
|
+
const handleTest = () => {
|
|
6550
|
+
if (busy || !validate()) return;
|
|
6551
|
+
setTesting(true);
|
|
6552
|
+
testModelConfig(item, buildPayload()).then((data) => toast(data && data.message || "连通性测试通过", "success")).catch(toastError).finally(() => setTesting(false));
|
|
6319
6553
|
};
|
|
6320
|
-
const
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6554
|
+
const handleSave = () => {
|
|
6555
|
+
if (busy || !validate()) return;
|
|
6556
|
+
setSaving(true);
|
|
6557
|
+
saveModelConfig(item, buildPayload()).then((data) => {
|
|
6558
|
+
toast("已保存,配置立即生效", "success");
|
|
6559
|
+
onSaved?.(data);
|
|
6560
|
+
}).catch(toastError).finally(() => setSaving(false));
|
|
6561
|
+
};
|
|
6562
|
+
const handleReset = () => {
|
|
6563
|
+
if (busy) return;
|
|
6564
|
+
setConfirmReset(true);
|
|
6565
|
+
};
|
|
6566
|
+
/** 确认弹窗确认后执行恢复默认:清除设置页覆盖,回落部署环境变量配置 */
|
|
6567
|
+
const doReset = () => {
|
|
6568
|
+
setResetting(true);
|
|
6569
|
+
saveModelConfig(item, null).then((data) => {
|
|
6570
|
+
setConfirmReset(false);
|
|
6571
|
+
toast("已恢复默认配置", "success");
|
|
6572
|
+
onSaved?.(data);
|
|
6573
|
+
}).catch(toastError).finally(() => setResetting(false));
|
|
6574
|
+
};
|
|
6575
|
+
const activeHint = findPreset(item, preset)?.hint || (preset === "local" ? LOCAL_HINT[item] : preset === "ollama" ? OLLAMA_HINT : "");
|
|
6576
|
+
if (!open) return null;
|
|
6577
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Modal, {
|
|
6578
|
+
title: `编辑模型服务 · ${ITEM_NAMES[item] || item}`,
|
|
6579
|
+
onClose,
|
|
6580
|
+
escClose: !confirmReset,
|
|
6581
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
6582
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6583
|
+
type: "button",
|
|
6584
|
+
className: "skai-kb-btn",
|
|
6585
|
+
disabled: busy,
|
|
6586
|
+
onClick: handleReset,
|
|
6587
|
+
children: resetting ? "恢复中…" : "恢复默认"
|
|
6588
|
+
}),
|
|
6589
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6590
|
+
type: "button",
|
|
6591
|
+
className: "skai-kb-btn",
|
|
6592
|
+
disabled: busy,
|
|
6593
|
+
onClick: handleTest,
|
|
6594
|
+
children: testing ? "测试中…" : "测试连接"
|
|
6595
|
+
}),
|
|
6596
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6597
|
+
type: "button",
|
|
6598
|
+
className: "skai-kb-btn skai-kb-btn-primary",
|
|
6599
|
+
disabled: busy,
|
|
6600
|
+
onClick: handleSave,
|
|
6601
|
+
children: saving ? "保存中…" : "保存"
|
|
6602
|
+
})
|
|
6603
|
+
] }),
|
|
6337
6604
|
children: [
|
|
6338
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
6339
|
-
className: "skai-kb-
|
|
6340
|
-
children:
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6605
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6606
|
+
className: "skai-kb-field",
|
|
6607
|
+
children: [
|
|
6608
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6609
|
+
className: "skai-kb-field-label",
|
|
6610
|
+
children: "服务商预设"
|
|
6611
|
+
}),
|
|
6612
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
6613
|
+
className: "skai-kb-select",
|
|
6614
|
+
value: preset,
|
|
6615
|
+
onChange: (e) => applyPreset(e.target.value),
|
|
6616
|
+
children: [
|
|
6617
|
+
(PROVIDER_PRESETS[item] || []).map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6618
|
+
value: p.value,
|
|
6619
|
+
children: p.label
|
|
6620
|
+
}, p.value)),
|
|
6621
|
+
item === "ocr" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6622
|
+
value: "ollama",
|
|
6623
|
+
children: "本地部署(ollama · glm-ocr容器)"
|
|
6624
|
+
}) : null,
|
|
6625
|
+
item === "embedding" || item === "rerank" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6626
|
+
value: "local",
|
|
6627
|
+
children: "本地部署(Infinity · bge 容器)"
|
|
6628
|
+
}) : null,
|
|
6629
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6630
|
+
value: "custom",
|
|
6631
|
+
children: "自定义服务商"
|
|
6632
|
+
})
|
|
6633
|
+
]
|
|
6634
|
+
}),
|
|
6635
|
+
activeHint ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6636
|
+
className: "skai-kb-sub",
|
|
6637
|
+
children: activeHint
|
|
6638
|
+
}) : null
|
|
6639
|
+
]
|
|
6344
6640
|
}),
|
|
6345
6641
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6346
|
-
className: "skai-kb-
|
|
6347
|
-
children: [
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
onClick: () => switchMode(0),
|
|
6364
|
-
children: "无需审核"
|
|
6365
|
-
})]
|
|
6366
|
-
})]
|
|
6642
|
+
className: "skai-kb-field",
|
|
6643
|
+
children: [
|
|
6644
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6645
|
+
className: "skai-kb-field-label",
|
|
6646
|
+
children: "API 地址 *"
|
|
6647
|
+
}),
|
|
6648
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6649
|
+
className: "skai-kb-input",
|
|
6650
|
+
value: apiBase,
|
|
6651
|
+
placeholder: "https://api.siliconflow.cn/v1",
|
|
6652
|
+
onChange: (e) => setApiBase(e.target.value)
|
|
6653
|
+
}),
|
|
6654
|
+
fieldErrors.api_base ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6655
|
+
className: "skai-kb-field-error",
|
|
6656
|
+
children: fieldErrors.api_base
|
|
6657
|
+
}) : null
|
|
6658
|
+
]
|
|
6367
6659
|
}),
|
|
6368
6660
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6369
|
-
className: "skai-kb-
|
|
6370
|
-
children: [
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
},
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6661
|
+
className: "skai-kb-field",
|
|
6662
|
+
children: [
|
|
6663
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6664
|
+
className: "skai-kb-field-label",
|
|
6665
|
+
children: "API 密钥"
|
|
6666
|
+
}),
|
|
6667
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6668
|
+
className: "skai-kb-input",
|
|
6669
|
+
value: apiKey,
|
|
6670
|
+
placeholder: "留空沿用当前密钥",
|
|
6671
|
+
onChange: (e) => setApiKey(e.target.value)
|
|
6672
|
+
}),
|
|
6673
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6674
|
+
className: "skai-kb-sub",
|
|
6675
|
+
children: "密钥经后端掩码展示(如 sk-a****abcd);留空或保持掩码串均表示沿用当前生效密钥,填写新值即更新"
|
|
6676
|
+
})
|
|
6677
|
+
]
|
|
6678
|
+
}),
|
|
6679
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6680
|
+
className: "skai-kb-field",
|
|
6681
|
+
children: [
|
|
6682
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6683
|
+
className: "skai-kb-field-label",
|
|
6684
|
+
children: "模型名 *"
|
|
6685
|
+
}),
|
|
6686
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6687
|
+
className: "skai-kb-input",
|
|
6688
|
+
value: model,
|
|
6689
|
+
placeholder: item === "ocr" ? "如 Qwen/Qwen2.5-VL-32B-Instruct" : "如 BAAI/bge-large-zh-v1.5",
|
|
6690
|
+
onChange: (e) => setModel(e.target.value)
|
|
6691
|
+
}),
|
|
6692
|
+
fieldErrors.model ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6693
|
+
className: "skai-kb-field-error",
|
|
6694
|
+
children: fieldErrors.model
|
|
6695
|
+
}) : null
|
|
6696
|
+
]
|
|
6697
|
+
}),
|
|
6698
|
+
item === "ocr" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6699
|
+
className: "skai-kb-field",
|
|
6700
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6701
|
+
className: "skai-kb-field-label",
|
|
6702
|
+
children: "调用协议"
|
|
6703
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
6704
|
+
className: "skai-kb-select",
|
|
6705
|
+
value: protocol,
|
|
6706
|
+
onChange: (e) => setProtocol(e.target.value),
|
|
6707
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6708
|
+
value: "openai",
|
|
6709
|
+
children: "OpenAI 兼容公网"
|
|
6710
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6711
|
+
value: "ollama",
|
|
6712
|
+
children: "本地 Ollama"
|
|
6713
|
+
})]
|
|
6714
|
+
})]
|
|
6715
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6716
|
+
className: "skai-kb-field",
|
|
6717
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6718
|
+
className: "skai-kb-field-label",
|
|
6719
|
+
children: "识别提示词"
|
|
6720
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
6721
|
+
className: "skai-kb-textarea",
|
|
6722
|
+
rows: 3,
|
|
6723
|
+
value: prompt,
|
|
6724
|
+
placeholder: "留空使用内置默认提示词",
|
|
6725
|
+
onChange: (e) => setPrompt(e.target.value)
|
|
6726
|
+
})]
|
|
6727
|
+
})] }) : null,
|
|
6728
|
+
item === "embedding" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6729
|
+
className: "skai-kb-field-row",
|
|
6730
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6731
|
+
className: "skai-kb-field",
|
|
6732
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6733
|
+
className: "skai-kb-field-label",
|
|
6734
|
+
children: "向量维度"
|
|
6735
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6736
|
+
className: "skai-kb-input",
|
|
6737
|
+
type: "number",
|
|
6738
|
+
min: 0,
|
|
6739
|
+
value: dimension,
|
|
6740
|
+
placeholder: "留空沿用默认 1024",
|
|
6741
|
+
onChange: (e) => setDimension(e.target.value)
|
|
6742
|
+
})]
|
|
6743
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6744
|
+
className: "skai-kb-field",
|
|
6745
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6746
|
+
className: "skai-kb-field-label",
|
|
6747
|
+
children: "批量大小"
|
|
6748
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6749
|
+
className: "skai-kb-input",
|
|
6750
|
+
type: "number",
|
|
6751
|
+
min: 0,
|
|
6752
|
+
value: batchSize,
|
|
6753
|
+
placeholder: "留空沿用默认",
|
|
6754
|
+
onChange: (e) => setBatchSize(e.target.value)
|
|
6755
|
+
})]
|
|
6756
|
+
})]
|
|
6757
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6758
|
+
className: "skai-kb-field-row",
|
|
6759
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6760
|
+
className: "skai-kb-field",
|
|
6761
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6762
|
+
className: "skai-kb-field-label",
|
|
6763
|
+
children: "超时秒数"
|
|
6764
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6765
|
+
className: "skai-kb-input",
|
|
6766
|
+
type: "number",
|
|
6767
|
+
min: 0,
|
|
6768
|
+
value: timeoutSec,
|
|
6769
|
+
placeholder: "留空沿用默认",
|
|
6770
|
+
onChange: (e) => setTimeoutSec(e.target.value)
|
|
6771
|
+
})]
|
|
6772
|
+
})
|
|
6773
|
+
})] }) : null,
|
|
6774
|
+
item === "rerank" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6775
|
+
className: "skai-kb-field-row",
|
|
6776
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6777
|
+
className: "skai-kb-field",
|
|
6778
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6779
|
+
className: "skai-kb-field-label",
|
|
6780
|
+
children: "重排后保留条数 top_n"
|
|
6781
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6782
|
+
className: "skai-kb-input",
|
|
6783
|
+
type: "number",
|
|
6784
|
+
min: 0,
|
|
6785
|
+
value: topN,
|
|
6786
|
+
placeholder: "留空沿用默认",
|
|
6787
|
+
onChange: (e) => setTopN(e.target.value)
|
|
6788
|
+
})]
|
|
6789
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6790
|
+
className: "skai-kb-field",
|
|
6791
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6792
|
+
className: "skai-kb-field-label",
|
|
6793
|
+
children: "超时秒数"
|
|
6794
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6795
|
+
className: "skai-kb-input",
|
|
6796
|
+
type: "number",
|
|
6797
|
+
min: 0,
|
|
6798
|
+
value: timeoutSec,
|
|
6799
|
+
placeholder: "留空沿用默认",
|
|
6800
|
+
onChange: (e) => setTimeoutSec(e.target.value)
|
|
6801
|
+
})]
|
|
6802
|
+
})]
|
|
6803
|
+
}) : null,
|
|
6804
|
+
item === "ocr" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6805
|
+
className: "skai-kb-field-row",
|
|
6806
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6807
|
+
className: "skai-kb-field",
|
|
6808
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6809
|
+
className: "skai-kb-field-label",
|
|
6810
|
+
children: "单文档识别页数上限"
|
|
6811
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6812
|
+
className: "skai-kb-input",
|
|
6813
|
+
type: "number",
|
|
6814
|
+
min: 0,
|
|
6815
|
+
value: maxPages,
|
|
6816
|
+
placeholder: "0=不限制",
|
|
6817
|
+
onChange: (e) => setMaxPages(e.target.value)
|
|
6818
|
+
})]
|
|
6819
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6820
|
+
className: "skai-kb-field",
|
|
6821
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6822
|
+
className: "skai-kb-field-label",
|
|
6823
|
+
children: "超时秒数"
|
|
6824
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6825
|
+
className: "skai-kb-input",
|
|
6826
|
+
type: "number",
|
|
6827
|
+
min: 0,
|
|
6828
|
+
value: timeoutSec,
|
|
6829
|
+
placeholder: "留空沿用默认",
|
|
6830
|
+
onChange: (e) => setTimeoutSec(e.target.value)
|
|
6831
|
+
})]
|
|
6832
|
+
})]
|
|
6833
|
+
}) : null
|
|
6834
|
+
]
|
|
6835
|
+
}), confirmReset ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
6836
|
+
title: "恢复默认配置",
|
|
6837
|
+
message: `确定恢复「${ITEM_NAMES[item] || item}」的默认配置吗?将清除设置页覆盖并回落到部署环境变量配置。`,
|
|
6838
|
+
confirmText: "恢复默认",
|
|
6839
|
+
danger: false,
|
|
6840
|
+
busy: resetting,
|
|
6841
|
+
onConfirm: doReset,
|
|
6842
|
+
onClose: () => setConfirmReset(false)
|
|
6843
|
+
}) : null] });
|
|
6844
|
+
}
|
|
6845
|
+
//#endregion
|
|
6846
|
+
//#region src/components/settings/ModelConfigHelpModal.jsx
|
|
6847
|
+
/**
|
|
6848
|
+
* 模型服务「配置步骤帮助」弹窗(设置页「模型服务」区块各行「配置说明」按钮入口)
|
|
6849
|
+
*
|
|
6850
|
+
* 分节内容:使用说明(预设回填机制 + 各服务项已收录名单 + 1024 维约束)→
|
|
6851
|
+
* 各服务商配置方法(按服务商分节覆盖全部已收录预设:硅基流动 / 阿里云百炼 /
|
|
6852
|
+
* 百度千帆 / 智谱 / DeepSeek / 火山方舟 / Kimi / 腾讯混元,每节含控制台入口、
|
|
6853
|
+
* 密钥创建步骤、API 地址与各服务项推荐模型)→ 本地部署 Infinity(向量化 / 重排,
|
|
6854
|
+
* bge 容器)→ 本地部署(ollama · glm-ocr容器,自动拉取 glm-ocr:q8_0)→ 密钥与生效方式;
|
|
6855
|
+
* 服务商能力与推荐模型与 ModelServiceModal 的 PROVIDER_PRESETS 保持一致;
|
|
6856
|
+
* 样式用 knowledge.css 末尾的 skai-kb-help-* 局部类
|
|
6857
|
+
*/
|
|
6858
|
+
function ModelConfigHelpModal({ onClose }) {
|
|
6859
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Modal, {
|
|
6860
|
+
title: "配置步骤帮助",
|
|
6861
|
+
onClose,
|
|
6862
|
+
width: 560,
|
|
6863
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6864
|
+
type: "button",
|
|
6865
|
+
className: "skai-kb-btn skai-kb-btn-primary",
|
|
6866
|
+
onClick: onClose,
|
|
6867
|
+
children: "关闭"
|
|
6868
|
+
}),
|
|
6869
|
+
children: [
|
|
6870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6871
|
+
className: "skai-kb-help-section",
|
|
6872
|
+
children: [
|
|
6873
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
6874
|
+
className: "skai-kb-help-title",
|
|
6875
|
+
children: "使用说明"
|
|
6876
|
+
}),
|
|
6877
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6878
|
+
className: "skai-kb-help-text",
|
|
6879
|
+
children: "编辑弹窗「服务商预设」选择服务商后自动回填 API 地址与推荐模型,一般只需粘贴密钥 → 「测试连接」→ 保存;选择「自定义服务商」可手动填写任意 OpenAI 兼容地址。"
|
|
6880
|
+
}),
|
|
6881
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6882
|
+
className: "skai-kb-help-text",
|
|
6883
|
+
children: "各服务项已收录:向量化——硅基流动 / 阿里云百炼 / 百度千帆 / 本地部署;重排——硅基流动 / 智谱 / 本地部署;OCR——DeepSeek / 火山方舟 / 硅基流动 / 阿里云百炼 / 智谱 / Kimi / 腾讯混元 / 本地部署。向量化所选模型须为 1024 维(与库内向量列一致),各预设已按此筛选。"
|
|
6884
|
+
})
|
|
6885
|
+
]
|
|
6886
|
+
}),
|
|
6887
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6888
|
+
className: "skai-kb-help-section",
|
|
6889
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
6890
|
+
className: "skai-kb-help-title",
|
|
6891
|
+
children: "硅基流动 SiliconFlow(向量化 / 重排 / OCR)"
|
|
6892
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
6893
|
+
className: "skai-kb-help-list",
|
|
6894
|
+
children: [
|
|
6895
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
6896
|
+
"注册并登录",
|
|
6897
|
+
" ",
|
|
6898
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
6899
|
+
className: "skai-kb-help-link",
|
|
6900
|
+
href: "https://cloud.siliconflow.cn/",
|
|
6901
|
+
target: "_blank",
|
|
6902
|
+
rel: "noreferrer",
|
|
6903
|
+
children: "https://cloud.siliconflow.cn/"
|
|
6904
|
+
})
|
|
6905
|
+
] }),
|
|
6906
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "控制台左侧「API 密钥」→「新建 API 密钥」并复制" }),
|
|
6907
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
6908
|
+
"API 地址统一",
|
|
6909
|
+
" ",
|
|
6910
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
6911
|
+
className: "skai-kb-help-code",
|
|
6912
|
+
children: "https://api.siliconflow.cn/v1"
|
|
6913
|
+
}),
|
|
6914
|
+
":向量化模型 ",
|
|
6915
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
6916
|
+
className: "skai-kb-help-code",
|
|
6917
|
+
children: "BAAI/bge-large-zh-v1.5"
|
|
6918
|
+
}),
|
|
6919
|
+
"(1024 维);重排模型 ",
|
|
6920
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
6921
|
+
className: "skai-kb-help-code",
|
|
6922
|
+
children: "BAAI/bge-reranker-v2-m3"
|
|
6923
|
+
}),
|
|
6924
|
+
";OCR 视觉模型",
|
|
6925
|
+
" ",
|
|
6926
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
6927
|
+
className: "skai-kb-help-code",
|
|
6928
|
+
children: "Qwen/Qwen2.5-VL-32B-Instruct"
|
|
6929
|
+
}),
|
|
6930
|
+
"(或其它支持视觉的模型)"
|
|
6931
|
+
] }),
|
|
6932
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "粘贴 API 密钥 → 点击「测试连接」→ 通过后保存" })
|
|
6933
|
+
]
|
|
6934
|
+
})]
|
|
6935
|
+
}),
|
|
6936
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6937
|
+
className: "skai-kb-help-section",
|
|
6938
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
6939
|
+
className: "skai-kb-help-title",
|
|
6940
|
+
children: "阿里云百炼(向量化 / OCR)"
|
|
6941
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
6942
|
+
className: "skai-kb-help-list",
|
|
6943
|
+
children: [
|
|
6944
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
6945
|
+
"登录阿里云并开通模型服务",
|
|
6946
|
+
" ",
|
|
6947
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
6948
|
+
className: "skai-kb-help-link",
|
|
6949
|
+
href: "https://bailian.console.aliyun.com/",
|
|
6950
|
+
target: "_blank",
|
|
6951
|
+
rel: "noreferrer",
|
|
6952
|
+
children: "https://bailian.console.aliyun.com/"
|
|
6953
|
+
})
|
|
6954
|
+
] }),
|
|
6955
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "控制台右上「API-KEY」→ 创建并复制 API Key" }),
|
|
6956
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
6957
|
+
"API 地址",
|
|
6958
|
+
" ",
|
|
6959
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
6960
|
+
className: "skai-kb-help-code",
|
|
6961
|
+
children: "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
6962
|
+
}),
|
|
6963
|
+
":向量化模型 ",
|
|
6964
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
6965
|
+
className: "skai-kb-help-code",
|
|
6966
|
+
children: "text-embedding-v4"
|
|
6967
|
+
}),
|
|
6968
|
+
"(默认 1024 维,与库内向量列一致);OCR 视觉模型",
|
|
6969
|
+
" ",
|
|
6970
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
6971
|
+
className: "skai-kb-help-code",
|
|
6972
|
+
children: "qwen-vl-max"
|
|
6973
|
+
}),
|
|
6974
|
+
"(或 qwen3-vl 系列)"
|
|
6975
|
+
] })
|
|
6976
|
+
]
|
|
6977
|
+
})]
|
|
6978
|
+
}),
|
|
6979
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6980
|
+
className: "skai-kb-help-section",
|
|
6981
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
6982
|
+
className: "skai-kb-help-title",
|
|
6983
|
+
children: "百度千帆(向量化)"
|
|
6984
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
6985
|
+
className: "skai-kb-help-list",
|
|
6986
|
+
children: [
|
|
6987
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
6988
|
+
"登录百度智能云并开通千帆 ModelBuilder 服务",
|
|
6989
|
+
" ",
|
|
6990
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
6991
|
+
className: "skai-kb-help-link",
|
|
6992
|
+
href: "https://console.bce.baidu.com/qianfan/",
|
|
6993
|
+
target: "_blank",
|
|
6994
|
+
rel: "noreferrer",
|
|
6995
|
+
children: "https://console.bce.baidu.com/qianfan/"
|
|
6996
|
+
})
|
|
6997
|
+
] }),
|
|
6998
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
6999
|
+
"在 IAM「API Key」页",
|
|
7000
|
+
" ",
|
|
7001
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7002
|
+
className: "skai-kb-help-link",
|
|
7003
|
+
href: "https://console.bce.baidu.com/iam/#/iam/apikey/list",
|
|
7004
|
+
target: "_blank",
|
|
7005
|
+
rel: "noreferrer",
|
|
7006
|
+
children: "console.bce.baidu.com/iam/#/iam/apikey/list"
|
|
7007
|
+
}),
|
|
7008
|
+
" ",
|
|
7009
|
+
"创建并复制(v2 接口按 Bearer 方式认证)"
|
|
7010
|
+
] }),
|
|
7011
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7012
|
+
"API 地址 ",
|
|
7013
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7014
|
+
className: "skai-kb-help-code",
|
|
7015
|
+
children: "https://qianfan.baidubce.com/v2"
|
|
7016
|
+
}),
|
|
7017
|
+
":向量化模型 ",
|
|
7018
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7019
|
+
className: "skai-kb-help-code",
|
|
7020
|
+
children: "bge-large-zh"
|
|
7021
|
+
}),
|
|
7022
|
+
"(1024 维)"
|
|
7023
|
+
] })
|
|
7024
|
+
]
|
|
7025
|
+
})]
|
|
7026
|
+
}),
|
|
7027
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7028
|
+
className: "skai-kb-help-section",
|
|
7029
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7030
|
+
className: "skai-kb-help-title",
|
|
7031
|
+
children: "智谱 AI(重排 / OCR)"
|
|
7032
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
7033
|
+
className: "skai-kb-help-list",
|
|
7034
|
+
children: [
|
|
7035
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7036
|
+
"注册并登录",
|
|
7037
|
+
" ",
|
|
7038
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7039
|
+
className: "skai-kb-help-link",
|
|
7040
|
+
href: "https://open.bigmodel.cn/",
|
|
7041
|
+
target: "_blank",
|
|
7042
|
+
rel: "noreferrer",
|
|
7043
|
+
children: "https://open.bigmodel.cn/"
|
|
7044
|
+
})
|
|
7045
|
+
] }),
|
|
7046
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "控制台「API Keys」页 → 创建并复制" }),
|
|
7047
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7048
|
+
"API 地址",
|
|
7049
|
+
" ",
|
|
7050
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7051
|
+
className: "skai-kb-help-code",
|
|
7052
|
+
children: "https://open.bigmodel.cn/api/paas/v4"
|
|
7053
|
+
}),
|
|
7054
|
+
":重排模型 ",
|
|
7055
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7056
|
+
className: "skai-kb-help-code",
|
|
7057
|
+
children: "rerank"
|
|
7058
|
+
}),
|
|
7059
|
+
"(Cohere 兼容 /rerank 端点);OCR 视觉模型",
|
|
7060
|
+
" ",
|
|
7061
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7062
|
+
className: "skai-kb-help-code",
|
|
7063
|
+
children: "glm-4.6v-flash"
|
|
7064
|
+
}),
|
|
7065
|
+
"(免费)/",
|
|
7066
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7067
|
+
className: "skai-kb-help-code",
|
|
7068
|
+
children: "glm-4.6v"
|
|
7069
|
+
}),
|
|
7070
|
+
" /",
|
|
7071
|
+
" ",
|
|
7072
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7073
|
+
className: "skai-kb-help-code",
|
|
7074
|
+
children: "GLM-OCR"
|
|
7075
|
+
})
|
|
7076
|
+
] })
|
|
7077
|
+
]
|
|
7078
|
+
})]
|
|
7079
|
+
}),
|
|
7080
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7081
|
+
className: "skai-kb-help-section",
|
|
7082
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7083
|
+
className: "skai-kb-help-title",
|
|
7084
|
+
children: "DeepSeek(OCR)"
|
|
7085
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
7086
|
+
className: "skai-kb-help-list",
|
|
7087
|
+
children: [
|
|
7088
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7089
|
+
"注册并登录",
|
|
7090
|
+
" ",
|
|
7091
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7092
|
+
className: "skai-kb-help-link",
|
|
7093
|
+
href: "https://platform.deepseek.com/",
|
|
7094
|
+
target: "_blank",
|
|
7095
|
+
rel: "noreferrer",
|
|
7096
|
+
children: "https://platform.deepseek.com/"
|
|
7097
|
+
})
|
|
7098
|
+
] }),
|
|
7099
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "左侧「API Keys」→ 创建并复制(需先充值,按量计费)" }),
|
|
7100
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7101
|
+
"API 地址 ",
|
|
7102
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7103
|
+
className: "skai-kb-help-code",
|
|
7104
|
+
children: "https://api.deepseek.com"
|
|
7105
|
+
}),
|
|
7106
|
+
":模型 ",
|
|
7107
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7108
|
+
className: "skai-kb-help-code",
|
|
7109
|
+
children: "deepseek-flash"
|
|
7110
|
+
}),
|
|
7111
|
+
"(原生多模态,支持 image_url 传图;DeepSeek 不提供向量化 / 重排服务)"
|
|
7112
|
+
] })
|
|
7113
|
+
]
|
|
7114
|
+
})]
|
|
7115
|
+
}),
|
|
7116
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7117
|
+
className: "skai-kb-help-section",
|
|
7118
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7119
|
+
className: "skai-kb-help-title",
|
|
7120
|
+
children: "火山方舟(OCR)"
|
|
7121
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
7122
|
+
className: "skai-kb-help-list",
|
|
7123
|
+
children: [
|
|
7124
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7125
|
+
"登录火山引擎(需实名认证)并进入方舟控制台",
|
|
7126
|
+
" ",
|
|
7127
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7128
|
+
className: "skai-kb-help-link",
|
|
7129
|
+
href: "https://console.volcengine.com/ark/",
|
|
7130
|
+
target: "_blank",
|
|
7131
|
+
rel: "noreferrer",
|
|
7132
|
+
children: "https://console.volcengine.com/ark/"
|
|
7133
|
+
})
|
|
7134
|
+
] }),
|
|
7135
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "左侧「API Key 管理」→「创建 API Key」并复制" }),
|
|
7136
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7137
|
+
"模型名直调(无需创建推理接入点):API 地址",
|
|
7138
|
+
" ",
|
|
7139
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7140
|
+
className: "skai-kb-help-code",
|
|
7141
|
+
children: "https://ark.cn-beijing.volces.com/api/v3"
|
|
7142
|
+
}),
|
|
7143
|
+
",OCR 模型 ",
|
|
7144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7145
|
+
className: "skai-kb-help-code",
|
|
7146
|
+
children: "doubao-seed-2-1-pro-260915"
|
|
7147
|
+
}),
|
|
7148
|
+
"(旗舰多模态)"
|
|
7149
|
+
] })
|
|
7150
|
+
]
|
|
7151
|
+
})]
|
|
7152
|
+
}),
|
|
7153
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7154
|
+
className: "skai-kb-help-section",
|
|
7155
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7156
|
+
className: "skai-kb-help-title",
|
|
7157
|
+
children: "月之暗面 Kimi(OCR)"
|
|
7158
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
7159
|
+
className: "skai-kb-help-list",
|
|
7160
|
+
children: [
|
|
7161
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7162
|
+
"注册并登录",
|
|
7163
|
+
" ",
|
|
7164
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7165
|
+
className: "skai-kb-help-link",
|
|
7166
|
+
href: "https://platform.moonshot.cn/",
|
|
7167
|
+
target: "_blank",
|
|
7168
|
+
rel: "noreferrer",
|
|
7169
|
+
children: "https://platform.moonshot.cn/"
|
|
7170
|
+
})
|
|
7171
|
+
] }),
|
|
7172
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "左侧「API Key 管理」→「新建」并复制" }),
|
|
7173
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7174
|
+
"API 地址 ",
|
|
7175
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7176
|
+
className: "skai-kb-help-code",
|
|
7177
|
+
children: "https://api.moonshot.cn/v1"
|
|
7178
|
+
}),
|
|
7179
|
+
":OCR 模型 ",
|
|
7180
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7181
|
+
className: "skai-kb-help-code",
|
|
7182
|
+
children: "kimi-k3.1-pro"
|
|
7183
|
+
}),
|
|
7184
|
+
"(原生视觉)"
|
|
7185
|
+
] })
|
|
7186
|
+
]
|
|
7187
|
+
})]
|
|
7188
|
+
}),
|
|
7189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7190
|
+
className: "skai-kb-help-section",
|
|
7191
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7192
|
+
className: "skai-kb-help-title",
|
|
7193
|
+
children: "腾讯混元(OCR)"
|
|
7194
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
7195
|
+
className: "skai-kb-help-list",
|
|
7196
|
+
children: [
|
|
7197
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7198
|
+
"登录腾讯云控制台",
|
|
7199
|
+
" ",
|
|
7200
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7201
|
+
className: "skai-kb-help-link",
|
|
7202
|
+
href: "https://console.cloud.tencent.com/hunyuan",
|
|
7203
|
+
target: "_blank",
|
|
7204
|
+
rel: "noreferrer",
|
|
7205
|
+
children: "https://console.cloud.tencent.com/hunyuan"
|
|
7206
|
+
}),
|
|
7207
|
+
" ",
|
|
7208
|
+
"并开通混元模型服务"
|
|
7209
|
+
] }),
|
|
7210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "左侧「API 密钥」→「创建 API Key」并复制" }),
|
|
7211
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
7212
|
+
"API 地址",
|
|
7213
|
+
" ",
|
|
7214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7215
|
+
className: "skai-kb-help-code",
|
|
7216
|
+
children: "https://api.hunyuan.cloud.tencent.com/v1"
|
|
7217
|
+
}),
|
|
7218
|
+
":OCR 模型 ",
|
|
7219
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7220
|
+
className: "skai-kb-help-code",
|
|
7221
|
+
children: "hunyuan-vision"
|
|
7222
|
+
})
|
|
7223
|
+
] })
|
|
7224
|
+
]
|
|
7225
|
+
})]
|
|
7226
|
+
}),
|
|
7227
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7228
|
+
className: "skai-kb-help-section",
|
|
7229
|
+
children: [
|
|
7230
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7231
|
+
className: "skai-kb-help-title",
|
|
7232
|
+
children: "本地部署(向量化 / 重排)"
|
|
7233
|
+
}),
|
|
7234
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
7235
|
+
className: "skai-kb-help-text",
|
|
7236
|
+
children: [
|
|
7237
|
+
"部署栈自带 Infinity 服务(dev/docker-compose.yml 的",
|
|
7238
|
+
" ",
|
|
7239
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7240
|
+
className: "skai-kb-help-code",
|
|
7241
|
+
children: "bge"
|
|
7242
|
+
}),
|
|
7243
|
+
" 容器,端口",
|
|
7244
|
+
" ",
|
|
7245
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7246
|
+
className: "skai-kb-help-code",
|
|
7247
|
+
children: "7997"
|
|
7248
|
+
}),
|
|
7249
|
+
",同时加载向量化与重排两个模型),无需 API 密钥;编辑弹窗选择预设",
|
|
7250
|
+
" ",
|
|
7251
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7252
|
+
className: "skai-kb-help-code",
|
|
7253
|
+
children: "本地部署(Infinity · bge 容器)"
|
|
7254
|
+
}),
|
|
7255
|
+
"即可自动回填,默认主机与当前 API 地址一致(未配置服务器时为 localhost)。"
|
|
7256
|
+
]
|
|
7257
|
+
}),
|
|
7258
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
7259
|
+
className: "skai-kb-help-text",
|
|
7260
|
+
children: [
|
|
7261
|
+
"向量化:API 地址 ",
|
|
7262
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7263
|
+
className: "skai-kb-help-code",
|
|
7264
|
+
children: "http://localhost:7997"
|
|
7265
|
+
}),
|
|
7266
|
+
",模型",
|
|
7267
|
+
" ",
|
|
7268
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7269
|
+
className: "skai-kb-help-code",
|
|
7270
|
+
children: "BAAI/bge-large-zh-v1.5"
|
|
7271
|
+
}),
|
|
7272
|
+
"(1024 维);重排:API 地址",
|
|
7273
|
+
" ",
|
|
7274
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7275
|
+
className: "skai-kb-help-code",
|
|
7276
|
+
children: "http://localhost:7997"
|
|
7277
|
+
}),
|
|
7278
|
+
",模型",
|
|
7279
|
+
" ",
|
|
7280
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7281
|
+
className: "skai-kb-help-code",
|
|
7282
|
+
children: "BAAI/bge-reranker-v2-m3"
|
|
7283
|
+
}),
|
|
7284
|
+
";同 Docker 网络容器内调用时地址改用",
|
|
7285
|
+
" ",
|
|
7286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7287
|
+
className: "skai-kb-help-code",
|
|
7288
|
+
children: "http://bge:7997"
|
|
7289
|
+
}),
|
|
7290
|
+
"。"
|
|
7291
|
+
]
|
|
7292
|
+
})
|
|
7293
|
+
]
|
|
7294
|
+
}),
|
|
7295
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7296
|
+
className: "skai-kb-help-section",
|
|
7297
|
+
children: [
|
|
7298
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7299
|
+
className: "skai-kb-help-title",
|
|
7300
|
+
children: "本地部署(ollama · glm-ocr容器,OCR)"
|
|
7301
|
+
}),
|
|
7302
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
7303
|
+
className: "skai-kb-help-text",
|
|
7304
|
+
children: [
|
|
7305
|
+
"部署栈自带 ",
|
|
7306
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7307
|
+
className: "skai-kb-help-code",
|
|
7308
|
+
children: "ollama"
|
|
7309
|
+
}),
|
|
7310
|
+
" 容器(dev/docker-compose.yml, 端口 ",
|
|
7311
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7312
|
+
className: "skai-kb-help-code",
|
|
7313
|
+
children: "11434"
|
|
7314
|
+
}),
|
|
7315
|
+
",启动时自动拉取 ",
|
|
7316
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7317
|
+
className: "skai-kb-help-code",
|
|
7318
|
+
children: "glm-ocr:q8_0"
|
|
7319
|
+
}),
|
|
7320
|
+
");编辑弹窗选择预设 「本地部署(ollama · glm-ocr容器)」自动回填地址(主机与当前 API 地址一致)与协议",
|
|
7321
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7322
|
+
className: "skai-kb-help-code",
|
|
7323
|
+
children: "ollama"
|
|
7324
|
+
}),
|
|
7325
|
+
",OCR 模型填 ",
|
|
7326
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7327
|
+
className: "skai-kb-help-code",
|
|
7328
|
+
children: "glm-ocr:q8_0"
|
|
7329
|
+
}),
|
|
7330
|
+
";手动填写时 API 地址格式为 ",
|
|
7331
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7332
|
+
className: "skai-kb-help-code",
|
|
7333
|
+
children: "http://主机:11434"
|
|
7334
|
+
}),
|
|
7335
|
+
"。"
|
|
7336
|
+
]
|
|
7337
|
+
}),
|
|
7338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
7339
|
+
className: "skai-kb-help-text",
|
|
7340
|
+
children: [
|
|
7341
|
+
"GLM-OCR 为任务专用模型,识别提示词需用官方触发前缀:全文",
|
|
7342
|
+
" ",
|
|
7343
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7344
|
+
className: "skai-kb-help-code",
|
|
7345
|
+
children: "Text Recognition:"
|
|
7346
|
+
}),
|
|
7347
|
+
"、表格",
|
|
7348
|
+
" ",
|
|
7349
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7350
|
+
className: "skai-kb-help-code",
|
|
7351
|
+
children: "Table Recognition:"
|
|
7352
|
+
}),
|
|
7353
|
+
"、图",
|
|
7354
|
+
" ",
|
|
7355
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7356
|
+
className: "skai-kb-help-code",
|
|
7357
|
+
children: "Figure Recognition:"
|
|
7358
|
+
}),
|
|
7359
|
+
";选择预设时已在提示词为空的情况下回填默认前缀",
|
|
7360
|
+
" ",
|
|
7361
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7362
|
+
className: "skai-kb-help-code",
|
|
7363
|
+
children: "Text Recognition:"
|
|
7364
|
+
}),
|
|
7365
|
+
",自定义提示词不受影响。"
|
|
7366
|
+
]
|
|
7367
|
+
})
|
|
7368
|
+
]
|
|
7369
|
+
}),
|
|
7370
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7371
|
+
className: "skai-kb-help-section",
|
|
7372
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
7373
|
+
className: "skai-kb-help-title",
|
|
7374
|
+
children: "密钥与生效方式"
|
|
7375
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
7376
|
+
className: "skai-kb-help-text",
|
|
7377
|
+
children: [
|
|
7378
|
+
"编辑时密钥留空或保留掩码串(含 ",
|
|
7379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
7380
|
+
className: "skai-kb-help-code",
|
|
7381
|
+
children: "****"
|
|
7382
|
+
}),
|
|
7383
|
+
")表示沿用当前生效密钥,填入新值即更新;保存后立即生效,无需重启服务;「恢复默认」清除设置页覆盖并回落到部署环境变量配置。"
|
|
7384
|
+
]
|
|
7385
|
+
})]
|
|
7386
|
+
})
|
|
7387
|
+
]
|
|
7388
|
+
});
|
|
7389
|
+
}
|
|
7390
|
+
//#endregion
|
|
7391
|
+
//#region src/components/settings/UserManagePanel.jsx
|
|
7392
|
+
/**
|
|
7393
|
+
* 用户管理区块(设置页;仅管理员可见可操作,非管理员仅展示状态说明)
|
|
7394
|
+
*
|
|
7395
|
+
* - 用户列表:昵称(用户中心批量获取,仅展示)/ UID / 脱敏手机号 / 状态标签 /
|
|
7396
|
+
* 注册时间 / 最近审核信息;状态筛选(全部/待审核/正常/禁用)+ 关键词(UID 或
|
|
7397
|
+
* 手机号片段)+ 分页;管理员行标注「管理员」且不可被审核/禁用/删除
|
|
7398
|
+
* - 操作:审核通过(待审核→正常)、禁用(同步吊销其全部插件 Token)、启用
|
|
7399
|
+
* (禁用→正常)、删除(物理删除状态与授权,保留其知识库,悬空所有者由管理员
|
|
7400
|
+
* 经「修改所有者」接管);操作均写后端审计日志
|
|
7401
|
+
* - 新用户默认审核模式:需审核 / 无需审核(默认);切换仅影响之后首次验签的新用户
|
|
7402
|
+
*/
|
|
7403
|
+
const PAGE_SIZE = 20;
|
|
7404
|
+
/** 用户状态 → 展示文案与徽标色系(0 待审核 / 1 正常 / 2 禁用) */
|
|
7405
|
+
const STATUS_VIEW = {
|
|
7406
|
+
0: {
|
|
7407
|
+
text: "待审核",
|
|
7408
|
+
badge: "skai-kb-badge-amber"
|
|
7409
|
+
},
|
|
7410
|
+
1: {
|
|
7411
|
+
text: "正常",
|
|
7412
|
+
badge: "skai-kb-badge-green"
|
|
7413
|
+
},
|
|
7414
|
+
2: {
|
|
7415
|
+
text: "禁用",
|
|
7416
|
+
badge: "skai-kb-badge-red"
|
|
7417
|
+
}
|
|
7418
|
+
};
|
|
7419
|
+
const STATUS_FILTERS = [
|
|
7420
|
+
{
|
|
7421
|
+
value: -1,
|
|
7422
|
+
label: "全部"
|
|
7423
|
+
},
|
|
7424
|
+
{
|
|
7425
|
+
value: 0,
|
|
7426
|
+
label: "待审核"
|
|
7427
|
+
},
|
|
7428
|
+
{
|
|
7429
|
+
value: 1,
|
|
7430
|
+
label: "正常"
|
|
7431
|
+
},
|
|
7432
|
+
{
|
|
7433
|
+
value: 2,
|
|
7434
|
+
label: "禁用"
|
|
7435
|
+
}
|
|
7436
|
+
];
|
|
7437
|
+
function UserManagePanel({ isAdmin }) {
|
|
7438
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
7439
|
+
const [users, setUsers] = (0, react.useState)([]);
|
|
7440
|
+
const [total, setTotal] = (0, react.useState)(0);
|
|
7441
|
+
const [pendingCount, setPendingCount] = (0, react.useState)(0);
|
|
7442
|
+
const [status, setStatus] = (0, react.useState)(-1);
|
|
7443
|
+
const [keyword, setKeyword] = (0, react.useState)("");
|
|
7444
|
+
const [page, setPage] = (0, react.useState)(1);
|
|
7445
|
+
const [mode, setMode] = (0, react.useState)(null);
|
|
7446
|
+
const [modeBusy, setModeBusy] = (0, react.useState)(false);
|
|
7447
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
7448
|
+
const [confirm, setConfirm] = (0, react.useState)(null);
|
|
7449
|
+
const { infoMap } = useMemberInfo(users.map((u) => u.uid));
|
|
7450
|
+
const reload = (0, react.useCallback)(() => {
|
|
7451
|
+
if (!isAdmin) {
|
|
7452
|
+
setLoading(false);
|
|
7453
|
+
return;
|
|
7454
|
+
}
|
|
7455
|
+
setLoading(true);
|
|
7456
|
+
listUsers({
|
|
7457
|
+
status,
|
|
7458
|
+
keyword: keyword.trim() || void 0,
|
|
7459
|
+
page,
|
|
7460
|
+
limit: PAGE_SIZE
|
|
7461
|
+
}).then((res) => {
|
|
7462
|
+
setUsers(Array.isArray(res?.list) ? res.list : []);
|
|
7463
|
+
setTotal(Number(res?.total) || 0);
|
|
7464
|
+
setPendingCount(Number(res?.pending_count) || 0);
|
|
7465
|
+
}).catch(toastError).finally(() => setLoading(false));
|
|
7466
|
+
}, [
|
|
7467
|
+
isAdmin,
|
|
7468
|
+
status,
|
|
7469
|
+
keyword,
|
|
7470
|
+
page
|
|
7471
|
+
]);
|
|
7472
|
+
(0, react.useEffect)(reload, [reload]);
|
|
7473
|
+
(0, react.useEffect)(() => {
|
|
7474
|
+
if (!isAdmin) return;
|
|
7475
|
+
getReviewConfig().then((res) => setMode(Number(res?.mode) === 1 ? 1 : 0)).catch(() => setMode(0));
|
|
7476
|
+
}, [isAdmin]);
|
|
7477
|
+
const switchMode = (next) => {
|
|
7478
|
+
if (modeBusy || next === mode) return;
|
|
7479
|
+
setModeBusy(true);
|
|
7480
|
+
setReviewConfig(next).then(() => {
|
|
7481
|
+
setMode(next);
|
|
7482
|
+
toast(next === 1 ? "已开启需审核:新用户首次进入知识库将处于待审核状态" : "已关闭审核:新用户首次进入即为正常状态", "success");
|
|
7483
|
+
}).catch(toastError).finally(() => setModeBusy(false));
|
|
7484
|
+
};
|
|
7485
|
+
const runAction = () => {
|
|
7486
|
+
const { action, user } = confirm;
|
|
7487
|
+
setBusy(true);
|
|
7488
|
+
action(user.uid).then(() => {
|
|
7489
|
+
toast("操作成功", "success");
|
|
7490
|
+
setConfirm(null);
|
|
7491
|
+
reload();
|
|
7492
|
+
}).catch(toastError).finally(() => setBusy(false));
|
|
7493
|
+
};
|
|
7494
|
+
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
7495
|
+
const canPrev = page > 1;
|
|
7496
|
+
const canNext = page < totalPages;
|
|
7497
|
+
if (!isAdmin) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
7498
|
+
className: "skai-kb-panel",
|
|
7499
|
+
"aria-label": "用户管理",
|
|
7500
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7501
|
+
className: "skai-kb-panel-head",
|
|
7502
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "用户管理" })
|
|
7503
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7504
|
+
className: "skai-kb-sub",
|
|
7505
|
+
children: "用户管理仅管理员可用:审核新用户、禁用/启用账号、删除用户与配置新用户审核模式。"
|
|
7506
|
+
})]
|
|
7507
|
+
});
|
|
7508
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
7509
|
+
className: "skai-kb-panel",
|
|
7510
|
+
"aria-label": "用户管理",
|
|
7511
|
+
children: [
|
|
7512
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7513
|
+
className: "skai-kb-panel-head",
|
|
7514
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", { children: ["用户管理", pendingCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7515
|
+
className: "skai-kb-badge skai-kb-badge-amber",
|
|
7516
|
+
children: [pendingCount, " 待审核"]
|
|
7517
|
+
}) : null] })
|
|
7518
|
+
}),
|
|
7519
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7520
|
+
className: "skai-kb-user-review-mode",
|
|
7521
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7522
|
+
className: "skai-kb-sub",
|
|
7523
|
+
style: { padding: 0 },
|
|
7524
|
+
children: "新用户默认审核模式(仅影响之后首次进入的新用户,不影响存量用户)"
|
|
7525
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7526
|
+
className: "skai-kb-row-actions",
|
|
7527
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7528
|
+
type: "button",
|
|
7529
|
+
className: `skai-kb-btn skai-kb-btn-sm ${mode === 1 ? "skai-kb-btn-primary" : ""}`,
|
|
7530
|
+
disabled: mode === null || modeBusy,
|
|
7531
|
+
onClick: () => switchMode(1),
|
|
7532
|
+
children: "需审核"
|
|
7533
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7534
|
+
type: "button",
|
|
7535
|
+
className: `skai-kb-btn skai-kb-btn-sm ${mode === 0 ? "skai-kb-btn-primary" : ""}`,
|
|
7536
|
+
disabled: mode === null || modeBusy,
|
|
7537
|
+
onClick: () => switchMode(0),
|
|
7538
|
+
children: "无需审核"
|
|
7539
|
+
})]
|
|
7540
|
+
})]
|
|
7541
|
+
}),
|
|
7542
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7543
|
+
className: "skai-kb-user-toolbar",
|
|
7544
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7545
|
+
className: "skai-kb-row-actions",
|
|
7546
|
+
children: STATUS_FILTERS.map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7547
|
+
type: "button",
|
|
7548
|
+
className: `skai-kb-btn skai-kb-btn-sm ${status === f.value ? "skai-kb-btn-primary" : ""}`,
|
|
7549
|
+
onClick: () => {
|
|
7550
|
+
setStatus(f.value);
|
|
7551
|
+
setPage(1);
|
|
7552
|
+
},
|
|
7553
|
+
children: f.label
|
|
7554
|
+
}, f.value))
|
|
7555
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7556
|
+
className: "skai-kb-input skai-kb-user-search",
|
|
7557
|
+
placeholder: "搜索 UID 或手机号片段",
|
|
6384
7558
|
value: keyword,
|
|
6385
7559
|
onChange: (e) => {
|
|
6386
7560
|
setKeyword(e.target.value);
|
|
@@ -6563,10 +7737,14 @@ window.__ModuleLoader__.load({
|
|
|
6563
7737
|
* - 页头:标题「设置」+ 右上角「添加服务器」主操作
|
|
6564
7738
|
* - 服务器配置:多服务器增删改切(云持久化 knowledge.servers / knowledge.serverActive),
|
|
6565
7739
|
* 当前服务器即 REST 基址与知识库 MCP 接入点,变更经 auth.subscribe 链路自动同步宿主
|
|
7740
|
+
* - 服务器探测:逐个探测已添加服务器(GET /kb/health 免鉴权),校验部署的应用id与版本
|
|
7741
|
+
* 和插件是否匹配,不匹配在该服务器条目下方红色提示(发布机制三处同号)
|
|
6566
7742
|
* - 管理员:全局角色(昵称/uid/脱敏手机号/添加时间,资料经用户中心批量获取仅展示);
|
|
6567
7743
|
* 管理员可添加、删除其他管理员(最后一个管理员不可删除);尚无任何管理员时可将
|
|
6568
7744
|
* 当前登录用户初始化为管理员(首次部署「添加管理员」流程)
|
|
6569
7745
|
* - 智能体(MCP)同步状态:宿主配置自检 + 手动重新自检 + 接入点展示
|
|
7746
|
+
* - 模型服务(仅管理员):向量化 / 重排 / OCR 的公网模型服务配置(优先推荐硅基流动),
|
|
7747
|
+
* 各行展示配置来源与模型名,支持编辑(含连通性测试、恢复默认)与配置步骤帮助
|
|
6570
7748
|
* - 账号信息:当前用户角色与 UID(仅展示)
|
|
6571
7749
|
*/
|
|
6572
7750
|
/** MCP 自检状态 → 展示文案与色系 */
|
|
@@ -6611,6 +7789,36 @@ window.__ModuleLoader__.load({
|
|
|
6611
7789
|
danger: "skai-kb-badge-red",
|
|
6612
7790
|
neutral: "skai-kb-badge-gray"
|
|
6613
7791
|
};
|
|
7792
|
+
/** 模型服务三类配置项(面板行展示名与编辑 key,对应后端 /kb/model-config 的 item) */
|
|
7793
|
+
const MODEL_ITEMS = [
|
|
7794
|
+
{
|
|
7795
|
+
key: "embedding",
|
|
7796
|
+
name: "向量化(Embedding)"
|
|
7797
|
+
},
|
|
7798
|
+
{
|
|
7799
|
+
key: "rerank",
|
|
7800
|
+
name: "重排(Rerank)"
|
|
7801
|
+
},
|
|
7802
|
+
{
|
|
7803
|
+
key: "ocr",
|
|
7804
|
+
name: "OCR(视觉识别)"
|
|
7805
|
+
}
|
|
7806
|
+
];
|
|
7807
|
+
/** 配置来源 → 展示文案与徽标色系(db=设置页覆盖 / env=环境变量 / unset=未配置) */
|
|
7808
|
+
const MODEL_SOURCE_VIEW = {
|
|
7809
|
+
db: {
|
|
7810
|
+
text: "设置页配置",
|
|
7811
|
+
badge: "skai-kb-badge-green"
|
|
7812
|
+
},
|
|
7813
|
+
env: {
|
|
7814
|
+
text: "环境变量配置",
|
|
7815
|
+
badge: "skai-kb-badge-blue"
|
|
7816
|
+
},
|
|
7817
|
+
unset: {
|
|
7818
|
+
text: "未配置",
|
|
7819
|
+
badge: "skai-kb-badge-gray"
|
|
7820
|
+
}
|
|
7821
|
+
};
|
|
6614
7822
|
function SettingsPage() {
|
|
6615
7823
|
const [, forceUpdate] = (0, react.useState)(0);
|
|
6616
7824
|
const [editing, setEditing] = (0, react.useState)(null);
|
|
@@ -6629,48 +7837,7 @@ window.__ModuleLoader__.load({
|
|
|
6629
7837
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
6630
7838
|
const servers = getServers();
|
|
6631
7839
|
const active = getActiveServer();
|
|
6632
|
-
const activeBase = active ? active.api_base : "";
|
|
6633
7840
|
const cloudOk = isCloudPersistAvailable();
|
|
6634
|
-
const pluginVersion = String(manifest.version || "");
|
|
6635
|
-
const [backendVersion, setBackendVersion] = (0, react.useState)({
|
|
6636
|
-
checking: false,
|
|
6637
|
-
version: "",
|
|
6638
|
-
error: ""
|
|
6639
|
-
});
|
|
6640
|
-
(0, react.useEffect)(() => {
|
|
6641
|
-
if (!activeBase) {
|
|
6642
|
-
setBackendVersion({
|
|
6643
|
-
checking: false,
|
|
6644
|
-
version: "",
|
|
6645
|
-
error: ""
|
|
6646
|
-
});
|
|
6647
|
-
return;
|
|
6648
|
-
}
|
|
6649
|
-
let cancelled = false;
|
|
6650
|
-
setBackendVersion({
|
|
6651
|
-
checking: true,
|
|
6652
|
-
version: "",
|
|
6653
|
-
error: ""
|
|
6654
|
-
});
|
|
6655
|
-
getHealth().then((health) => {
|
|
6656
|
-
if (!cancelled) setBackendVersion({
|
|
6657
|
-
checking: false,
|
|
6658
|
-
version: String(health?.version || ""),
|
|
6659
|
-
error: ""
|
|
6660
|
-
});
|
|
6661
|
-
}).catch((error) => {
|
|
6662
|
-
if (!cancelled) setBackendVersion({
|
|
6663
|
-
checking: false,
|
|
6664
|
-
version: "",
|
|
6665
|
-
error: error && error.msg || "无法获取后端版本"
|
|
6666
|
-
});
|
|
6667
|
-
});
|
|
6668
|
-
return () => {
|
|
6669
|
-
cancelled = true;
|
|
6670
|
-
};
|
|
6671
|
-
}, [activeBase]);
|
|
6672
|
-
const backendVer = backendVersion.version;
|
|
6673
|
-
const versionMismatch = Boolean(backendVer) && backendVer !== "dev" && backendVer.replace(/^v/i, "") !== pluginVersion.replace(/^v/i, "");
|
|
6674
7841
|
const { infoMap } = useMemberInfo(adminInfo.list.map((m) => m.uid));
|
|
6675
7842
|
(0, react.useEffect)(() => {
|
|
6676
7843
|
return subscribe(() => forceUpdate((n) => n + 1));
|
|
@@ -6702,6 +7869,70 @@ window.__ModuleLoader__.load({
|
|
|
6702
7869
|
if (typeof off === "function") off();
|
|
6703
7870
|
};
|
|
6704
7871
|
}, [runMcpCheck]);
|
|
7872
|
+
const [healthMap, setHealthMap] = (0, react.useState)({});
|
|
7873
|
+
const serverSig = servers.map((s) => `${s.id}:${s.api_base}`).join("|");
|
|
7874
|
+
(0, react.useEffect)(() => {
|
|
7875
|
+
let cancelled = false;
|
|
7876
|
+
for (const s of servers) {
|
|
7877
|
+
setHealthMap((m) => m[s.id] && m[s.id].state === "checking" ? m : {
|
|
7878
|
+
...m,
|
|
7879
|
+
[s.id]: { state: "checking" }
|
|
7880
|
+
});
|
|
7881
|
+
getHealthByBase(s.api_base).then((data) => {
|
|
7882
|
+
if (!cancelled) setHealthMap((m) => ({
|
|
7883
|
+
...m,
|
|
7884
|
+
[s.id]: {
|
|
7885
|
+
state: "ok",
|
|
7886
|
+
app: String(data?.app || ""),
|
|
7887
|
+
version: String(data?.version || "")
|
|
7888
|
+
}
|
|
7889
|
+
}));
|
|
7890
|
+
}).catch((error) => {
|
|
7891
|
+
if (!cancelled) setHealthMap((m) => ({
|
|
7892
|
+
...m,
|
|
7893
|
+
[s.id]: {
|
|
7894
|
+
state: "error",
|
|
7895
|
+
error: error && (error.msg || error.message) || "请求失败"
|
|
7896
|
+
}
|
|
7897
|
+
}));
|
|
7898
|
+
});
|
|
7899
|
+
}
|
|
7900
|
+
return () => {
|
|
7901
|
+
cancelled = true;
|
|
7902
|
+
};
|
|
7903
|
+
}, [serverSig]);
|
|
7904
|
+
const healthView = (h) => {
|
|
7905
|
+
if (!h || h.state === "checking") return {
|
|
7906
|
+
cls: "is-muted",
|
|
7907
|
+
text: "应用与版本检测中…"
|
|
7908
|
+
};
|
|
7909
|
+
if (h.state === "error") return {
|
|
7910
|
+
cls: "is-danger",
|
|
7911
|
+
text: `服务器不可达:${h.error}`
|
|
7912
|
+
};
|
|
7913
|
+
if (h.app && h.app !== manifest.id) return {
|
|
7914
|
+
cls: "is-danger",
|
|
7915
|
+
text: `应用不匹配:该服务器部署的是「${h.app}」,本插件为 ${manifest.id}`
|
|
7916
|
+
};
|
|
7917
|
+
if (!h.version) return {
|
|
7918
|
+
cls: "is-danger",
|
|
7919
|
+
text: "后端未上报版本(版本过旧),请升级后端",
|
|
7920
|
+
tips: [`升级后端对应版本:在「时刻AI后台」将该应用升级到 v${manifest.version}`]
|
|
7921
|
+
};
|
|
7922
|
+
if (h.version === "dev") return {
|
|
7923
|
+
cls: "is-muted",
|
|
7924
|
+
text: "后端 dev(未注入版本,本地开发构建)"
|
|
7925
|
+
};
|
|
7926
|
+
if (h.version !== manifest.version) return {
|
|
7927
|
+
cls: "is-danger",
|
|
7928
|
+
text: `前后端版本不一致:后端 v${h.version} / 插件 ${manifest.version}`,
|
|
7929
|
+
tips: [`部署前端对应版本:dsh plugin --profile web add ${manifest.packageName}@${h.version}`, `升级后端对应版本:在「时刻AI后台」将该应用升级到 v${manifest.version}`]
|
|
7930
|
+
};
|
|
7931
|
+
return {
|
|
7932
|
+
cls: "is-ok",
|
|
7933
|
+
text: `应用与版本一致 v${h.version}`
|
|
7934
|
+
};
|
|
7935
|
+
};
|
|
6705
7936
|
const reloadAdmin = () => {
|
|
6706
7937
|
setAdminInfo((s) => ({
|
|
6707
7938
|
...s,
|
|
@@ -6722,6 +7953,16 @@ window.__ModuleLoader__.load({
|
|
|
6722
7953
|
}));
|
|
6723
7954
|
};
|
|
6724
7955
|
(0, react.useEffect)(reloadAdmin, []);
|
|
7956
|
+
const [modelConfig, setModelConfig] = (0, react.useState)(null);
|
|
7957
|
+
const [modelLoading, setModelLoading] = (0, react.useState)(false);
|
|
7958
|
+
const [showModelHelp, setShowModelHelp] = (0, react.useState)(false);
|
|
7959
|
+
const [modelEditItem, setModelEditItem] = (0, react.useState)(null);
|
|
7960
|
+
const reloadModelConfig = (0, react.useCallback)(() => {
|
|
7961
|
+
if (!adminInfo.isAdmin) return;
|
|
7962
|
+
setModelLoading(true);
|
|
7963
|
+
getModelConfig().then((res) => setModelConfig(res || null)).catch(toastError).finally(() => setModelLoading(false));
|
|
7964
|
+
}, [adminInfo.isAdmin]);
|
|
7965
|
+
(0, react.useEffect)(reloadModelConfig, [reloadModelConfig]);
|
|
6725
7966
|
const [myStatus, setMyStatus] = (0, react.useState)(null);
|
|
6726
7967
|
(0, react.useEffect)(() => {
|
|
6727
7968
|
let cancelled = false;
|
|
@@ -6861,20 +8102,32 @@ window.__ModuleLoader__.load({
|
|
|
6861
8102
|
className: "skai-kb-server-list",
|
|
6862
8103
|
children: servers.map((s) => {
|
|
6863
8104
|
const isActive = Boolean(active && active.id === s.id);
|
|
8105
|
+
const health = healthView(healthMap[s.id]);
|
|
6864
8106
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
6865
8107
|
className: `skai-kb-server-item ${isActive ? "is-active" : ""}`,
|
|
6866
8108
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6867
8109
|
className: "skai-kb-server-info",
|
|
6868
|
-
children: [
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
8110
|
+
children: [
|
|
8111
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8112
|
+
className: "skai-kb-server-name",
|
|
8113
|
+
children: [s.name, isActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8114
|
+
className: "skai-kb-badge skai-kb-badge-green",
|
|
8115
|
+
children: "当前"
|
|
8116
|
+
}) : null]
|
|
8117
|
+
}),
|
|
8118
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8119
|
+
className: "skai-kb-server-url",
|
|
8120
|
+
children: s.api_base
|
|
8121
|
+
}),
|
|
8122
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8123
|
+
className: `skai-kb-server-health ${health.cls}`,
|
|
8124
|
+
children: health.text
|
|
8125
|
+
}),
|
|
8126
|
+
(health.tips || []).map((tip) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8127
|
+
className: "skai-kb-server-health is-muted",
|
|
8128
|
+
children: ["· ", tip]
|
|
8129
|
+
}, tip))
|
|
8130
|
+
]
|
|
6878
8131
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6879
8132
|
className: "skai-kb-row-actions",
|
|
6880
8133
|
children: [
|
|
@@ -6898,36 +8151,115 @@ window.__ModuleLoader__.load({
|
|
|
6898
8151
|
})
|
|
6899
8152
|
]
|
|
6900
8153
|
})]
|
|
6901
|
-
}, s.id);
|
|
6902
|
-
})
|
|
6903
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6904
|
-
className: "skai-kb-empty skai-kb-empty-sub",
|
|
6905
|
-
children: "尚未配置服务器。添加后即可使用知识库全部功能,并自动注册 mcp__knowledge__* 智能体工具。"
|
|
8154
|
+
}, s.id);
|
|
8155
|
+
})
|
|
8156
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8157
|
+
className: "skai-kb-empty skai-kb-empty-sub",
|
|
8158
|
+
children: "尚未配置服务器。添加后即可使用知识库全部功能,并自动注册 mcp__knowledge__* 智能体工具。"
|
|
8159
|
+
}),
|
|
8160
|
+
!cloudOk ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8161
|
+
className: "skai-kb-sub skai-kb-cloud-hint",
|
|
8162
|
+
children: "当前为游客态或用户中心配置接口未就绪:服务器配置仅本次会话内有效,登录后自动云同步。"
|
|
8163
|
+
}) : null
|
|
8164
|
+
]
|
|
8165
|
+
}),
|
|
8166
|
+
adminInfo.isAdmin ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
8167
|
+
className: "skai-kb-panel",
|
|
8168
|
+
"aria-label": "模型服务",
|
|
8169
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8170
|
+
className: "skai-kb-panel-head",
|
|
8171
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "模型服务" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8172
|
+
className: "skai-kb-muted",
|
|
8173
|
+
children: "向量化 / 重排 / OCR 支持调用互联网公网模型服务,优先推荐硅基流动"
|
|
8174
|
+
})]
|
|
8175
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
8176
|
+
className: "skai-kb-server-list",
|
|
8177
|
+
children: modelLoading && !modelConfig ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
8178
|
+
className: "skai-kb-sub",
|
|
8179
|
+
children: "配置加载中…"
|
|
8180
|
+
}) : MODEL_ITEMS.map(({ key, name }) => {
|
|
8181
|
+
const entry = (modelConfig || {})[key] || {};
|
|
8182
|
+
const sourceView = MODEL_SOURCE_VIEW[entry.source] || MODEL_SOURCE_VIEW.unset;
|
|
8183
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
8184
|
+
className: "skai-kb-server-item",
|
|
8185
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8186
|
+
className: "skai-kb-server-info",
|
|
8187
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8188
|
+
className: "skai-kb-server-name",
|
|
8189
|
+
children: [
|
|
8190
|
+
name,
|
|
8191
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8192
|
+
className: `skai-kb-badge ${sourceView.badge}`,
|
|
8193
|
+
children: sourceView.text
|
|
8194
|
+
}),
|
|
8195
|
+
entry.enabled === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8196
|
+
className: "skai-kb-badge skai-kb-badge-red",
|
|
8197
|
+
children: "未启用"
|
|
8198
|
+
}) : null
|
|
8199
|
+
]
|
|
8200
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8201
|
+
className: "skai-kb-server-url",
|
|
8202
|
+
children: ["模型:", entry.config?.model || "—"]
|
|
8203
|
+
})]
|
|
8204
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8205
|
+
className: "skai-kb-row-actions",
|
|
8206
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8207
|
+
type: "button",
|
|
8208
|
+
className: "skai-kb-btn skai-kb-btn-sm",
|
|
8209
|
+
onClick: () => setShowModelHelp(true),
|
|
8210
|
+
children: "配置说明"
|
|
8211
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8212
|
+
type: "button",
|
|
8213
|
+
className: "skai-kb-btn skai-kb-btn-sm",
|
|
8214
|
+
onClick: () => setModelEditItem(key),
|
|
8215
|
+
children: "编辑"
|
|
8216
|
+
})]
|
|
8217
|
+
})]
|
|
8218
|
+
}, key);
|
|
8219
|
+
})
|
|
8220
|
+
})]
|
|
8221
|
+
}) : null,
|
|
8222
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
8223
|
+
className: "skai-kb-panel",
|
|
8224
|
+
"aria-label": "MCP 同步状态",
|
|
8225
|
+
children: [
|
|
8226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8227
|
+
className: "skai-kb-panel-head",
|
|
8228
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "智能体(MCP)同步状态" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8229
|
+
type: "button",
|
|
8230
|
+
className: "skai-kb-btn skai-kb-btn-sm",
|
|
8231
|
+
disabled: mcpStatus.checking,
|
|
8232
|
+
onClick: runMcpCheck,
|
|
8233
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
8234
|
+
size: 12,
|
|
8235
|
+
d: P.refresh
|
|
8236
|
+
}), "重新自检"]
|
|
8237
|
+
})]
|
|
8238
|
+
}),
|
|
8239
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8240
|
+
className: "skai-kb-mcp-status",
|
|
8241
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8242
|
+
className: `skai-kb-badge ${TONE_BADGE[mcpView.tone] || "skai-kb-badge-gray"}`,
|
|
8243
|
+
children: mcpStatus.checking ? "检查中…" : mcpView.tone === "success" ? "就绪" : "未就绪"
|
|
8244
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8245
|
+
className: "skai-kb-mcp-text",
|
|
8246
|
+
children: [mcpView.text, mcpStatus.detail && mcpStatus.detail !== mcpView.text ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8247
|
+
className: "skai-kb-muted",
|
|
8248
|
+
children: [
|
|
8249
|
+
"(",
|
|
8250
|
+
mcpStatus.detail,
|
|
8251
|
+
")"
|
|
8252
|
+
]
|
|
8253
|
+
}) : null]
|
|
8254
|
+
})]
|
|
6906
8255
|
}),
|
|
6907
|
-
|
|
8256
|
+
active ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6908
8257
|
className: "skai-kb-sub",
|
|
6909
|
-
style: {
|
|
6910
|
-
display: "flex",
|
|
6911
|
-
alignItems: "center",
|
|
6912
|
-
gap: 8,
|
|
6913
|
-
flexWrap: "wrap",
|
|
6914
|
-
marginTop: 8
|
|
6915
|
-
},
|
|
6916
8258
|
children: [
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
children: ["插件版本:v", pluginVersion]
|
|
6921
|
-
}),
|
|
6922
|
-
versionMismatch ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6923
|
-
className: "skai-kb-badge skai-kb-badge-amber",
|
|
6924
|
-
children: "前后端版本不一致,请将镜像与插件升级到同一版本"
|
|
6925
|
-
}) : null
|
|
8259
|
+
"接入点:",
|
|
8260
|
+
active.api_base,
|
|
8261
|
+
"/kb/mcp/v1 · 智能体经 mcp__knowledge__ 前缀工具检索知识库"
|
|
6926
8262
|
]
|
|
6927
|
-
}) : null,
|
|
6928
|
-
!cloudOk ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6929
|
-
className: "skai-kb-sub skai-kb-cloud-hint",
|
|
6930
|
-
children: "当前为游客态或用户中心配置接口未就绪:服务器配置仅本次会话内有效,登录后自动云同步。"
|
|
6931
8263
|
}) : null
|
|
6932
8264
|
]
|
|
6933
8265
|
}),
|
|
@@ -7097,50 +8429,6 @@ window.__ModuleLoader__.load({
|
|
|
7097
8429
|
]
|
|
7098
8430
|
}) : null,
|
|
7099
8431
|
!statusRestricted && adminInfo.isAdmin ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserManagePanel, { isAdmin: true }) : null,
|
|
7100
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
7101
|
-
className: "skai-kb-panel",
|
|
7102
|
-
"aria-label": "MCP 同步状态",
|
|
7103
|
-
children: [
|
|
7104
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7105
|
-
className: "skai-kb-panel-head",
|
|
7106
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "智能体(MCP)同步状态" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7107
|
-
type: "button",
|
|
7108
|
-
className: "skai-kb-btn skai-kb-btn-sm",
|
|
7109
|
-
disabled: mcpStatus.checking,
|
|
7110
|
-
onClick: runMcpCheck,
|
|
7111
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
7112
|
-
size: 12,
|
|
7113
|
-
d: P.refresh
|
|
7114
|
-
}), "重新自检"]
|
|
7115
|
-
})]
|
|
7116
|
-
}),
|
|
7117
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7118
|
-
className: "skai-kb-mcp-status",
|
|
7119
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7120
|
-
className: `skai-kb-badge ${TONE_BADGE[mcpView.tone] || "skai-kb-badge-gray"}`,
|
|
7121
|
-
children: mcpStatus.checking ? "检查中…" : mcpView.tone === "success" ? "就绪" : "未就绪"
|
|
7122
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7123
|
-
className: "skai-kb-mcp-text",
|
|
7124
|
-
children: [mcpView.text, mcpStatus.detail && mcpStatus.detail !== mcpView.text ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7125
|
-
className: "skai-kb-muted",
|
|
7126
|
-
children: [
|
|
7127
|
-
"(",
|
|
7128
|
-
mcpStatus.detail,
|
|
7129
|
-
")"
|
|
7130
|
-
]
|
|
7131
|
-
}) : null]
|
|
7132
|
-
})]
|
|
7133
|
-
}),
|
|
7134
|
-
active ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7135
|
-
className: "skai-kb-sub",
|
|
7136
|
-
children: [
|
|
7137
|
-
"接入点:",
|
|
7138
|
-
active.api_base,
|
|
7139
|
-
"/kb/mcp/v1 · 智能体经 mcp__knowledge__ 前缀工具检索知识库"
|
|
7140
|
-
]
|
|
7141
|
-
}) : null
|
|
7142
|
-
]
|
|
7143
|
-
}),
|
|
7144
8432
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
7145
8433
|
className: "skai-kb-panel",
|
|
7146
8434
|
"aria-label": "账号信息",
|
|
@@ -7208,6 +8496,17 @@ window.__ModuleLoader__.load({
|
|
|
7208
8496
|
onConfirm: handleRemovePending,
|
|
7209
8497
|
onClose: () => setRemovingPending(null)
|
|
7210
8498
|
}) : null,
|
|
8499
|
+
modelEditItem ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelServiceModal, {
|
|
8500
|
+
item: modelEditItem,
|
|
8501
|
+
open: true,
|
|
8502
|
+
config: modelConfig?.[modelEditItem]?.config || null,
|
|
8503
|
+
onClose: () => setModelEditItem(null),
|
|
8504
|
+
onSaved: () => {
|
|
8505
|
+
setModelEditItem(null);
|
|
8506
|
+
reloadModelConfig();
|
|
8507
|
+
}
|
|
8508
|
+
}) : null,
|
|
8509
|
+
showModelHelp ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelConfigHelpModal, { onClose: () => setShowModelHelp(false) }) : null,
|
|
7211
8510
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toast, {})
|
|
7212
8511
|
]
|
|
7213
8512
|
});
|
|
@@ -7224,7 +8523,7 @@ window.__ModuleLoader__.load({
|
|
|
7224
8523
|
*/
|
|
7225
8524
|
//#endregion
|
|
7226
8525
|
//#region src/styles/knowledge.css?raw
|
|
7227
|
-
var knowledge_default = "/* ============================================================\n 时刻AI知识库插件样式(前缀 skai-kb-,不覆盖宿主样式)\n - 0.8.0 起渲染于 DSH 右侧 Sidebar 标签页正文(首页 / 知识库详情 /\n 文档详情 / 设置 各为一个标签页)与 standalone 调试台:\n .skai-kb-page 声明 inline-size 容器查询(与 .skai-kb-app 同名 skai-kb),\n 布局按容器宽度自适应,不依赖视口宽度(面板开合、拖宽均正确响应)\n - 颜色全部取 DSH 官方主题令牌(--dsw-alias-*),随宿主\n 浅色/深色主题自动切换;var() 兜底值仅供 standalone 预览\n (无宿主令牌)使用\n - z-index 使用适中值(≤35),禁止覆盖宿主设置菜单\n ============================================================ */\n\n/* ---------- 主题令牌别名层(统一映射到 DSH 官方令牌) ----------\n DSH 强调色语义(与常见设计系统相反,务必注意):\n - --dsw-alias-brand-primary:亮色主题为深色(bluish-1000)、暗色主题为浅色\n (bluish-50),即「反色强调」——主按钮填充随主题黑白互换\n - --dsw-alias-label-primary-foreground:强调填充上的前景文字,恒与填充反相\n (亮色=白、暗色=黑),是主按钮等强调填充上文字的正确取值。\n (DSH 无 --dsw-alias-brand-text;此前误用导致暗色模式白底白字)\n - --dsw-alias-link:正文链接色(亮 deepseek-500 / 暗 deepseek-400,恒为蓝) */\n.skai-kb-root,\n.skai-kb-modal-mask,\n.skai-kb-toast-layer {\n --skai-kb-bg: var(--dsw-alias-bg-base, #ffffff);\n --skai-kb-bg-card: var(--dsw-alias-bg-layer-1, #ffffff);\n --skai-kb-bg-soft: var(--dsw-alias-bg-layer-2, #f6f7f9);\n --skai-kb-bg-hover: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));\n --skai-kb-mask: var(--dsw-alias-bg-mask-1, rgba(17, 19, 23, 0.5));\n --skai-kb-border: var(--dsw-alias-border-l1, #e4e6eb);\n --skai-kb-border-strong: var(--dsw-alias-border-l2, #d0d3d9);\n --skai-kb-text: var(--dsw-alias-label-primary, #1f2329);\n --skai-kb-text-sub: var(--dsw-alias-label-secondary, #8a8f99);\n --skai-kb-text-caption: var(--dsw-alias-label-caption, #9ca1ab);\n /* 知识库品牌青色:与 DSH 侧边栏标签页/引导页图标同色(#06b6d4) */\n --skai-kb-primary: #06b6d4;\n /* 强调填充上的文字(官方前景令牌:亮=白 / 暗=黑;无 token 环境兜底白色) */\n --skai-kb-on-primary: var(--dsw-alias-label-primary-foreground, #ffffff);\n /* 主按钮:品牌青色实心 + 白字(青色为高亮度色相,不适用深底反色公式),hover 提亮一档(与 CRM 一致) */\n --skai-kb-btn-primary: var(--skai-kb-primary);\n --skai-kb-primary-weak: color-mix(in srgb, var(--skai-kb-primary) 12%, transparent);\n --skai-kb-danger: var(--dsw-alias-state-error-primary, #d83931);\n --skai-kb-danger-weak: color-mix(in srgb, var(--skai-kb-danger) 12%, transparent);\n --skai-kb-ok: var(--dsw-alias-state-success-primary, #2ea44f);\n --skai-kb-ok-weak: color-mix(in srgb, var(--skai-kb-ok) 14%, transparent);\n --skai-kb-warn: var(--dsw-alias-state-warn-primary, #d97706);\n --skai-kb-warn-weak: color-mix(in srgb, var(--skai-kb-warn) 14%, transparent);\n --skai-kb-link: var(--skai-kb-primary);\n --skai-kb-shadow: 0 8px 24px rgba(31, 35, 41, 0.12);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--skai-kb-text);\n background: var(--skai-kb-bg);\n scrollbar-width: thin;\n}\n\n/* 盒模型统一(与 CRM 一致):root 作用域内全部 border-box */\n.skai-kb-root *,\n.skai-kb-root *::before,\n.skai-kb-root *::after,\n.skai-kb-modal-mask *,\n.skai-kb-modal-mask *::before,\n.skai-kb-modal-mask *::after {\n box-sizing: border-box;\n}\n\n/* 原生 button 不继承字体,与 CRM 一致显式继承宿主字体 */\n.skai-kb-root button,\n.skai-kb-modal-mask button {\n font-family: inherit;\n}\n\n/* 深色模式(跟随系统 prefers-color-scheme,与 CRM 插件策略一致):\n 宿主令牌存在时仍优先取宿主值(var 包裹),此处仅提供 standalone 兜底 */\n@media (prefers-color-scheme: dark) {\n .skai-kb-root,\n .skai-kb-modal-mask,\n .skai-kb-toast-layer {\n --skai-kb-bg: var(--dsw-alias-bg-base, #1e1f22);\n --skai-kb-bg-card: var(--dsw-alias-bg-layer-1, #1e1f22);\n --skai-kb-bg-soft: var(--dsw-alias-bg-layer-2, #2a2b2e);\n --skai-kb-bg-hover: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n --skai-kb-mask: rgba(0, 0, 0, 0.55);\n --skai-kb-border: var(--dsw-alias-border-l1, #2c2d2f);\n --skai-kb-border-strong: var(--dsw-alias-border-l2, #3a3d42);\n --skai-kb-text: var(--dsw-alias-label-primary, #e8eaed);\n --skai-kb-text-sub: var(--dsw-alias-label-secondary, #8b8f96);\n --skai-kb-text-caption: var(--dsw-alias-label-caption, #6f747d);\n --skai-kb-primary: #22d3ee;\n --skai-kb-on-primary: var(--dsw-alias-label-primary-foreground, #10131a);\n --skai-kb-btn-primary: #06b6d4;\n --skai-kb-danger: var(--dsw-alias-state-error-primary, #f5564e);\n --skai-kb-ok: var(--dsw-alias-state-success-primary, #46c26a);\n --skai-kb-warn: var(--dsw-alias-state-warn-primary, #e8a23c);\n --skai-kb-link: var(--skai-kb-primary);\n --skai-kb-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- 布局骨架(容器查询基准) ---------- */\n.skai-kb-app {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n position: relative;\n container-type: inline-size;\n container-name: skai-kb;\n}\n\n/* standalone 调试台挂载容器(模拟抽屉内 100% 高度) */\n.skai-kb-standalone-inner {\n height: 100%;\n min-height: 0;\n}\n\n/* ---------- DSH 标签页正文骨架 ----------\n sidebar.right.pane.tab 正文:不滚动定位容器(弹窗/Toast 绝对定位收敛于此),\n 内容滚动由 .skai-kb-page-scroll 承担(文档详情为 .skai-kb-md-wrap) */\n.skai-kb-tab-root {\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.skai-kb-page {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 12px;\n height: 100%;\n min-height: 0;\n padding: 14px 16px;\n overflow: hidden;\n container-type: inline-size;\n container-name: skai-kb;\n}\n.skai-kb-tab-root > .skai-kb-page {\n flex: 1;\n}\n.skai-kb-page-scroll {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.skai-kb-header {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 14px 16px 0;\n flex: none;\n}\n.skai-kb-header-title {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 15px;\n font-weight: 600;\n color: var(--skai-kb-text);\n}\n\n/* 分区导航:窄容器横向滑动(不换行挤压),宽容器自然展开 */\n.skai-kb-nav {\n display: flex;\n gap: 4px;\n overflow-x: auto;\n scrollbar-width: none;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--skai-kb-border);\n align-items: center;\n}\n.skai-kb-nav::-webkit-scrollbar {\n display: none;\n}\n.skai-kb-nav-btn {\n flex: none;\n border: none;\n background: transparent;\n color: var(--skai-kb-text-sub);\n padding: 4px 10px;\n border-radius: 999px;\n cursor: pointer;\n font-size: 12px;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.skai-kb-nav-btn:hover {\n background: var(--skai-kb-bg-hover);\n color: var(--skai-kb-text);\n}\n.skai-kb-nav-btn.is-active {\n background: var(--skai-kb-primary-weak);\n color: var(--skai-kb-primary);\n font-weight: 500;\n}\n.skai-kb-nav-btn:focus-visible {\n outline: 2px solid var(--skai-kb-primary);\n outline-offset: 1px;\n}\n/* standalone 页签关闭钮 */\n.skai-kb-tab-close {\n flex: none;\n border: none;\n background: transparent;\n color: var(--skai-kb-text-caption);\n cursor: pointer;\n font-size: 10px;\n padding: 2px 4px;\n border-radius: 6px;\n}\n.skai-kb-tab-close:hover {\n background: var(--skai-kb-bg-hover);\n color: var(--skai-kb-danger);\n}\n\n.skai-kb-body {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 14px 16px;\n}\n\n/* ---------- 详情页头(知识库详情 / 文档详情标签页) ---------- */\n.skai-kb-detail-head {\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-bottom: 8px;\n}\n.skai-kb-detail-title {\n font-weight: 600;\n font-size: 15px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-back {\n flex: none;\n width: auto;\n min-width: 0;\n}\n\n/* ---------- 首页页头(左侧说明介绍 + 右上角设置入口) ---------- */\n.skai-kb-home-head {\n flex: none;\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 10px;\n padding-bottom: 10px;\n}\n.skai-kb-home-head-main {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.skai-kb-home-desc {\n font-size: 12px;\n line-height: 1.6;\n color: var(--skai-kb-text-sub);\n /* 介绍信息完整展示:不截断,自动换行铺满全量文字 */\n overflow-wrap: anywhere;\n}\n.skai-kb-home-head > .skai-kb-btn {\n flex: none;\n width: auto;\n margin-top: 1px;\n}\n\n/* ---------- 通用元素 ---------- */\n.skai-kb-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 5px;\n width: auto;\n height: 30px;\n padding: 0 12px;\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n background: var(--skai-kb-bg-card);\n color: var(--skai-kb-text);\n cursor: pointer;\n font-size: 12.5px;\n white-space: nowrap;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n.skai-kb-btn:hover:not(:disabled) {\n background: var(--skai-kb-bg-soft);\n}\n.skai-kb-btn:focus-visible {\n outline: 2px solid var(--skai-kb-primary);\n outline-offset: 1px;\n}\n.skai-kb-btn:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n}\n/* 等待转圈动画(SpinnerGlyph 专用) */\n@keyframes skai-kb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.skai-kb-spin {\n animation: skai-kb-spin 0.8s linear infinite;\n flex: none;\n}\n.skai-kb-btn-primary {\n background: var(--skai-kb-btn-primary);\n border-color: transparent;\n color: #fff;\n}\n.skai-kb-btn-primary:hover:not(:disabled) {\n background: var(--skai-kb-btn-primary);\n filter: brightness(1.08);\n}\n.skai-kb-btn-danger {\n background: var(--skai-kb-danger);\n border-color: var(--skai-kb-danger);\n color: #fff;\n}\n.skai-kb-btn-danger:hover:not(:disabled) {\n background: var(--skai-kb-danger);\n filter: brightness(1.05);\n}\n.skai-kb-btn-ghost-danger {\n border-color: transparent;\n background: transparent;\n color: var(--skai-kb-danger);\n}\n.skai-kb-btn-ghost-danger:hover:not(:disabled) {\n background: var(--skai-kb-danger-weak);\n}\n.skai-kb-btn-sm {\n width: auto;\n height: 26px;\n padding: 0 9px;\n font-size: 12px;\n border-radius: 8px;\n}\n\n.skai-kb-icon-btn {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--skai-kb-text-sub);\n cursor: pointer;\n border-radius: 8px;\n font-size: 12px;\n}\n.skai-kb-icon-btn:hover {\n background: var(--skai-kb-bg-hover);\n color: var(--skai-kb-text);\n}\n/* 悬停文字提示(CSS 实现,即时显示;按钮右上角对齐避免溢出弹窗) */\n.skai-kb-icon-btn[data-tip]:hover::after {\n content: attr(data-tip);\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 30;\n padding: 4px 8px;\n border-radius: 6px;\n background: var(--skai-kb-bg);\n color: var(--skai-kb-text);\n font-size: 12px;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n}\n\n.skai-kb-input,\n.skai-kb-select,\n.skai-kb-textarea {\n width: 100%;\n padding: 5px 10px;\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n background: var(--skai-kb-bg);\n color: var(--skai-kb-text);\n /* 原生表单控件不继承字体,与 CRM 一致显式继承 */\n font: inherit;\n font-size: 12.5px;\n box-sizing: border-box;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n/* 下拉选择与 CRM 一致使用手型光标 */\n.skai-kb-select {\n cursor: pointer;\n}\n/* 单行输入控件与按钮(30px)同高 */\n.skai-kb-input,\n.skai-kb-select {\n height: 30px;\n}\n.skai-kb-textarea {\n min-height: 56px;\n resize: vertical;\n line-height: 1.6;\n}\n.skai-kb-input:focus,\n.skai-kb-select:focus,\n.skai-kb-textarea:focus {\n outline: none;\n border-color: var(--skai-kb-primary);\n box-shadow: 0 0 0 2px var(--skai-kb-primary-weak);\n}\n.skai-kb-input::placeholder,\n.skai-kb-textarea::placeholder {\n color: var(--skai-kb-text-caption);\n}\n.skai-kb-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-bottom: 10px;\n}\n.skai-kb-field-label {\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n}\n.skai-kb-field-error {\n font-size: 12px;\n color: var(--skai-kb-danger);\n}\n.skai-kb-field-row {\n display: flex;\n gap: 8px;\n}\n.skai-kb-field-row > * {\n flex: 1;\n}\n\n.skai-kb-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 10px;\n flex-wrap: wrap;\n flex: none;\n}\n.skai-kb-toolbar .skai-kb-search {\n flex: 1;\n min-width: 120px;\n}\n\n.skai-kb-card {\n border: 1px solid var(--skai-kb-border);\n border-radius: 10px;\n background: var(--skai-kb-bg-card);\n padding: 10px 12px;\n margin-bottom: 8px;\n transition: border-color 0.15s ease, background 0.15s ease;\n}\n.skai-kb-card:hover {\n border-color: var(--skai-kb-border-strong);\n}\n\n/* 卡片列表:每行一个知识库卡片(纵向堆叠,占满整行宽度) */\n.skai-kb-card-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.skai-kb-card-list > .skai-kb-card {\n margin-bottom: 0;\n}\n\n.skai-kb-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.skai-kb-row-between {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.skai-kb-title {\n font-weight: 600;\n font-size: 13px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-sub {\n padding: 7px 0 0 0;\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n /* 说明/介绍类文本完整展示:不截断省略,自动换行铺满全量文字 */\n overflow-wrap: anywhere;\n}\n\n.skai-kb-badge {\n display: inline-flex;\n align-items: center;\n padding: 0 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 18px;\n white-space: nowrap;\n}\n.skai-kb-badge-gray { background: var(--skai-kb-bg-soft); color: var(--skai-kb-text-sub); }\n.skai-kb-badge-blue { background: var(--skai-kb-primary-weak); color: var(--skai-kb-primary); }\n.skai-kb-badge-green { background: var(--skai-kb-ok-weak); color: var(--skai-kb-ok); }\n.skai-kb-badge-amber { background: var(--skai-kb-warn-weak); color: var(--skai-kb-warn); }\n.skai-kb-badge-red { background: var(--skai-kb-danger-weak); color: var(--skai-kb-danger); }\n\n.skai-kb-tag {\n display: inline-block;\n padding: 0 6px;\n border-radius: 4px;\n font-size: 11px;\n line-height: 18px;\n background: var(--skai-kb-bg-soft);\n color: var(--skai-kb-text-sub);\n margin-right: 4px;\n}\n\n.skai-kb-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n text-align: center;\n padding: 28px 16px;\n color: var(--skai-kb-text-sub);\n font-size: 13px;\n}\n.skai-kb-empty-sub {\n font-size: 12px;\n}\n\n.skai-kb-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 8px 0;\n color: var(--skai-kb-text-sub);\n font-size: 12px;\n flex: none;\n}\n.skai-kb-pagination-btns {\n display: flex;\n gap: 8px;\n}\n\n/* ---------- 设置页(对齐 CRM:页头 + panel 区块 + 行操作 + 详情列表) ---------- */\n.skai-kb-page-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n flex-wrap: wrap;\n flex: none;\n}\n.skai-kb-page-title {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n.skai-kb-page-title svg {\n color: var(--skai-kb-primary);\n}\n.skai-kb-page-actions {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.skai-kb-panel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border: 1px solid var(--skai-kb-border);\n border-radius: 10px;\n background: var(--skai-kb-bg-card);\n}\n.skai-kb-panel-head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n}\n.skai-kb-panel-head h3 {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n.skai-kb-panel-head .skai-kb-btn {\n flex: none;\n width: auto;\n}\n.skai-kb-muted {\n color: var(--skai-kb-text-sub);\n font-size: 12px;\n}\n.skai-kb-row-actions {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n justify-content: flex-end;\n}\n.skai-kb-server-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.skai-kb-server-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n background: var(--skai-kb-bg-card);\n padding: 10px 12px;\n transition: border-color 0.15s ease;\n}\n.skai-kb-server-item.is-active {\n border-color: var(--skai-kb-primary);\n background: var(--skai-kb-primary-weak);\n}\n.skai-kb-server-info {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n.skai-kb-server-name {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 500;\n font-size: 13px;\n}\n.skai-kb-server-url {\n margin-top: 2px;\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n word-break: break-all;\n}\n.skai-kb-mcp-status {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n.skai-kb-mcp-text {\n font-size: 12.5px;\n line-height: 1.6;\n}\n.skai-kb-cloud-hint {\n padding: 8px 10px;\n border: 1px dashed var(--skai-kb-border);\n border-radius: 8px;\n}\n.skai-kb-detail-list {\n display: flex;\n flex-direction: column;\n}\n.skai-kb-detail-row {\n display: flex;\n gap: 12px;\n padding: 7px 0;\n border-bottom: 1px dashed var(--skai-kb-border);\n font-size: 12.5px;\n}\n.skai-kb-detail-row:last-child {\n border-bottom: none;\n}\n.skai-kb-detail-row .skai-kb-field-label {\n width: 92px;\n flex-shrink: 0;\n padding-top: 1px;\n}\n.skai-kb-detail-text {\n flex: 1;\n min-width: 0;\n word-break: break-all;\n white-space: pre-wrap;\n}\n\n/* ---------- 用户管理(设置页区块:审核模式开关 / 筛选工具条 / 状态横幅) ---------- */\n.skai-kb-user-review-mode {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n padding: 8px 10px;\n border: 1px dashed var(--skai-kb-border);\n border-radius: 8px;\n margin-bottom: 10px;\n}\n.skai-kb-user-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n margin-bottom: 8px;\n}\n\n/* 添加成员弹窗「从用户列表选择」候选列表(弹窗内滚动,选中高亮) */\n.skai-kb-user-pick-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n max-height: 200px;\n overflow-y: auto;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 8px;\n padding: 4px;\n margin-bottom: 8px;\n}\n.skai-kb-user-pick-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n padding: 6px 8px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n}\n.skai-kb-user-pick-item:hover {\n background: var(--skai-kb-hover, rgba(0, 0, 0, 0.04));\n}\n.skai-kb-user-pick-item-active,\n.skai-kb-user-pick-item-active:hover {\n background: var(--skai-kb-primary-soft, rgba(49, 92, 255, 0.1));\n}\n.skai-kb-user-pick-nickname {\n display: block;\n font-weight: 500;\n}\n/* 多选复选标记(行首圆角方框,选中品牌色) */\n.skai-kb-pick-check {\n flex: none;\n width: 16px;\n height: 16px;\n border: 1px solid var(--skai-kb-border, #d1d5db);\n border-radius: 4px;\n font-size: 11px;\n line-height: 14px;\n text-align: center;\n color: #fff;\n}\n.skai-kb-pick-check-on {\n background: var(--skai-kb-primary, #17b0c8);\n border-color: var(--skai-kb-primary, #17b0c8);\n}\n.skai-kb-user-pick-item-disabled,\n.skai-kb-user-pick-item-disabled:hover {\n opacity: 0.55;\n cursor: not-allowed;\n background: transparent;\n}\n/* 已选用户 chips(头像 + 昵称 + 移除) */\n.skai-kb-picked-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-bottom: 8px;\n}\n.skai-kb-picked-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n padding: 3px 6px 3px 4px;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 999px;\n background: var(--skai-kb-primary-soft, rgba(23, 176, 200, 0.08));\n font-size: 12px;\n}\n.skai-kb-picked-chip-avatar {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n object-fit: cover;\n}\n.skai-kb-picked-chip-text {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-picked-chip-remove {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.08);\n color: inherit;\n font-size: 12px;\n line-height: 16px;\n cursor: pointer;\n}\n.skai-kb-picked-chip-remove:hover {\n background: rgba(0, 0, 0, 0.16);\n}\n.skai-kb-user-search {\n width: 200px;\n height: 26px;\n flex: none;\n}\n\n/* ---------- 弹窗(Portal 挂 body,遮罩 fixed 铺满整个窗口并居中,与 CRM 同款;\n 弹层根已纳入文件头部主题令牌作用域;z-index ≤35 不覆盖宿主设置菜单) ---------- */\n.skai-kb-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 30;\n background: var(--skai-kb-mask);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n animation: skai-kb-fade-in 0.15s ease;\n}\n.skai-kb-modal {\n width: 100%;\n max-width: 520px;\n max-height: 86%;\n display: flex;\n flex-direction: column;\n background: var(--skai-kb-bg-card);\n border-radius: 10px;\n box-shadow: var(--skai-kb-shadow);\n animation: skai-kb-pop-in 0.16s ease;\n}\n.skai-kb-modal.is-fullscreen {\n max-width: none;\n width: 100%;\n max-height: 96%;\n height: 96%;\n}\n.skai-kb-modal-head-actions {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n flex: none;\n}\n.skai-kb-modal-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 12px 16px;\n border-bottom: 1px solid var(--skai-kb-border);\n flex: none;\n}\n.skai-kb-modal-title {\n font-size: 14px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-modal-body {\n padding: 14px 16px;\n overflow-y: auto;\n}\n.skai-kb-modal-foot {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 10px 16px;\n border-top: 1px solid var(--skai-kb-border);\n flex: none;\n}\n.skai-kb-modal-foot .skai-kb-btn {\n width: auto;\n min-width: 72px;\n}\n.skai-kb-confirm-text {\n margin: 0;\n font-size: 13px;\n line-height: 1.7;\n}\n\n/* ---------- Toast(面板底 + 彩色描边样式,与 CRM 一致;z-index 34 宿主菜单避让约定内) ---------- */\n.skai-kb-toast-layer {\n /* createPortal 到 body + fixed:相对整个 DSH 窗口顶部居中(非插件标签页区域);\n z-index 34 宿主菜单避让约定内 */\n position: fixed;\n left: 50%;\n top: 18px;\n transform: translateX(-50%);\n z-index: 34;\n display: flex;\n flex-direction: column;\n gap: 8px;\n align-items: center;\n pointer-events: none;\n}\n.skai-kb-toast {\n pointer-events: auto;\n max-width: 90%;\n padding: 7px 14px;\n border-radius: 999px;\n font-size: 12.5px;\n background: var(--skai-kb-bg-card);\n border: 1px solid var(--skai-kb-border);\n color: var(--skai-kb-text);\n box-shadow: var(--skai-kb-shadow);\n cursor: pointer;\n word-break: break-all;\n animation: skai-kb-toast-in 0.18s ease;\n}\n.skai-kb-toast-info { border-color: var(--skai-kb-primary); color: var(--skai-kb-primary); }\n.skai-kb-toast-error { border-color: var(--skai-kb-danger); color: var(--skai-kb-danger); }\n.skai-kb-toast-success { border-color: var(--skai-kb-ok); color: var(--skai-kb-ok); }\n\n/* ---------- 动画(与 CRM 同款节奏) ---------- */\n@keyframes skai-kb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n@keyframes skai-kb-pop-in {\n from { opacity: 0; transform: translateY(8px) scale(0.98); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n}\n@keyframes skai-kb-toast-in {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* ---------- 概览统计(首页) ---------- */\n.skai-kb-stat-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n margin-bottom: 4px;\n}\n.skai-kb-stat-card {\n border: 1px solid var(--skai-kb-border);\n border-radius: 10px;\n background: var(--skai-kb-bg-card);\n padding: 10px 12px;\n}\n.skai-kb-stat-num {\n font-size: 18px;\n font-weight: 600;\n}\n/* 统计卡内气泡(首页「用户数」待审核提示) */\n.skai-kb-stat-badge {\n margin-left: 8px;\n vertical-align: 2px;\n cursor: default;\n}\n.skai-kb-stat-label {\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n margin-top: 2px;\n}\n.skai-kb-quick-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n}\n\n/* ---------- 知识库卡片 ---------- */\n.skai-kb-kb-meta {\n display: flex;\n gap: 12px;\n margin-top: 6px;\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n flex-wrap: wrap;\n}\n.skai-kb-kb-actions {\n display: flex;\n gap: 6px;\n margin-top: 8px;\n flex-wrap: wrap;\n}\n.skai-kb-kb-actions .skai-kb-btn-sm {\n flex: 1;\n}\n\n/* ---------- 文档列表 ---------- */\n.skai-kb-doc-item {\n cursor: pointer;\n}\n\n/* 文档卡片右上角「操作」下拉菜单(遮罩点击关闭;z-index ≤35 不覆盖宿主) */\n.skai-kb-doc-topbar {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n.skai-kb-doc-menu-anchor {\n position: relative;\n display: inline-flex;\n}\n.skai-kb-doc-menu-trigger {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n line-height: 1;\n border: none;\n background: transparent;\n color: var(--skai-kb-text-sub);\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 6px;\n cursor: pointer;\n}\n/* CSS 三角下箭头:随 flex 垂直居中,字体字形对齐不可控故不用 ⌄ */\n.skai-kb-caret {\n width: 0;\n height: 0;\n border-left: 4px solid transparent;\n border-right: 4px solid transparent;\n border-top: 5px solid currentColor;\n}\n.skai-kb-doc-menu-trigger:hover {\n background: var(--skai-kb-bg-hover);\n color: var(--skai-kb-text);\n}\n.skai-kb-menu-mask {\n position: fixed;\n inset: 0;\n z-index: 34;\n}\n.skai-kb-menu {\n position: absolute;\n top: 22px;\n right: 0;\n z-index: 35;\n min-width: 104px;\n padding: 4px;\n background: var(--skai-kb-bg-card);\n border: 1px solid var(--skai-kb-border-strong);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);\n}\n.skai-kb-menu-item {\n display: block;\n width: 100%;\n text-align: left;\n border: none;\n background: transparent;\n color: var(--skai-kb-text);\n font-size: 12px;\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n white-space: nowrap;\n}\n.skai-kb-menu-item:hover {\n background: var(--skai-kb-bg-hover);\n}\n.skai-kb-menu-item.is-danger {\n color: var(--skai-kb-danger);\n}\n.skai-kb-menu-item.is-danger:hover {\n background: var(--skai-kb-danger-weak);\n}\n\n.skai-kb-pre {\n white-space: pre-wrap;\n word-break: break-word;\n background: var(--skai-kb-bg-soft);\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n padding: 8px 10px;\n max-height: 260px;\n overflow-y: auto;\n font-size: 12px;\n margin: 0;\n}\n\n/* ---------- 文档详情正文 ---------- */\n.skai-kb-doc-detail {\n gap: 8px;\n}\n.skai-kb-md-wrap {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n border: 1px solid var(--skai-kb-border);\n border-radius: 10px;\n background: var(--skai-kb-bg-card);\n padding: 14px 16px;\n}\n\n/* ---------- Markdown 渲染(文档详情正文;.skai-kb-md 作用域,不外溢) ---------- */\n.skai-kb-md {\n font-size: 13px;\n line-height: 1.7;\n word-break: break-word;\n}\n.skai-kb-md .skai-kb-md-h {\n font-weight: 600;\n line-height: 1.4;\n margin: 14px 0 6px;\n}\n.skai-kb-md .skai-kb-md-h:first-child {\n margin-top: 0;\n}\n.skai-kb-md .skai-kb-md-h1 { font-size: 18px; }\n.skai-kb-md .skai-kb-md-h2 { font-size: 16px; }\n.skai-kb-md .skai-kb-md-h3 { font-size: 15px; }\n.skai-kb-md .skai-kb-md-h4,\n.skai-kb-md .skai-kb-md-h5,\n.skai-kb-md .skai-kb-md-h6 { font-size: 14px; }\n.skai-kb-md .skai-kb-md-p {\n margin: 6px 0;\n}\n.skai-kb-md .skai-kb-md-code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n background: var(--skai-kb-bg-soft);\n border: 1px solid var(--skai-kb-border);\n border-radius: 4px;\n padding: 0 4px;\n}\n.skai-kb-md .skai-kb-md-pre {\n background: var(--skai-kb-bg-soft);\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n padding: 10px 12px;\n overflow-x: auto;\n font-size: 12px;\n line-height: 1.6;\n margin: 8px 0;\n white-space: pre;\n}\n.skai-kb-md .skai-kb-md-pre code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n background: transparent;\n border: none;\n padding: 0;\n}\n.skai-kb-md .skai-kb-md-quote {\n margin: 8px 0;\n padding: 4px 12px;\n border-left: 3px solid var(--skai-kb-border-strong);\n color: var(--skai-kb-text-sub);\n background: var(--skai-kb-bg-soft);\n border-radius: 0 8px 8px 0;\n}\n.skai-kb-md .skai-kb-md-ul,\n.skai-kb-md .skai-kb-md-ol {\n margin: 6px 0;\n padding-left: 22px;\n}\n.skai-kb-md .skai-kb-md-li {\n margin: 2px 0;\n}\n.skai-kb-md .skai-kb-md-link {\n color: var(--skai-kb-link);\n text-decoration: none;\n}\n.skai-kb-md .skai-kb-md-link:hover {\n text-decoration: underline;\n}\n.skai-kb-md .skai-kb-md-img {\n max-width: 100%;\n border-radius: 8px;\n border: 1px solid var(--skai-kb-border);\n}\n.skai-kb-md .skai-kb-md-hr {\n border: none;\n border-top: 1px solid var(--skai-kb-border);\n margin: 12px 0;\n}\n.skai-kb-md .skai-kb-md-table-wrap {\n overflow-x: auto;\n margin: 8px 0;\n}\n.skai-kb-md .skai-kb-md-table {\n border-collapse: collapse;\n font-size: 12px;\n min-width: 60%;\n}\n.skai-kb-md .skai-kb-md-table th,\n.skai-kb-md .skai-kb-md-table td {\n border: 1px solid var(--skai-kb-border);\n padding: 4px 10px;\n text-align: left;\n}\n.skai-kb-md .skai-kb-md-table th {\n background: var(--skai-kb-bg-soft);\n font-weight: 600;\n}\n\n/* ---------- 时间线(文档版本) ---------- */\n.skai-kb-timeline {\n list-style: none;\n margin: 0;\n padding: 0 0 0 14px;\n border-left: 2px solid var(--skai-kb-border);\n}\n.skai-kb-timeline li {\n position: relative;\n padding: 0 0 12px 10px;\n}\n.skai-kb-timeline li::before {\n content: '';\n position: absolute;\n left: -20px;\n top: 4px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--skai-kb-primary);\n}\n\n/* ---------- 文档上传 ---------- */\n.skai-kb-dropzone {\n border: 1px dashed var(--skai-kb-border-strong);\n border-radius: 8px;\n padding: 12px;\n}\n\n/* ---------- 成员权限 ---------- */\n.skai-kb-member-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 0;\n border-bottom: 1px solid var(--skai-kb-border);\n}\n.skai-kb-member-row:last-child {\n border-bottom: none;\n}\n.skai-kb-member-avatar {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n flex: none;\n object-fit: cover;\n background: var(--skai-kb-bg-soft);\n}\n.skai-kb-member-avatar-fallback {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 13px;\n font-weight: 500;\n color: var(--skai-kb-primary);\n background: var(--skai-kb-primary-weak);\n}\n.skai-kb-member-main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n.skai-kb-member-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-member-mobile {\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ============================================================\n 宽容器布局(DSH 标签页 / standalone 宽屏):按容器宽度自适应,\n 与视口无关(面板开合、拖宽均正确响应)\n ============================================================ */\n@container skai-kb (min-width: 620px) {\n /* 概览:统计四列 */\n .skai-kb-stat-grid {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n }\n .skai-kb-quick-grid {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n }\n}\n\n@container skai-kb (min-width: 980px) {\n /* 超宽容器:正文限宽保持可读性(卡片分列由基础网格自适应) */\n .skai-kb-page-scroll,\n .skai-kb-md-wrap {\n max-width: 1080px;\n }\n}\n\n/* =======================================================================\n 目录(文件夹)管理:目录树 + 文档列表布局 + 拖拽指示\n ======================================================================= */\n\n/* 详情页两栏布局:左目录树 + 右文档区(窄容器降级为上下布局) */\n.skai-kb-doc-layout {\n display: flex;\n gap: 10px;\n align-items: stretch;\n min-height: 0;\n}\n\n.skai-kb-doc-main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* 目录树面板 */\n.skai-kb-folder-tree {\n flex: none;\n width: 168px;\n display: flex;\n flex-direction: column;\n background: var(--skai-kb-bg-card, #fff);\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 10px;\n padding: 6px;\n align-self: flex-start;\n position: sticky;\n top: 0;\n}\n\n.skai-kb-folder-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 2px 6px 6px;\n border-bottom: 1px solid var(--skai-kb-bg-soft, #f0f1f3);\n margin-bottom: 4px;\n}\n\n.skai-kb-folder-head-title {\n font-size: 12px;\n font-weight: 600;\n color: var(--skai-kb-text-sub, #6b7280);\n}\n\n.skai-kb-folder-body {\n display: flex;\n flex-direction: column;\n gap: 1px;\n overflow-y: auto;\n max-height: 320px;\n}\n\n.skai-kb-folder-item {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px 6px;\n border-radius: 7px;\n cursor: pointer;\n font-size: 12.5px;\n color: var(--skai-kb-text, #374151);\n border: 1px dashed transparent;\n user-select: none;\n min-width: 0;\n}\n\n.skai-kb-folder-item:hover {\n background: var(--skai-kb-bg-hover, #f5f6f8);\n}\n\n.skai-kb-folder-item.is-active {\n background: var(--skai-kb-primary-weak, #eef4ff);\n color: var(--skai-kb-primary, #2f6bff);\n font-weight: 500;\n}\n\n.skai-kb-folder-arrow {\n flex: none;\n width: 12px;\n font-size: 10px;\n color: var(--skai-kb-text-caption, #9ca3af);\n text-align: center;\n}\n\n.skai-kb-folder-arrow.is-leaf {\n visibility: hidden;\n}\n\n.skai-kb-folder-icon {\n flex: none;\n font-size: 12px;\n}\n\n.skai-kb-folder-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.skai-kb-folder-actions {\n flex: none;\n display: none;\n gap: 2px;\n}\n\n.skai-kb-folder-item:hover .skai-kb-folder-actions {\n display: inline-flex;\n}\n\n.skai-kb-folder-act {\n border: 0;\n background: transparent;\n padding: 0 3px;\n font-size: 11px;\n line-height: 16px;\n color: var(--skai-kb-text-caption, #9ca3af);\n border-radius: 4px;\n cursor: pointer;\n}\n\n.skai-kb-folder-act:hover {\n background: var(--skai-kb-bg-soft, #f0f1f3);\n color: var(--skai-kb-primary, #2f6bff);\n}\n\n.skai-kb-folder-act.is-danger:hover {\n color: var(--skai-kb-danger, #e5484d);\n}\n\n/* 目录树节点:文档拖入高亮(in)与同级排序插入线(before/after) */\n.skai-kb-folder-item.skai-kb-drop-in {\n border: 1px solid var(--skai-kb-primary, #2f6bff);\n background: var(--skai-kb-primary-weak, #eef4ff);\n}\n\n.skai-kb-folder-item.skai-kb-drop-before {\n box-shadow: inset 0 2px 0 0 var(--skai-kb-primary, #2f6bff);\n}\n\n.skai-kb-folder-item.skai-kb-drop-after {\n box-shadow: inset 0 -2px 0 0 var(--skai-kb-primary, #2f6bff);\n}\n\n/* 文档列表项:作为同目录排序 drop 目标时高亮 */\n.skai-kb-doc-item.skai-kb-drop-in {\n outline: 1.5px dashed var(--skai-kb-primary, #2f6bff);\n outline-offset: -2px;\n}\n\n/* 移动文档弹窗:目录选择列表 */\n.skai-kb-move-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 240px;\n overflow-y: auto;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 8px;\n padding: 4px;\n}\n\n.skai-kb-move-item {\n border: 0;\n background: transparent;\n text-align: left;\n padding: 6px 8px;\n font-size: 12.5px;\n border-radius: 6px;\n cursor: pointer;\n color: var(--skai-kb-text, #374151);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.skai-kb-move-item:hover {\n background: var(--skai-kb-bg-hover, #f5f6f8);\n}\n\n.skai-kb-move-item.is-active {\n background: var(--skai-kb-primary-weak, #eef4ff);\n color: var(--skai-kb-primary, #2f6bff);\n font-weight: 500;\n}\n\n/* =======================================================================\n 文档在线编辑:Markdown 可视化编辑器(工具栏 + 双栏分屏)\n ======================================================================= */\n\n.skai-kb-editor {\n display: flex;\n flex-direction: column;\n gap: 8px;\n height: 100%;\n min-height: 320px;\n}\n\n.skai-kb-editor-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.skai-kb-editor-tools {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n}\n\n.skai-kb-editor-tool {\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n background: var(--skai-kb-bg-card, #fff);\n border-radius: 6px;\n padding: 3px 7px;\n font-size: 11.5px;\n line-height: 16px;\n color: var(--skai-kb-text, #374151);\n cursor: pointer;\n}\n\n.skai-kb-editor-tool:hover:not(:disabled) {\n border-color: var(--skai-kb-primary, #2f6bff);\n color: var(--skai-kb-primary, #2f6bff);\n}\n\n.skai-kb-editor-tool:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.skai-kb-editor-modes {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.skai-kb-editor-mode {\n border: 1px solid transparent;\n background: transparent;\n padding: 3px 8px;\n font-size: 12px;\n border-radius: 6px;\n cursor: pointer;\n color: var(--skai-kb-text-sub, #6b7280);\n}\n\n.skai-kb-editor-mode.is-active {\n background: var(--skai-kb-primary-weak, #eef4ff);\n color: var(--skai-kb-primary, #2f6bff);\n font-weight: 500;\n}\n\n.skai-kb-editor-dirty {\n font-size: 11px;\n color: var(--skai-kb-warn, #d97706);\n}\n\n/* 双栏分屏:编辑区 | 预览区(仅预览/仅编辑时单栏占满) */\n.skai-kb-editor-panes {\n display: flex;\n gap: 8px;\n flex: 1;\n min-height: 0;\n}\n\n.skai-kb-editor-input {\n flex: 1;\n min-width: 0;\n min-height: 300px;\n resize: vertical;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 10px;\n padding: 10px 12px;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12.5px;\n line-height: 1.6;\n color: var(--skai-kb-text, #374151);\n background: var(--skai-kb-bg-card, #fff);\n}\n\n.skai-kb-editor-input:focus {\n outline: none;\n border-color: var(--skai-kb-primary, #2f6bff);\n}\n\n/* 仅编辑模式:编辑框撑满父容器高度(取消最小高度下限与手动拉伸) */\n.skai-kb-editor.edit .skai-kb-editor-input {\n min-height: 0;\n resize: none;\n}\n\n.skai-kb-editor-preview {\n flex: 1;\n min-width: 0;\n min-height: 300px;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 10px;\n padding: 10px 12px;\n overflow-y: auto;\n background: var(--skai-kb-bg-soft, #fafafa);\n}\n\n/* 窄容器:编辑器分屏改为上下堆叠 */\n@container skai-kb (max-width: 560px) {\n .skai-kb-doc-layout {\n flex-direction: column;\n }\n\n .skai-kb-folder-tree {\n width: auto;\n position: static;\n }\n\n .skai-kb-folder-body {\n flex-direction: row;\n flex-wrap: wrap;\n max-height: none;\n }\n\n .skai-kb-editor-panes.split {\n flex-direction: column;\n }\n\n .skai-kb-editor-panes.split .skai-kb-editor-input,\n .skai-kb-editor-panes.split .skai-kb-editor-preview {\n min-height: 180px;\n }\n}\n\n/* ---------- 对话消息操作区「存入知识库」(conversation.chat.assistant-actions) ----------\n 按钮渲染于宿主 AI 消息操作行(官方复制/点赞同款 28px 图标钮),颜色直接取\n DSH 官方令牌(--dsw-alias-*,全局可达,随宿主浅/深色主题切换);\n --skai-kb-* 别名仅作 standalone 兜底 */\n.skai-kb-chat-save-btn {\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 6px;\n color: var(--dsw-alias-label-tertiary, var(--skai-kb-text-caption, #9ca1ab));\n background: none;\n border: none;\n border-radius: 28px;\n cursor: pointer;\n transition: color 0.15s, background 0.15s;\n}\n\n.skai-kb-chat-save-btn:hover {\n color: var(--dsw-alias-label-secondary, var(--skai-kb-text-sub, #8a8f99));\n background: var(--dsw-alias-interactive-bg-hover, var(--skai-kb-bg-hover, rgba(0, 0, 0, 0.04)));\n}\n\n.skai-kb-chat-save-preview {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.skai-kb-chat-save-preview-label {\n font-size: 12px;\n color: var(--skai-kb-text-caption);\n}\n\n/* 保存方式切换(新建文档 / 覆盖已有文档):与弹窗按钮风格一致 */\n.skai-kb-chat-save-modes {\n display: flex;\n gap: 8px;\n}\n.skai-kb-chat-save-hint {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--skai-kb-text-caption);\n}\n\n.skai-kb-chat-save-preview pre {\n max-height: 140px;\n overflow: auto;\n margin: 0;\n padding: 8px 10px;\n font-size: 12px;\n line-height: 1.6;\n white-space: pre-wrap;\n word-break: break-word;\n background: var(--skai-kb-bg-soft);\n border-radius: 6px;\n}\n\n";
|
|
8526
|
+
var knowledge_default = "/* ============================================================\n 时刻AI知识库插件样式(前缀 skai-kb-,不覆盖宿主样式)\n - 0.8.0 起渲染于 DSH 右侧 Sidebar 标签页正文(首页 / 知识库详情 /\n 文档详情 / 设置 各为一个标签页)与 standalone 调试台:\n .skai-kb-page 声明 inline-size 容器查询(与 .skai-kb-app 同名 skai-kb),\n 布局按容器宽度自适应,不依赖视口宽度(面板开合、拖宽均正确响应)\n - 颜色全部取 DSH 官方主题令牌(--dsw-alias-*),随宿主\n 浅色/深色主题自动切换;var() 兜底值仅供 standalone 预览\n (无宿主令牌)使用\n - z-index 使用适中值(≤35),禁止覆盖宿主设置菜单\n ============================================================ */\n\n/* ---------- 主题令牌别名层(统一映射到 DSH 官方令牌) ----------\n DSH 强调色语义(与常见设计系统相反,务必注意):\n - --dsw-alias-brand-primary:亮色主题为深色(bluish-1000)、暗色主题为浅色\n (bluish-50),即「反色强调」——主按钮填充随主题黑白互换\n - --dsw-alias-label-primary-foreground:强调填充上的前景文字,恒与填充反相\n (亮色=白、暗色=黑),是主按钮等强调填充上文字的正确取值。\n (DSH 无 --dsw-alias-brand-text;此前误用导致暗色模式白底白字)\n - --dsw-alias-link:正文链接色(亮 deepseek-500 / 暗 deepseek-400,恒为蓝) */\n.skai-kb-root,\n.skai-kb-modal-mask,\n.skai-kb-toast-layer {\n --skai-kb-bg: var(--dsw-alias-bg-base, #ffffff);\n --skai-kb-bg-card: var(--dsw-alias-bg-layer-1, #ffffff);\n --skai-kb-bg-soft: var(--dsw-alias-bg-layer-2, #f6f7f9);\n --skai-kb-bg-hover: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));\n --skai-kb-mask: var(--dsw-alias-bg-mask-1, rgba(17, 19, 23, 0.5));\n --skai-kb-border: var(--dsw-alias-border-l1, #e4e6eb);\n --skai-kb-border-strong: var(--dsw-alias-border-l2, #d0d3d9);\n --skai-kb-text: var(--dsw-alias-label-primary, #1f2329);\n --skai-kb-text-sub: var(--dsw-alias-label-secondary, #8a8f99);\n --skai-kb-text-caption: var(--dsw-alias-label-caption, #9ca1ab);\n /* 知识库品牌青色:与 DSH 侧边栏标签页/引导页图标同色(#06b6d4) */\n --skai-kb-primary: #06b6d4;\n /* 强调填充上的文字(官方前景令牌:亮=白 / 暗=黑;无 token 环境兜底白色) */\n --skai-kb-on-primary: var(--dsw-alias-label-primary-foreground, #ffffff);\n /* 主按钮:品牌青色实心 + 白字(青色为高亮度色相,不适用深底反色公式),hover 提亮一档(与 CRM 一致) */\n --skai-kb-btn-primary: var(--skai-kb-primary);\n --skai-kb-primary-weak: color-mix(in srgb, var(--skai-kb-primary) 12%, transparent);\n --skai-kb-danger: var(--dsw-alias-state-error-primary, #d83931);\n --skai-kb-danger-weak: color-mix(in srgb, var(--skai-kb-danger) 12%, transparent);\n --skai-kb-ok: var(--dsw-alias-state-success-primary, #2ea44f);\n --skai-kb-ok-weak: color-mix(in srgb, var(--skai-kb-ok) 14%, transparent);\n --skai-kb-warn: var(--dsw-alias-state-warn-primary, #d97706);\n --skai-kb-warn-weak: color-mix(in srgb, var(--skai-kb-warn) 14%, transparent);\n --skai-kb-link: var(--skai-kb-primary);\n --skai-kb-shadow: 0 8px 24px rgba(31, 35, 41, 0.12);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--skai-kb-text);\n background: var(--skai-kb-bg);\n scrollbar-width: thin;\n}\n\n/* 盒模型统一(与 CRM 一致):root 作用域内全部 border-box */\n.skai-kb-root *,\n.skai-kb-root *::before,\n.skai-kb-root *::after,\n.skai-kb-modal-mask *,\n.skai-kb-modal-mask *::before,\n.skai-kb-modal-mask *::after {\n box-sizing: border-box;\n}\n\n/* 原生 button 不继承字体,与 CRM 一致显式继承宿主字体 */\n.skai-kb-root button,\n.skai-kb-modal-mask button {\n font-family: inherit;\n}\n\n/* 深色模式(跟随系统 prefers-color-scheme,与 CRM 插件策略一致):\n 宿主令牌存在时仍优先取宿主值(var 包裹),此处仅提供 standalone 兜底 */\n@media (prefers-color-scheme: dark) {\n .skai-kb-root,\n .skai-kb-modal-mask,\n .skai-kb-toast-layer {\n --skai-kb-bg: var(--dsw-alias-bg-base, #1e1f22);\n --skai-kb-bg-card: var(--dsw-alias-bg-layer-1, #1e1f22);\n --skai-kb-bg-soft: var(--dsw-alias-bg-layer-2, #2a2b2e);\n --skai-kb-bg-hover: var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08));\n --skai-kb-mask: rgba(0, 0, 0, 0.55);\n --skai-kb-border: var(--dsw-alias-border-l1, #2c2d2f);\n --skai-kb-border-strong: var(--dsw-alias-border-l2, #3a3d42);\n --skai-kb-text: var(--dsw-alias-label-primary, #e8eaed);\n --skai-kb-text-sub: var(--dsw-alias-label-secondary, #8b8f96);\n --skai-kb-text-caption: var(--dsw-alias-label-caption, #6f747d);\n --skai-kb-primary: #22d3ee;\n --skai-kb-on-primary: var(--dsw-alias-label-primary-foreground, #10131a);\n --skai-kb-btn-primary: #06b6d4;\n --skai-kb-danger: var(--dsw-alias-state-error-primary, #f5564e);\n --skai-kb-ok: var(--dsw-alias-state-success-primary, #46c26a);\n --skai-kb-warn: var(--dsw-alias-state-warn-primary, #e8a23c);\n --skai-kb-link: var(--skai-kb-primary);\n --skai-kb-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);\n }\n}\n\n/* ---------- 布局骨架(容器查询基准) ---------- */\n.skai-kb-app {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n position: relative;\n container-type: inline-size;\n container-name: skai-kb;\n}\n\n/* standalone 调试台挂载容器(模拟抽屉内 100% 高度) */\n.skai-kb-standalone-inner {\n height: 100%;\n min-height: 0;\n}\n\n/* ---------- DSH 标签页正文骨架 ----------\n sidebar.right.pane.tab 正文:不滚动定位容器(弹窗/Toast 绝对定位收敛于此),\n 内容滚动由 .skai-kb-page-scroll 承担(文档详情为 .skai-kb-md-wrap) */\n.skai-kb-tab-root {\n height: 100%;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.skai-kb-page {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 12px;\n height: 100%;\n min-height: 0;\n padding: 14px 16px;\n overflow: hidden;\n container-type: inline-size;\n container-name: skai-kb;\n}\n.skai-kb-tab-root > .skai-kb-page {\n flex: 1;\n}\n.skai-kb-page-scroll {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.skai-kb-header {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 14px 16px 0;\n flex: none;\n}\n.skai-kb-header-title {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 15px;\n font-weight: 600;\n color: var(--skai-kb-text);\n}\n\n/* 分区导航:窄容器横向滑动(不换行挤压),宽容器自然展开 */\n.skai-kb-nav {\n display: flex;\n gap: 4px;\n overflow-x: auto;\n scrollbar-width: none;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--skai-kb-border);\n align-items: center;\n}\n.skai-kb-nav::-webkit-scrollbar {\n display: none;\n}\n.skai-kb-nav-btn {\n flex: none;\n border: none;\n background: transparent;\n color: var(--skai-kb-text-sub);\n padding: 4px 10px;\n border-radius: 999px;\n cursor: pointer;\n font-size: 12px;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.skai-kb-nav-btn:hover {\n background: var(--skai-kb-bg-hover);\n color: var(--skai-kb-text);\n}\n.skai-kb-nav-btn.is-active {\n background: var(--skai-kb-primary-weak);\n color: var(--skai-kb-primary);\n font-weight: 500;\n}\n.skai-kb-nav-btn:focus-visible {\n outline: 2px solid var(--skai-kb-primary);\n outline-offset: 1px;\n}\n/* standalone 页签关闭钮 */\n.skai-kb-tab-close {\n flex: none;\n border: none;\n background: transparent;\n color: var(--skai-kb-text-caption);\n cursor: pointer;\n font-size: 10px;\n padding: 2px 4px;\n border-radius: 6px;\n}\n.skai-kb-tab-close:hover {\n background: var(--skai-kb-bg-hover);\n color: var(--skai-kb-danger);\n}\n\n.skai-kb-body {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 14px 16px;\n}\n\n/* ---------- 详情页头(知识库详情 / 文档详情标签页) ---------- */\n.skai-kb-detail-head {\n flex: none;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-bottom: 8px;\n}\n.skai-kb-detail-title {\n font-weight: 600;\n font-size: 15px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-back {\n flex: none;\n width: auto;\n min-width: 0;\n}\n\n/* ---------- 首页页头(左侧说明介绍 + 右上角设置入口) ---------- */\n.skai-kb-home-head {\n flex: none;\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 10px;\n padding-bottom: 10px;\n}\n.skai-kb-home-head-main {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 3px;\n}\n.skai-kb-home-desc {\n font-size: 12px;\n line-height: 1.6;\n color: var(--skai-kb-text-sub);\n /* 介绍信息完整展示:不截断,自动换行铺满全量文字 */\n overflow-wrap: anywhere;\n}\n.skai-kb-home-head > .skai-kb-btn {\n flex: none;\n width: auto;\n margin-top: 1px;\n}\n\n/* ---------- 通用元素 ---------- */\n.skai-kb-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 5px;\n width: auto;\n height: 30px;\n padding: 0 12px;\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n background: var(--skai-kb-bg-card);\n color: var(--skai-kb-text);\n cursor: pointer;\n font-size: 12.5px;\n white-space: nowrap;\n transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n}\n.skai-kb-btn:hover:not(:disabled) {\n background: var(--skai-kb-bg-soft);\n}\n.skai-kb-btn:focus-visible {\n outline: 2px solid var(--skai-kb-primary);\n outline-offset: 1px;\n}\n.skai-kb-btn:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n}\n/* 等待转圈动画(SpinnerGlyph 专用) */\n@keyframes skai-kb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.skai-kb-spin {\n animation: skai-kb-spin 0.8s linear infinite;\n flex: none;\n}\n.skai-kb-btn-primary {\n background: var(--skai-kb-btn-primary);\n border-color: transparent;\n color: #fff;\n}\n.skai-kb-btn-primary:hover:not(:disabled) {\n background: var(--skai-kb-btn-primary);\n filter: brightness(1.08);\n}\n.skai-kb-btn-danger {\n background: var(--skai-kb-danger);\n border-color: var(--skai-kb-danger);\n color: #fff;\n}\n.skai-kb-btn-danger:hover:not(:disabled) {\n background: var(--skai-kb-danger);\n filter: brightness(1.05);\n}\n.skai-kb-btn-ghost-danger {\n border-color: transparent;\n background: transparent;\n color: var(--skai-kb-danger);\n}\n.skai-kb-btn-ghost-danger:hover:not(:disabled) {\n background: var(--skai-kb-danger-weak);\n}\n.skai-kb-btn-sm {\n width: auto;\n height: 26px;\n padding: 0 9px;\n font-size: 12px;\n border-radius: 8px;\n}\n\n.skai-kb-icon-btn {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n background: transparent;\n color: var(--skai-kb-text-sub);\n cursor: pointer;\n border-radius: 8px;\n font-size: 12px;\n}\n.skai-kb-icon-btn:hover {\n background: var(--skai-kb-bg-hover);\n color: var(--skai-kb-text);\n}\n/* 悬停文字提示(CSS 实现,即时显示;按钮右上角对齐避免溢出弹窗) */\n.skai-kb-icon-btn[data-tip]:hover::after {\n content: attr(data-tip);\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 30;\n padding: 4px 8px;\n border-radius: 6px;\n background: var(--skai-kb-bg);\n color: var(--skai-kb-text);\n font-size: 12px;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n}\n\n.skai-kb-input,\n.skai-kb-select,\n.skai-kb-textarea {\n width: 100%;\n padding: 5px 10px;\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n background: var(--skai-kb-bg);\n color: var(--skai-kb-text);\n /* 原生表单控件不继承字体,与 CRM 一致显式继承 */\n font: inherit;\n font-size: 12.5px;\n box-sizing: border-box;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n/* 下拉选择与 CRM 一致使用手型光标 */\n.skai-kb-select {\n cursor: pointer;\n}\n/* 单行输入控件与按钮(30px)同高 */\n.skai-kb-input,\n.skai-kb-select {\n height: 30px;\n}\n.skai-kb-textarea {\n min-height: 56px;\n resize: vertical;\n line-height: 1.6;\n}\n.skai-kb-input:focus,\n.skai-kb-select:focus,\n.skai-kb-textarea:focus {\n outline: none;\n border-color: var(--skai-kb-primary);\n box-shadow: 0 0 0 2px var(--skai-kb-primary-weak);\n}\n.skai-kb-input::placeholder,\n.skai-kb-textarea::placeholder {\n color: var(--skai-kb-text-caption);\n}\n.skai-kb-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin-bottom: 10px;\n}\n.skai-kb-field-label {\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n}\n.skai-kb-field-error {\n font-size: 12px;\n color: var(--skai-kb-danger);\n}\n.skai-kb-field-row {\n display: flex;\n gap: 8px;\n}\n.skai-kb-field-row > * {\n flex: 1;\n}\n\n.skai-kb-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 10px;\n flex-wrap: wrap;\n flex: none;\n}\n.skai-kb-toolbar .skai-kb-search {\n flex: 1;\n min-width: 120px;\n}\n\n.skai-kb-card {\n border: 1px solid var(--skai-kb-border);\n border-radius: 10px;\n background: var(--skai-kb-bg-card);\n padding: 10px 12px;\n margin-bottom: 8px;\n transition: border-color 0.15s ease, background 0.15s ease;\n}\n.skai-kb-card:hover {\n border-color: var(--skai-kb-border-strong);\n}\n\n/* 卡片列表:每行一个知识库卡片(纵向堆叠,占满整行宽度) */\n.skai-kb-card-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.skai-kb-card-list > .skai-kb-card {\n margin-bottom: 0;\n}\n\n.skai-kb-row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.skai-kb-row-between {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.skai-kb-title {\n font-weight: 600;\n font-size: 13px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-sub {\n padding: 7px 0 0 0;\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n /* 说明/介绍类文本完整展示:不截断省略,自动换行铺满全量文字 */\n overflow-wrap: anywhere;\n}\n\n.skai-kb-badge {\n display: inline-flex;\n align-items: center;\n padding: 0 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 18px;\n white-space: nowrap;\n}\n.skai-kb-badge-gray { background: var(--skai-kb-bg-soft); color: var(--skai-kb-text-sub); }\n.skai-kb-badge-blue { background: var(--skai-kb-primary-weak); color: var(--skai-kb-primary); }\n.skai-kb-badge-green { background: var(--skai-kb-ok-weak); color: var(--skai-kb-ok); }\n.skai-kb-badge-amber { background: var(--skai-kb-warn-weak); color: var(--skai-kb-warn); }\n.skai-kb-badge-red { background: var(--skai-kb-danger-weak); color: var(--skai-kb-danger); }\n\n.skai-kb-tag {\n display: inline-block;\n padding: 0 6px;\n border-radius: 4px;\n font-size: 11px;\n line-height: 18px;\n background: var(--skai-kb-bg-soft);\n color: var(--skai-kb-text-sub);\n margin-right: 4px;\n}\n\n.skai-kb-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n text-align: center;\n padding: 28px 16px;\n color: var(--skai-kb-text-sub);\n font-size: 13px;\n}\n.skai-kb-empty-sub {\n font-size: 12px;\n}\n\n.skai-kb-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 8px 0;\n color: var(--skai-kb-text-sub);\n font-size: 12px;\n flex: none;\n}\n.skai-kb-pagination-btns {\n display: flex;\n gap: 8px;\n}\n\n/* ---------- 设置页(对齐 CRM:页头 + panel 区块 + 行操作 + 详情列表) ---------- */\n.skai-kb-page-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n flex-wrap: wrap;\n flex: none;\n}\n.skai-kb-page-title {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n.skai-kb-page-title svg {\n color: var(--skai-kb-primary);\n}\n.skai-kb-page-actions {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.skai-kb-panel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 14px;\n border: 1px solid var(--skai-kb-border);\n border-radius: 10px;\n background: var(--skai-kb-bg-card);\n}\n.skai-kb-panel-head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n}\n.skai-kb-panel-head h3 {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n}\n.skai-kb-panel-head .skai-kb-btn {\n flex: none;\n width: auto;\n}\n.skai-kb-muted {\n color: var(--skai-kb-text-sub);\n font-size: 12px;\n}\n.skai-kb-row-actions {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n justify-content: flex-end;\n}\n.skai-kb-server-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n.skai-kb-server-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n background: var(--skai-kb-bg-card);\n padding: 10px 12px;\n transition: border-color 0.15s ease;\n}\n.skai-kb-server-item.is-active {\n border-color: var(--skai-kb-primary);\n background: var(--skai-kb-primary-weak);\n}\n.skai-kb-server-info {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n/* 服务器条目下方健康提示(应用id / 版本匹配状态,探测 /kb/health) */\n.skai-kb-server-health {\n font-size: 12px;\n line-height: 1.5;\n}\n\n.skai-kb-server-health.is-ok {\n color: var(--skai-kb-ok);\n}\n\n.skai-kb-server-health.is-danger {\n color: var(--skai-kb-danger);\n}\n\n.skai-kb-server-health.is-muted {\n color: var(--skai-kb-text-sub);\n}\n\n.skai-kb-server-name {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 500;\n font-size: 13px;\n}\n.skai-kb-server-url {\n margin-top: 2px;\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n word-break: break-all;\n}\n.skai-kb-mcp-status {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n}\n.skai-kb-mcp-text {\n font-size: 12.5px;\n line-height: 1.6;\n}\n.skai-kb-cloud-hint {\n padding: 8px 10px;\n border: 1px dashed var(--skai-kb-border);\n border-radius: 8px;\n}\n.skai-kb-detail-list {\n display: flex;\n flex-direction: column;\n}\n.skai-kb-detail-row {\n display: flex;\n gap: 12px;\n padding: 7px 0;\n border-bottom: 1px dashed var(--skai-kb-border);\n font-size: 12.5px;\n}\n.skai-kb-detail-row:last-child {\n border-bottom: none;\n}\n.skai-kb-detail-row .skai-kb-field-label {\n width: 92px;\n flex-shrink: 0;\n padding-top: 1px;\n}\n.skai-kb-detail-text {\n flex: 1;\n min-width: 0;\n word-break: break-all;\n white-space: pre-wrap;\n}\n\n/* ---------- 用户管理(设置页区块:审核模式开关 / 筛选工具条 / 状态横幅) ---------- */\n.skai-kb-user-review-mode {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n padding: 8px 10px;\n border: 1px dashed var(--skai-kb-border);\n border-radius: 8px;\n margin-bottom: 10px;\n}\n.skai-kb-user-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n margin-bottom: 8px;\n}\n\n/* 添加成员弹窗「从用户列表选择」候选列表(弹窗内滚动,选中高亮) */\n.skai-kb-user-pick-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n max-height: 200px;\n overflow-y: auto;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 8px;\n padding: 4px;\n margin-bottom: 8px;\n}\n.skai-kb-user-pick-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n padding: 6px 8px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: inherit;\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n}\n.skai-kb-user-pick-item:hover {\n background: var(--skai-kb-hover, rgba(0, 0, 0, 0.04));\n}\n.skai-kb-user-pick-item-active,\n.skai-kb-user-pick-item-active:hover {\n background: var(--skai-kb-primary-soft, rgba(49, 92, 255, 0.1));\n}\n.skai-kb-user-pick-nickname {\n display: block;\n font-weight: 500;\n}\n/* 多选复选标记(行首圆角方框,选中品牌色) */\n.skai-kb-pick-check {\n flex: none;\n width: 16px;\n height: 16px;\n border: 1px solid var(--skai-kb-border, #d1d5db);\n border-radius: 4px;\n font-size: 11px;\n line-height: 14px;\n text-align: center;\n color: #fff;\n}\n.skai-kb-pick-check-on {\n background: var(--skai-kb-primary, #17b0c8);\n border-color: var(--skai-kb-primary, #17b0c8);\n}\n.skai-kb-user-pick-item-disabled,\n.skai-kb-user-pick-item-disabled:hover {\n opacity: 0.55;\n cursor: not-allowed;\n background: transparent;\n}\n/* 已选用户 chips(头像 + 昵称 + 移除) */\n.skai-kb-picked-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-bottom: 8px;\n}\n.skai-kb-picked-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n padding: 3px 6px 3px 4px;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 999px;\n background: var(--skai-kb-primary-soft, rgba(23, 176, 200, 0.08));\n font-size: 12px;\n}\n.skai-kb-picked-chip-avatar {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n object-fit: cover;\n}\n.skai-kb-picked-chip-text {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-picked-chip-remove {\n flex: none;\n width: 16px;\n height: 16px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.08);\n color: inherit;\n font-size: 12px;\n line-height: 16px;\n cursor: pointer;\n}\n.skai-kb-picked-chip-remove:hover {\n background: rgba(0, 0, 0, 0.16);\n}\n.skai-kb-user-search {\n width: 200px;\n height: 26px;\n flex: none;\n}\n\n/* ---------- 弹窗(Portal 挂 body,遮罩 fixed 铺满整个窗口并居中,与 CRM 同款;\n 弹层根已纳入文件头部主题令牌作用域;z-index ≤35 不覆盖宿主设置菜单) ---------- */\n.skai-kb-modal-mask {\n position: fixed;\n inset: 0;\n z-index: 30;\n background: var(--skai-kb-mask);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n animation: skai-kb-fade-in 0.15s ease;\n}\n.skai-kb-modal {\n width: 100%;\n max-width: 520px;\n max-height: 86%;\n display: flex;\n flex-direction: column;\n background: var(--skai-kb-bg-card);\n border-radius: 10px;\n box-shadow: var(--skai-kb-shadow);\n animation: skai-kb-pop-in 0.16s ease;\n}\n.skai-kb-modal.is-fullscreen {\n max-width: none;\n width: 100%;\n max-height: 96%;\n height: 96%;\n}\n.skai-kb-modal-head-actions {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n flex: none;\n}\n.skai-kb-modal-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 12px 16px;\n border-bottom: 1px solid var(--skai-kb-border);\n flex: none;\n}\n.skai-kb-modal-title {\n font-size: 14px;\n font-weight: 600;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-modal-body {\n padding: 14px 16px;\n overflow-y: auto;\n}\n.skai-kb-modal-foot {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 10px 16px;\n border-top: 1px solid var(--skai-kb-border);\n flex: none;\n}\n.skai-kb-modal-foot .skai-kb-btn {\n width: auto;\n min-width: 72px;\n}\n.skai-kb-confirm-text {\n margin: 0;\n font-size: 13px;\n line-height: 1.7;\n}\n\n/* ---------- Toast(面板底 + 彩色描边样式,与 CRM 一致;z-index 34 宿主菜单避让约定内) ---------- */\n.skai-kb-toast-layer {\n /* createPortal 到 body + fixed:相对整个 DSH 窗口顶部居中(非插件标签页区域);\n z-index 34 宿主菜单避让约定内 */\n position: fixed;\n left: 50%;\n top: 18px;\n transform: translateX(-50%);\n z-index: 34;\n display: flex;\n flex-direction: column;\n gap: 8px;\n align-items: center;\n pointer-events: none;\n}\n.skai-kb-toast {\n pointer-events: auto;\n max-width: 90%;\n padding: 7px 14px;\n border-radius: 999px;\n font-size: 12.5px;\n background: var(--skai-kb-bg-card);\n border: 1px solid var(--skai-kb-border);\n color: var(--skai-kb-text);\n box-shadow: var(--skai-kb-shadow);\n cursor: pointer;\n word-break: break-all;\n animation: skai-kb-toast-in 0.18s ease;\n}\n.skai-kb-toast-info { border-color: var(--skai-kb-primary); color: var(--skai-kb-primary); }\n.skai-kb-toast-error { border-color: var(--skai-kb-danger); color: var(--skai-kb-danger); }\n.skai-kb-toast-success { border-color: var(--skai-kb-ok); color: var(--skai-kb-ok); }\n.skai-kb-toast-warning { border-color: var(--skai-kb-warn); color: var(--skai-kb-warn); white-space: pre-line; }\n\n/* ---------- 动画(与 CRM 同款节奏) ---------- */\n@keyframes skai-kb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n@keyframes skai-kb-pop-in {\n from { opacity: 0; transform: translateY(8px) scale(0.98); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n}\n@keyframes skai-kb-toast-in {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* ---------- 概览统计(首页) ---------- */\n.skai-kb-stat-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n margin-bottom: 4px;\n}\n.skai-kb-stat-card {\n border: 1px solid var(--skai-kb-border);\n border-radius: 10px;\n background: var(--skai-kb-bg-card);\n padding: 10px 12px;\n}\n.skai-kb-stat-num {\n font-size: 18px;\n font-weight: 600;\n}\n/* 统计卡内气泡(首页「用户数」待审核提示) */\n.skai-kb-stat-badge {\n margin-left: 8px;\n vertical-align: 2px;\n cursor: default;\n}\n.skai-kb-stat-label {\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n margin-top: 2px;\n}\n.skai-kb-quick-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n}\n\n/* ---------- 知识库卡片 ---------- */\n.skai-kb-kb-meta {\n display: flex;\n gap: 12px;\n margin-top: 6px;\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n flex-wrap: wrap;\n}\n.skai-kb-kb-actions {\n display: flex;\n gap: 6px;\n margin-top: 8px;\n flex-wrap: wrap;\n}\n.skai-kb-kb-actions .skai-kb-btn-sm {\n flex: 1;\n}\n\n/* ---------- 文档列表 ---------- */\n.skai-kb-doc-item {\n cursor: pointer;\n}\n\n/* 文档卡片右上角「操作」下拉菜单(遮罩点击关闭;z-index ≤35 不覆盖宿主) */\n.skai-kb-doc-topbar {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n.skai-kb-doc-menu-anchor {\n position: relative;\n display: inline-flex;\n}\n.skai-kb-doc-menu-trigger {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n line-height: 1;\n border: none;\n background: transparent;\n color: var(--skai-kb-text-sub);\n font-size: 12px;\n padding: 2px 6px;\n border-radius: 6px;\n cursor: pointer;\n}\n/* CSS 三角下箭头:随 flex 垂直居中,字体字形对齐不可控故不用 ⌄ */\n.skai-kb-caret {\n width: 0;\n height: 0;\n border-left: 4px solid transparent;\n border-right: 4px solid transparent;\n border-top: 5px solid currentColor;\n}\n.skai-kb-doc-menu-trigger:hover {\n background: var(--skai-kb-bg-hover);\n color: var(--skai-kb-text);\n}\n.skai-kb-menu-mask {\n position: fixed;\n inset: 0;\n z-index: 34;\n}\n.skai-kb-menu {\n position: absolute;\n top: 22px;\n right: 0;\n z-index: 35;\n min-width: 104px;\n padding: 4px;\n background: var(--skai-kb-bg-card);\n border: 1px solid var(--skai-kb-border-strong);\n border-radius: 8px;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.14);\n}\n.skai-kb-menu-item {\n display: block;\n width: 100%;\n text-align: left;\n border: none;\n background: transparent;\n color: var(--skai-kb-text);\n font-size: 12px;\n padding: 6px 10px;\n border-radius: 6px;\n cursor: pointer;\n white-space: nowrap;\n}\n.skai-kb-menu-item:hover {\n background: var(--skai-kb-bg-hover);\n}\n.skai-kb-menu-item.is-danger {\n color: var(--skai-kb-danger);\n}\n.skai-kb-menu-item.is-danger:hover {\n background: var(--skai-kb-danger-weak);\n}\n\n.skai-kb-pre {\n white-space: pre-wrap;\n word-break: break-word;\n background: var(--skai-kb-bg-soft);\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n padding: 8px 10px;\n max-height: 260px;\n overflow-y: auto;\n font-size: 12px;\n margin: 0;\n}\n\n/* ---------- 文档详情正文 ---------- */\n.skai-kb-doc-detail {\n gap: 8px;\n}\n.skai-kb-md-wrap {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n border: 1px solid var(--skai-kb-border);\n border-radius: 10px;\n background: var(--skai-kb-bg-card);\n padding: 14px 16px;\n}\n\n/* ---------- Markdown 渲染(文档详情正文;.skai-kb-md 作用域,不外溢) ---------- */\n.skai-kb-md {\n font-size: 13px;\n line-height: 1.7;\n word-break: break-word;\n}\n.skai-kb-md .skai-kb-md-h {\n font-weight: 600;\n line-height: 1.4;\n margin: 14px 0 6px;\n}\n.skai-kb-md .skai-kb-md-h:first-child {\n margin-top: 0;\n}\n.skai-kb-md .skai-kb-md-h1 { font-size: 18px; }\n.skai-kb-md .skai-kb-md-h2 { font-size: 16px; }\n.skai-kb-md .skai-kb-md-h3 { font-size: 15px; }\n.skai-kb-md .skai-kb-md-h4,\n.skai-kb-md .skai-kb-md-h5,\n.skai-kb-md .skai-kb-md-h6 { font-size: 14px; }\n.skai-kb-md .skai-kb-md-p {\n margin: 6px 0;\n}\n.skai-kb-md .skai-kb-md-code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n background: var(--skai-kb-bg-soft);\n border: 1px solid var(--skai-kb-border);\n border-radius: 4px;\n padding: 0 4px;\n}\n.skai-kb-md .skai-kb-md-pre {\n background: var(--skai-kb-bg-soft);\n border: 1px solid var(--skai-kb-border);\n border-radius: 8px;\n padding: 10px 12px;\n overflow-x: auto;\n font-size: 12px;\n line-height: 1.6;\n margin: 8px 0;\n white-space: pre;\n}\n.skai-kb-md .skai-kb-md-pre code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n background: transparent;\n border: none;\n padding: 0;\n}\n.skai-kb-md .skai-kb-md-quote {\n margin: 8px 0;\n padding: 4px 12px;\n border-left: 3px solid var(--skai-kb-border-strong);\n color: var(--skai-kb-text-sub);\n background: var(--skai-kb-bg-soft);\n border-radius: 0 8px 8px 0;\n}\n.skai-kb-md .skai-kb-md-ul,\n.skai-kb-md .skai-kb-md-ol {\n margin: 6px 0;\n padding-left: 22px;\n}\n.skai-kb-md .skai-kb-md-li {\n margin: 2px 0;\n}\n.skai-kb-md .skai-kb-md-link {\n color: var(--skai-kb-link);\n text-decoration: none;\n}\n.skai-kb-md .skai-kb-md-link:hover {\n text-decoration: underline;\n}\n.skai-kb-md .skai-kb-md-img {\n max-width: 100%;\n border-radius: 8px;\n border: 1px solid var(--skai-kb-border);\n}\n.skai-kb-md .skai-kb-md-hr {\n border: none;\n border-top: 1px solid var(--skai-kb-border);\n margin: 12px 0;\n}\n.skai-kb-md .skai-kb-md-table-wrap {\n overflow-x: auto;\n margin: 8px 0;\n}\n.skai-kb-md .skai-kb-md-table {\n border-collapse: collapse;\n font-size: 12px;\n min-width: 60%;\n}\n.skai-kb-md .skai-kb-md-table th,\n.skai-kb-md .skai-kb-md-table td {\n border: 1px solid var(--skai-kb-border);\n padding: 4px 10px;\n text-align: left;\n}\n.skai-kb-md .skai-kb-md-table th {\n background: var(--skai-kb-bg-soft);\n font-weight: 600;\n}\n\n/* ---------- 时间线(文档版本) ---------- */\n.skai-kb-timeline {\n list-style: none;\n margin: 0;\n padding: 0 0 0 14px;\n border-left: 2px solid var(--skai-kb-border);\n}\n.skai-kb-timeline li {\n position: relative;\n padding: 0 0 12px 10px;\n}\n.skai-kb-timeline li::before {\n content: '';\n position: absolute;\n left: -20px;\n top: 4px;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--skai-kb-primary);\n}\n\n/* ---------- 文档上传 ---------- */\n.skai-kb-dropzone {\n border: 1px dashed var(--skai-kb-border-strong);\n border-radius: 8px;\n padding: 12px;\n}\n\n/* ---------- 成员权限 ---------- */\n.skai-kb-member-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 0;\n border-bottom: 1px solid var(--skai-kb-border);\n}\n.skai-kb-member-row:last-child {\n border-bottom: none;\n}\n.skai-kb-member-avatar {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n flex: none;\n object-fit: cover;\n background: var(--skai-kb-bg-soft);\n}\n.skai-kb-member-avatar-fallback {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 13px;\n font-weight: 500;\n color: var(--skai-kb-primary);\n background: var(--skai-kb-primary-weak);\n}\n.skai-kb-member-main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n.skai-kb-member-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.skai-kb-member-mobile {\n font-size: 12px;\n color: var(--skai-kb-text-sub);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ============================================================\n 宽容器布局(DSH 标签页 / standalone 宽屏):按容器宽度自适应,\n 与视口无关(面板开合、拖宽均正确响应)\n ============================================================ */\n@container skai-kb (min-width: 620px) {\n /* 概览:统计四列 */\n .skai-kb-stat-grid {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n }\n .skai-kb-quick-grid {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n }\n}\n\n@container skai-kb (min-width: 980px) {\n /* 超宽容器:正文限宽保持可读性(卡片分列由基础网格自适应) */\n .skai-kb-page-scroll,\n .skai-kb-md-wrap {\n max-width: 1080px;\n }\n}\n\n/* =======================================================================\n 目录(文件夹)管理:目录树 + 文档列表布局 + 拖拽指示\n ======================================================================= */\n\n/* 详情页两栏布局:左目录树 + 右文档区(窄容器降级为上下布局) */\n.skai-kb-doc-layout {\n display: flex;\n gap: 10px;\n align-items: stretch;\n min-height: 0;\n}\n\n.skai-kb-doc-main {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* 目录树面板 */\n.skai-kb-folder-tree {\n flex: none;\n width: 168px;\n display: flex;\n flex-direction: column;\n background: var(--skai-kb-bg-card, #fff);\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 10px;\n padding: 6px;\n align-self: flex-start;\n position: sticky;\n top: 0;\n}\n\n.skai-kb-folder-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 2px 6px 6px;\n border-bottom: 1px solid var(--skai-kb-bg-soft, #f0f1f3);\n margin-bottom: 4px;\n}\n\n.skai-kb-folder-head-title {\n font-size: 12px;\n font-weight: 600;\n color: var(--skai-kb-text-sub, #6b7280);\n}\n\n.skai-kb-folder-body {\n display: flex;\n flex-direction: column;\n gap: 1px;\n overflow-y: auto;\n max-height: 320px;\n}\n\n.skai-kb-folder-item {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 4px 6px;\n border-radius: 7px;\n cursor: pointer;\n font-size: 12.5px;\n color: var(--skai-kb-text, #374151);\n border: 1px dashed transparent;\n user-select: none;\n min-width: 0;\n}\n\n.skai-kb-folder-item:hover {\n background: var(--skai-kb-bg-hover, #f5f6f8);\n}\n\n.skai-kb-folder-item.is-active {\n background: var(--skai-kb-primary-weak, #eef4ff);\n color: var(--skai-kb-primary, #2f6bff);\n font-weight: 500;\n}\n\n.skai-kb-folder-arrow {\n flex: none;\n width: 12px;\n font-size: 10px;\n color: var(--skai-kb-text-caption, #9ca3af);\n text-align: center;\n}\n\n.skai-kb-folder-arrow.is-leaf {\n visibility: hidden;\n}\n\n.skai-kb-folder-icon {\n flex: none;\n font-size: 12px;\n}\n\n.skai-kb-folder-name {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.skai-kb-folder-actions {\n flex: none;\n display: none;\n gap: 2px;\n}\n\n.skai-kb-folder-item:hover .skai-kb-folder-actions {\n display: inline-flex;\n}\n\n.skai-kb-folder-act {\n border: 0;\n background: transparent;\n padding: 0 3px;\n font-size: 11px;\n line-height: 16px;\n color: var(--skai-kb-text-caption, #9ca3af);\n border-radius: 4px;\n cursor: pointer;\n}\n\n.skai-kb-folder-act:hover {\n background: var(--skai-kb-bg-soft, #f0f1f3);\n color: var(--skai-kb-primary, #2f6bff);\n}\n\n.skai-kb-folder-act.is-danger:hover {\n color: var(--skai-kb-danger, #e5484d);\n}\n\n/* 目录树节点:文档拖入高亮(in)与同级排序插入线(before/after) */\n.skai-kb-folder-item.skai-kb-drop-in {\n border: 1px solid var(--skai-kb-primary, #2f6bff);\n background: var(--skai-kb-primary-weak, #eef4ff);\n}\n\n.skai-kb-folder-item.skai-kb-drop-before {\n box-shadow: inset 0 2px 0 0 var(--skai-kb-primary, #2f6bff);\n}\n\n.skai-kb-folder-item.skai-kb-drop-after {\n box-shadow: inset 0 -2px 0 0 var(--skai-kb-primary, #2f6bff);\n}\n\n/* 文档列表项:作为同目录排序 drop 目标时高亮 */\n.skai-kb-doc-item.skai-kb-drop-in {\n outline: 1.5px dashed var(--skai-kb-primary, #2f6bff);\n outline-offset: -2px;\n}\n\n/* 移动文档弹窗:目录选择列表 */\n.skai-kb-move-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 240px;\n overflow-y: auto;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 8px;\n padding: 4px;\n}\n\n.skai-kb-move-item {\n border: 0;\n background: transparent;\n text-align: left;\n padding: 6px 8px;\n font-size: 12.5px;\n border-radius: 6px;\n cursor: pointer;\n color: var(--skai-kb-text, #374151);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.skai-kb-move-item:hover {\n background: var(--skai-kb-bg-hover, #f5f6f8);\n}\n\n.skai-kb-move-item.is-active {\n background: var(--skai-kb-primary-weak, #eef4ff);\n color: var(--skai-kb-primary, #2f6bff);\n font-weight: 500;\n}\n\n/* =======================================================================\n 文档在线编辑:Markdown 可视化编辑器(工具栏 + 双栏分屏)\n ======================================================================= */\n\n.skai-kb-editor {\n display: flex;\n flex-direction: column;\n gap: 8px;\n height: 100%;\n min-height: 320px;\n}\n\n.skai-kb-editor-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.skai-kb-editor-tools {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-wrap: wrap;\n}\n\n.skai-kb-editor-tool {\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n background: var(--skai-kb-bg-card, #fff);\n border-radius: 6px;\n padding: 3px 7px;\n font-size: 11.5px;\n line-height: 16px;\n color: var(--skai-kb-text, #374151);\n cursor: pointer;\n}\n\n.skai-kb-editor-tool:hover:not(:disabled) {\n border-color: var(--skai-kb-primary, #2f6bff);\n color: var(--skai-kb-primary, #2f6bff);\n}\n\n.skai-kb-editor-tool:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.skai-kb-editor-modes {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.skai-kb-editor-mode {\n border: 1px solid transparent;\n background: transparent;\n padding: 3px 8px;\n font-size: 12px;\n border-radius: 6px;\n cursor: pointer;\n color: var(--skai-kb-text-sub, #6b7280);\n}\n\n.skai-kb-editor-mode.is-active {\n background: var(--skai-kb-primary-weak, #eef4ff);\n color: var(--skai-kb-primary, #2f6bff);\n font-weight: 500;\n}\n\n.skai-kb-editor-dirty {\n font-size: 11px;\n color: var(--skai-kb-warn, #d97706);\n}\n\n/* 双栏分屏:编辑区 | 预览区(仅预览/仅编辑时单栏占满) */\n.skai-kb-editor-panes {\n display: flex;\n gap: 8px;\n flex: 1;\n min-height: 0;\n}\n\n.skai-kb-editor-input {\n flex: 1;\n min-width: 0;\n min-height: 300px;\n resize: vertical;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 10px;\n padding: 10px 12px;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12.5px;\n line-height: 1.6;\n color: var(--skai-kb-text, #374151);\n background: var(--skai-kb-bg-card, #fff);\n}\n\n.skai-kb-editor-input:focus {\n outline: none;\n border-color: var(--skai-kb-primary, #2f6bff);\n}\n\n/* 仅编辑模式:编辑框撑满父容器高度(取消最小高度下限与手动拉伸) */\n.skai-kb-editor.edit .skai-kb-editor-input {\n min-height: 0;\n resize: none;\n}\n\n.skai-kb-editor-preview {\n flex: 1;\n min-width: 0;\n min-height: 300px;\n border: 1px solid var(--skai-kb-border, #e5e7eb);\n border-radius: 10px;\n padding: 10px 12px;\n overflow-y: auto;\n background: var(--skai-kb-bg-soft, #fafafa);\n}\n\n/* 窄容器:编辑器分屏改为上下堆叠 */\n@container skai-kb (max-width: 560px) {\n .skai-kb-doc-layout {\n flex-direction: column;\n }\n\n .skai-kb-folder-tree {\n width: auto;\n position: static;\n }\n\n .skai-kb-folder-body {\n flex-direction: row;\n flex-wrap: wrap;\n max-height: none;\n }\n\n .skai-kb-editor-panes.split {\n flex-direction: column;\n }\n\n .skai-kb-editor-panes.split .skai-kb-editor-input,\n .skai-kb-editor-panes.split .skai-kb-editor-preview {\n min-height: 180px;\n }\n}\n\n/* ---------- 对话消息操作区「存入知识库」(conversation.chat.assistant-actions) ----------\n 按钮渲染于宿主 AI 消息操作行(官方复制/点赞同款 28px 图标钮),颜色直接取\n DSH 官方令牌(--dsw-alias-*,全局可达,随宿主浅/深色主题切换);\n --skai-kb-* 别名仅作 standalone 兜底 */\n.skai-kb-chat-save-btn {\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 6px;\n color: var(--dsw-alias-label-tertiary, var(--skai-kb-text-caption, #9ca1ab));\n background: none;\n border: none;\n border-radius: 28px;\n cursor: pointer;\n transition: color 0.15s, background 0.15s;\n}\n\n.skai-kb-chat-save-btn:hover {\n color: var(--dsw-alias-label-secondary, var(--skai-kb-text-sub, #8a8f99));\n background: var(--dsw-alias-interactive-bg-hover, var(--skai-kb-bg-hover, rgba(0, 0, 0, 0.04)));\n}\n\n.skai-kb-chat-save-preview {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.skai-kb-chat-save-preview-label {\n font-size: 12px;\n color: var(--skai-kb-text-caption);\n}\n\n/* 保存方式切换(新建文档 / 覆盖已有文档):与弹窗按钮风格一致 */\n.skai-kb-chat-save-modes {\n display: flex;\n gap: 8px;\n}\n.skai-kb-chat-save-hint {\n display: block;\n margin-top: 4px;\n font-size: 12px;\n color: var(--skai-kb-text-caption);\n}\n\n.skai-kb-chat-save-preview pre {\n max-height: 140px;\n overflow: auto;\n margin: 0;\n padding: 8px 10px;\n font-size: 12px;\n line-height: 1.6;\n white-space: pre-wrap;\n word-break: break-word;\n background: var(--skai-kb-bg-soft);\n border-radius: 6px;\n}\n\n/* ---------- 模型服务帮助弹窗(设置页「模型服务」区块「配置说明」入口) ---------- */\n.skai-kb-help-section {\n margin-bottom: 14px;\n}\n.skai-kb-help-section:last-child {\n margin-bottom: 0;\n}\n.skai-kb-help-title {\n margin: 0 0 6px;\n font-size: 13px;\n font-weight: 600;\n}\n.skai-kb-help-list {\n margin: 0;\n padding-left: 18px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.skai-kb-help-list li {\n font-size: 12.5px;\n line-height: 1.7;\n}\n.skai-kb-help-text {\n margin: 0;\n font-size: 12.5px;\n line-height: 1.7;\n}\n.skai-kb-help-link {\n color: var(--skai-kb-primary);\n text-decoration: none;\n word-break: break-all;\n}\n.skai-kb-help-link:hover {\n text-decoration: underline;\n}\n.skai-kb-help-code {\n padding: 1px 5px;\n border-radius: 4px;\n background: var(--skai-kb-bg-soft);\n font-size: 12px;\n word-break: break-all;\n}\n\n";
|
|
7228
8527
|
//#endregion
|
|
7229
8528
|
//#region src/index.js
|
|
7230
8529
|
/**
|
|
@@ -7784,6 +9083,8 @@ window.__ModuleLoader__.load({
|
|
|
7784
9083
|
const [docs, setDocs] = (0, react.useState)([]);
|
|
7785
9084
|
const [docsLoaded, setDocsLoaded] = (0, react.useState)(false);
|
|
7786
9085
|
const [docId, setDocId] = (0, react.useState)(0);
|
|
9086
|
+
const [confirmForce, setConfirmForce] = (0, react.useState)(false);
|
|
9087
|
+
const [forcing, setForcing] = (0, react.useState)(false);
|
|
7787
9088
|
const chat = typeof useChat === "function" ? useChat((s) => s) : null;
|
|
7788
9089
|
const chatText = (0, react.useMemo)(() => extractTextFromChat(chat, messageId), [chat, messageId]);
|
|
7789
9090
|
const kbDocRef = (0, react.useMemo)(() => extractKbDocRef(chat), [chat]);
|
|
@@ -7906,172 +9207,195 @@ window.__ModuleLoader__.load({
|
|
|
7906
9207
|
toast(`已覆盖文档「${target?.name || docId}」,重新解析与向量化中`, "success", 4e3);
|
|
7907
9208
|
setOpen(false);
|
|
7908
9209
|
} catch (e) {
|
|
7909
|
-
if (
|
|
7910
|
-
|
|
7911
|
-
content,
|
|
7912
|
-
baseUpdatedAt: target?.update_time || 0,
|
|
7913
|
-
force: true
|
|
7914
|
-
});
|
|
7915
|
-
toast(`已强制覆盖文档「${target?.name || docId}」,重新解析与向量化中`, "success", 4e3);
|
|
7916
|
-
setOpen(false);
|
|
7917
|
-
return;
|
|
7918
|
-
} catch (e2) {
|
|
7919
|
-
setError(e2 && (e2.msg || e2.message) || "覆盖失败,请稍后重试");
|
|
9210
|
+
if (e?.code === 409 || /冲突/.test(e?.msg || e?.message || "")) {
|
|
9211
|
+
setConfirmForce(true);
|
|
7920
9212
|
return;
|
|
7921
9213
|
}
|
|
7922
9214
|
setError(e && (e.msg || e.message) || "覆盖失败,请稍后重试");
|
|
7923
9215
|
}
|
|
7924
9216
|
}
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
}
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
9217
|
+
/** 冲突确认弹窗确认后强制覆盖(force=true 跳过乐观锁) */
|
|
9218
|
+
async function doForceOverwrite() {
|
|
9219
|
+
const target = docs.find((d) => Number(d.id) === Number(docId));
|
|
9220
|
+
setForcing(true);
|
|
9221
|
+
try {
|
|
9222
|
+
await updateDocContent(kbId, docId, {
|
|
9223
|
+
content,
|
|
9224
|
+
baseUpdatedAt: target?.update_time || 0,
|
|
9225
|
+
force: true
|
|
9226
|
+
});
|
|
9227
|
+
toast(`已强制覆盖文档「${target?.name || docId}」,重新解析与向量化中`, "success", 4e3);
|
|
9228
|
+
setConfirmForce(false);
|
|
9229
|
+
setOpen(false);
|
|
9230
|
+
} catch (e2) {
|
|
9231
|
+
setConfirmForce(false);
|
|
9232
|
+
setError(e2 && (e2.msg || e2.message) || "覆盖失败,请稍后重试");
|
|
9233
|
+
} finally {
|
|
9234
|
+
setForcing(false);
|
|
9235
|
+
}
|
|
9236
|
+
}
|
|
9237
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
9238
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9239
|
+
ref: btnRef,
|
|
7946
9240
|
type: "button",
|
|
7947
|
-
className: "skai-kb-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
9241
|
+
className: "skai-kb-chat-save-btn",
|
|
9242
|
+
title: BTN_LABEL$1,
|
|
9243
|
+
"aria-label": BTN_LABEL$1,
|
|
9244
|
+
onClick: openModal,
|
|
9245
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BookPlusGlyph, { size: 15 })
|
|
9246
|
+
}),
|
|
9247
|
+
open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Modal, {
|
|
9248
|
+
title: "存入知识库",
|
|
9249
|
+
width: 480,
|
|
9250
|
+
escClose: !confirmForce,
|
|
9251
|
+
onClose: () => {
|
|
9252
|
+
if (!busy && !confirmForce) setOpen(false);
|
|
9253
|
+
},
|
|
9254
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9255
|
+
type: "button",
|
|
9256
|
+
className: "skai-kb-btn",
|
|
9257
|
+
onClick: () => setOpen(false),
|
|
9258
|
+
disabled: busy,
|
|
9259
|
+
children: "取消"
|
|
9260
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9261
|
+
type: "button",
|
|
9262
|
+
className: "skai-kb-btn skai-kb-btn-primary",
|
|
9263
|
+
onClick: submit,
|
|
9264
|
+
disabled: busy,
|
|
9265
|
+
children: busy ? "提交中…" : mode === "overwrite" ? "覆盖" : "保存"
|
|
9266
|
+
})] }),
|
|
9267
|
+
children: [
|
|
9268
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9269
|
+
className: "skai-kb-field",
|
|
9270
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9271
|
+
className: "skai-kb-field-label",
|
|
9272
|
+
children: "保存方式"
|
|
9273
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9274
|
+
className: "skai-kb-chat-save-modes",
|
|
9275
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9276
|
+
type: "button",
|
|
9277
|
+
className: `skai-kb-btn${mode === "create" ? " skai-kb-btn-primary" : ""}`,
|
|
9278
|
+
onClick: () => setMode("create"),
|
|
9279
|
+
disabled: busy,
|
|
9280
|
+
children: "新建文档"
|
|
9281
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9282
|
+
type: "button",
|
|
9283
|
+
className: `skai-kb-btn${mode === "overwrite" ? " skai-kb-btn-primary" : ""}`,
|
|
9284
|
+
onClick: () => {
|
|
9285
|
+
setMode("overwrite");
|
|
9286
|
+
setError("");
|
|
9287
|
+
},
|
|
9288
|
+
disabled: busy,
|
|
9289
|
+
children: "覆盖已有文档"
|
|
9290
|
+
})]
|
|
7975
9291
|
})]
|
|
7976
|
-
})
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7981
|
-
className: "skai-kb-field-label",
|
|
7982
|
-
children: "目标知识库 *"
|
|
7983
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
7984
|
-
className: "skai-kb-select",
|
|
7985
|
-
value: kbId,
|
|
7986
|
-
onChange: (e) => setKbId(Number(e.target.value) || 0),
|
|
7987
|
-
disabled: busy,
|
|
7988
|
-
children: [
|
|
7989
|
-
!kbLoaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
7990
|
-
value: 0,
|
|
7991
|
-
children: "加载中…"
|
|
7992
|
-
}) : null,
|
|
7993
|
-
kbLoaded && kbs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
7994
|
-
value: 0,
|
|
7995
|
-
children: "(无可用知识库,请先创建)"
|
|
7996
|
-
}) : null,
|
|
7997
|
-
kbs.map((kb) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
7998
|
-
value: kb.id,
|
|
7999
|
-
children: kb.name
|
|
8000
|
-
}, kb.id))
|
|
8001
|
-
]
|
|
8002
|
-
})]
|
|
8003
|
-
}),
|
|
8004
|
-
mode === "overwrite" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8005
|
-
className: "skai-kb-field",
|
|
8006
|
-
children: [
|
|
8007
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9292
|
+
}),
|
|
9293
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9294
|
+
className: "skai-kb-field",
|
|
9295
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8008
9296
|
className: "skai-kb-field-label",
|
|
8009
|
-
children: "
|
|
8010
|
-
}),
|
|
8011
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
9297
|
+
children: "目标知识库 *"
|
|
9298
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
8012
9299
|
className: "skai-kb-select",
|
|
8013
|
-
value:
|
|
8014
|
-
onChange: (e) =>
|
|
8015
|
-
disabled: busy
|
|
9300
|
+
value: kbId,
|
|
9301
|
+
onChange: (e) => setKbId(Number(e.target.value) || 0),
|
|
9302
|
+
disabled: busy,
|
|
8016
9303
|
children: [
|
|
8017
|
-
!
|
|
9304
|
+
!kbLoaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
8018
9305
|
value: 0,
|
|
8019
9306
|
children: "加载中…"
|
|
8020
9307
|
}) : null,
|
|
8021
|
-
|
|
9308
|
+
kbLoaded && kbs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
8022
9309
|
value: 0,
|
|
8023
|
-
children: "
|
|
9310
|
+
children: "(无可用知识库,请先创建)"
|
|
8024
9311
|
}) : null,
|
|
8025
|
-
|
|
8026
|
-
value:
|
|
8027
|
-
children:
|
|
8028
|
-
},
|
|
9312
|
+
kbs.map((kb) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
9313
|
+
value: kb.id,
|
|
9314
|
+
children: kb.name
|
|
9315
|
+
}, kb.id))
|
|
8029
9316
|
]
|
|
8030
|
-
})
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
8065
|
-
className: "skai-kb-
|
|
8066
|
-
children:
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
9317
|
+
})]
|
|
9318
|
+
}),
|
|
9319
|
+
mode === "overwrite" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9320
|
+
className: "skai-kb-field",
|
|
9321
|
+
children: [
|
|
9322
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9323
|
+
className: "skai-kb-field-label",
|
|
9324
|
+
children: "选择要覆盖的文档 *"
|
|
9325
|
+
}),
|
|
9326
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
9327
|
+
className: "skai-kb-select",
|
|
9328
|
+
value: docId,
|
|
9329
|
+
onChange: (e) => setDocId(Number(e.target.value) || 0),
|
|
9330
|
+
disabled: busy || !docsLoaded || docs.length === 0,
|
|
9331
|
+
children: [
|
|
9332
|
+
!docsLoaded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
9333
|
+
value: 0,
|
|
9334
|
+
children: "加载中…"
|
|
9335
|
+
}) : null,
|
|
9336
|
+
docsLoaded && docs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
9337
|
+
value: 0,
|
|
9338
|
+
children: "(该知识库暂无文档)"
|
|
9339
|
+
}) : null,
|
|
9340
|
+
docs.map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
9341
|
+
value: d.id,
|
|
9342
|
+
children: d.name
|
|
9343
|
+
}, d.id))
|
|
9344
|
+
]
|
|
9345
|
+
}),
|
|
9346
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9347
|
+
className: "skai-kb-chat-save-hint",
|
|
9348
|
+
children: "覆盖将替换文档全部内容,原内容自动保留为历史版本"
|
|
9349
|
+
})
|
|
9350
|
+
]
|
|
9351
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9352
|
+
className: "skai-kb-field",
|
|
9353
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9354
|
+
className: "skai-kb-field-label",
|
|
9355
|
+
children: "文档名称 *"
|
|
9356
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
9357
|
+
className: "skai-kb-input",
|
|
9358
|
+
value: docName,
|
|
9359
|
+
maxLength: 120,
|
|
9360
|
+
onChange: (e) => setDocName(e.target.value),
|
|
9361
|
+
placeholder: "如:接口设计要点-20260101-1530.md",
|
|
9362
|
+
disabled: busy
|
|
9363
|
+
})]
|
|
9364
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9365
|
+
className: "skai-kb-field",
|
|
9366
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9367
|
+
className: "skai-kb-field-label",
|
|
9368
|
+
children: "业务标签(逗号分隔,可选)"
|
|
9369
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
9370
|
+
className: "skai-kb-input",
|
|
9371
|
+
value: tags,
|
|
9372
|
+
onChange: (e) => setTags(e.target.value),
|
|
9373
|
+
placeholder: "如:对话纪要, 方案",
|
|
9374
|
+
disabled: busy
|
|
9375
|
+
})]
|
|
9376
|
+
})] }),
|
|
9377
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9378
|
+
className: "skai-kb-chat-save-preview",
|
|
9379
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
9380
|
+
className: "skai-kb-chat-save-preview-label",
|
|
9381
|
+
children: "内容预览(前 200 字)"
|
|
9382
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("pre", { children: [content.slice(0, 200), content.length > 200 ? "…" : ""] })]
|
|
9383
|
+
}),
|
|
9384
|
+
error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9385
|
+
className: "skai-kb-field-error",
|
|
9386
|
+
children: error
|
|
9387
|
+
}) : null
|
|
9388
|
+
]
|
|
9389
|
+
}) : null,
|
|
9390
|
+
confirmForce ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmDialog, {
|
|
9391
|
+
title: "文档已被修改",
|
|
9392
|
+
message: "该文档在此期间已被其他人修改,是否强制覆盖?覆盖将替换文档全部内容,原内容自动保留为历史版本。",
|
|
9393
|
+
confirmText: "强制覆盖",
|
|
9394
|
+
busy: forcing,
|
|
9395
|
+
onConfirm: doForceOverwrite,
|
|
9396
|
+
onClose: () => setConfirmForce(false)
|
|
9397
|
+
}) : null
|
|
9398
|
+
] });
|
|
8075
9399
|
}
|
|
8076
9400
|
//#endregion
|
|
8077
9401
|
//#region src/components/chat/ReferenceKbButton.jsx
|