diff --git a/Cunkebao/src/pages/mobile/mine/recharge/index/index.tsx b/Cunkebao/src/pages/mobile/mine/recharge/index/index.tsx index c0a493b1..ac5ef9e5 100644 --- a/Cunkebao/src/pages/mobile/mine/recharge/index/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/recharge/index/index.tsx @@ -44,49 +44,8 @@ const aiServices = [ }, ]; -// 版本套餐数据 -const versionPackages = [ - { - id: 1, - name: "普通版本", - icon: "📦", - price: "免费", - description: "充值即可使用,包含基础AI功能", - features: ["基础AI服务", "标准客服支持", "基础数据统计"], - status: "当前使用中", - buttonText: null, - tagColor: undefined, - }, - { - id: 2, - name: "标准版本", - icon: "👑", - price: "¥98/月", - tag: "推荐", - tagColor: "blue", - description: "适合中小企业,AI功能更丰富", - features: ["高级AI服务", "优先客服支持", "详细数据分析", "API接口访问"], - status: null, - buttonText: "立即升级", - }, - { - id: 3, - name: "企业版本", - icon: "🏢", - price: "¥1980/月", - description: "适合大型企业,提供专属服务", - features: [ - "专属AI服务", - "24小时专属客服", - "高级数据分析", - "API接口访问", - "专属技术支持", - ], - status: null, - buttonText: "立即升级", - tagColor: undefined, - }, -]; +// 快捷金额选项(可选) +const quickAmounts = [50, 100, 200, 500, 1000, 2000]; const Recharge: React.FC = () => { const navigate = useNavigate(); @@ -96,6 +55,7 @@ const Recharge: React.FC = () => { const [loading, setLoading] = useState(false); const [activeTab, setActiveTab] = useState("account"); const [taocanList, setTaocanList] = useState([]); + const [customAmount, setCustomAmount] = useState(""); // 自定义充值金额 // 加载套餐列表 useEffect(() => { @@ -113,8 +73,68 @@ const Recharge: React.FC = () => { loadTaocanList(); }, []); - // 充值操作 - const handleRecharge = async () => { + // 账户充值操作(自定义金额) + const handleCustomRecharge = async () => { + const amount = parseFloat(customAmount); + if (!customAmount || isNaN(amount) || amount <= 0) { + Toast.show({ content: "请输入有效的充值金额", position: "top" }); + return; + } + if (amount < 1) { + Toast.show({ content: "充值金额不能小于1元", position: "top" }); + return; + } + if (amount > 100000) { + Toast.show({ content: "单次充值金额不能超过10万元", position: "top" }); + return; + } + + setLoading(true); + try { + const res = await pay({ + id: "", // 自定义充值不需要套餐ID + price: Math.round(amount * 100), // 转换为分 + }); + // 假设返回的是二维码链接,存储在res中 + if (res) { + // 显示二维码弹窗 + Dialog.show({ + content: ( +
+
+ 请使用微信扫码支付 +
+ 支付二维码 +
+ 支付金额: ¥{amount.toFixed(2)} +
+
+ ), + closeOnMaskClick: true, + }); + } + } catch (error) { + console.error("支付失败:", error); + Toast.show({ content: "支付失败,请重试", position: "top" }); + } finally { + setLoading(false); + } + }; + + // 版本套餐充值操作 + const handlePackageRecharge = async () => { if (!selected) { Toast.show({ content: "请选择充值套餐", position: "top" }); return; @@ -177,98 +197,121 @@ const Recharge: React.FC = () => { + -
选择套餐
-
- {taocanList.map(item => ( - - ))} +
充值金额
+ + {/* 快捷金额选择 */} +
+
+ 快捷选择 +
+
+ {quickAmounts.map(amount => ( + + ))} +
- {selected && ( + + {/* 自定义金额输入 */} +
+
+ 或输入自定义金额(元) +
+ setCustomAmount(e.target.value)} + style={{ + width: "100%", + padding: "12px 16px", + border: "1px solid #e5e5e5", + borderRadius: "8px", + fontSize: "16px", + outline: "none", + }} + min="1" + max="100000" + step="0.01" + />
-
- {selected.name} - {selected.isRecommend === 1 && ( - - 推荐 - - )} - {selected.isHot === 1 && ( - - 热门 - - )} -
-
- 包含 {selected.tokens} Tokens -
- {selected.originalPrice && ( -
+ • 单次充值上限:100,000元 +
• 支持微信支付 +
+
+ + {customAmount && parseFloat(customAmount) > 0 && ( +
+
+ 充值金额: + - 原价: ¥{selected.originalPrice / 100} -
- )} + ¥{parseFloat(customAmount).toFixed(2)} + +
+
+ 到账金额:¥{parseFloat(customAmount).toFixed(2)} +
)} + + -
服务消耗
+
充值说明
- 使用以下服务将从余额中扣除相应费用。 + • 充值金额实时到账,可用于购买AI服务和版本套餐 +
+ • 使用AI服务将从余额中扣除相应费用 +
• 余额支持退款,详情请联系客服
+ {balance < 10 && (
@@ -349,63 +392,106 @@ const Recharge: React.FC = () => {
- 存客宝版本套餐 + 充值套餐
- 选择适合的版本,享受不同级别的AI服务 + 选择合适的充值套餐,享受更多优惠
- {versionPackages.map(pkg => ( + {taocanList.map(pkg => (
-
{pkg.icon}
+
💰
{pkg.name} - {pkg.tag && ( + {pkg.isRecommend === 1 && ( - {pkg.tag} + 推荐 + + )} + {pkg.isHot === 1 && ( + + 热门 )}
-
{pkg.price}
+
+ ¥{pkg.price / 100} +
-
- {pkg.description} -
+ {pkg.description && ( +
+ {pkg.description} +
+ )}
-
包含功能:
- {pkg.features.map((feature, index) => ( -
- - {feature} +
套餐内容:
+
+ + 包含 {pkg.tokens} Tokens +
+ {pkg.originalPrice && ( +
+ 原价: ¥{pkg.originalPrice / 100}
- ))} + )}
- {pkg.status && ( -
{pkg.status}
- )} - {pkg.buttonText && ( - - )} + ))}
+ + {selected && ( +
+ +
+ )}
); diff --git a/Cunkebao/src/pages/mobile/workspace/ai-knowledge/api 文档 b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/api 文档 new file mode 100644 index 00000000..1659a0c3 --- /dev/null +++ b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/api 文档 @@ -0,0 +1,168 @@ +初始化AI功能(每次都得执行) + GET /v1/knowledge/init + + + + +发布并应用AI工具(修改知识库需要重新发布) + GET /v1/knowledge/release + 传参: + { + id:number + } + + 返回参数: + { + "id": 1, + "companyId": 2130, + "userId": 128, + "config": { + "name": "魔兽世界", + "model_id": "1737521813", + "prompt_info": "# 角色\r\n你是一位全能知识客服,作为专业的客服智能体,具备全面的知识储备,能够回答用户提出的各类问题。在回答问题前,会仔细查阅知识库内容,并且始终严格遵守中国法律法规。\r\n\r\n## 技能\r\n### 技能 1: 回答用户问题\r\n1. 当用户提出问题时,首先在知识库中进行搜索查找相关信息。\r\n2. 依据知识库中的内容,为用户提供准确、清晰、完整的回答。\r\n \r\n## 限制\r\n- 仅依据知识库内容回答问题,对于知识库中没有的信息,如实告知用户无法回答。\r\n- 回答必须严格遵循中国法律法规,不得出现任何违法违规内容。\r\n- 回答需简洁明了,避免冗长复杂的表述。" + }, + "createTime": "2025-10-24 16:55:08", + "updateTime": "2025-10-24 16:56:28", + "isRelease": 1, + "releaseTime": 1761296188, + "botId": "7564707767488610345", + "datasetId": "7564708881499619366" + } + + + +知识库类型 - 列表 + GET /v1/knowledge/typeList + 传参: + { + page:number + limit:number + } + 返回参数: + "total": 5, + "per_page": 20, + "current_page": 1, + "last_page": 1, + "data": [ + { + "id": 1, + "type": 0, + "name": "产品介绍库", + "description": "包含所有产品相关的介绍文档、图片和视频资料", + "label": [ + "产品", + "营销" + ], + "prompt": null, + "companyId": 0, + "userId": 0, + "createTime": null, + "updateTime": null, + "isDel": 0, + "delTime": 0 + }, + + ] + + +知识库类型 - 添加 + POST /v1/knowledge/addType + 传参: + { + name:string + description:string + label:string[] + prompt:string + } + + +知识库类型 - 编辑 + POST /v1/knowledge/editType + 传参: + { + id:number + name:string + description:string + label:string[] + prompt:string + } + + +知识库类型 - 删除 + DELETE /v1/knowledge/deleteType + 传参: + { + id:number + } + + +知识库 - 列表 + GET /v1/knowledge/getList + 传参: + { + name:number + typeId:number + label:string[] + fileUrl:string + } + 返回参数: + { + "total": 1, + "per_page": 20, + "current_page": 1, + "last_page": 1, + "data": [ + { + "id": 1, + "typeId": 1, + "name": "存客宝项目介绍(面向开发人员).docx", + "label": [ + "1231", + "3453" + ], + "companyId": 2130, + "userId": 128, + "createTime": 1761296164, + "updateTime": 1761296165, + "isDel": 0, + "delTime": 0, + "documentId": "7564706328503189558", + "fileUrl": "http://karuosiyujzk.oss-cn-shenzhen.aliyuncs.com/2025/10/22/9de59fc8723f10973ade586650dfb235.docx", + "type": { + "id": 1, + "type": 0, + "name": "产品介绍库", + "description": "包含所有产品相关的介绍文档、图片和视频资料", + "label": [ + "产品", + "营销" + ], + "prompt": null, + "companyId": 0, + "userId": 0, + "createTime": null, + "updateTime": null, + "isDel": 0, + "delTime": 0 + } + } + ] + } + + +知识库 - 添加 + POST /v1/knowledge/add + 传参: + { + name:number + typeId:number + label:string[] + fileUrl:string + } + +知识库 - 删除 + DELETE /v1/knowledge/delete + 传参: + { + id:number + } diff --git a/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/api.ts b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/api.ts new file mode 100644 index 00000000..aecda9a0 --- /dev/null +++ b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/api.ts @@ -0,0 +1,109 @@ +import request from "@/api/request"; +import type { + KnowledgeBaseDetailResponse, + MaterialListResponse, + CallerListResponse, +} from "./data"; + +// 获取知识库类型详情(复用列表接口) +export function getKnowledgeBaseDetail( + id: number, +): Promise { + // 接口文档中没有单独的详情接口,通过列表接口获取 + return request("/v1/knowledge/typeList", { page: 1, limit: 100 }, "GET").then( + (res: any) => { + const item = res.data?.find((item: any) => item.id === id); + if (!item) { + throw new Error("知识库不存在"); + } + // 转换数据格式 + return { + ...item, + tags: item.label || [], + useIndependentPrompt: !!item.prompt, + independentPrompt: item.prompt || "", + materials: [], // 需要单独获取 + callers: [], // 暂无接口 + }; + }, + ); +} + +// 获取知识库素材列表(对应接口的 knowledge/getList) +export function getMaterialList(params: { + knowledgeBaseId: number; + page?: number; + limit?: number; + name?: string; + label?: string[]; +}): Promise { + return request( + "/v1/knowledge/getList", + { + typeId: params.knowledgeBaseId, + name: params.name, + label: params.label, + page: params.page || 1, + limit: params.limit || 20, + }, + "GET", + ).then((res: any) => ({ + list: res.data || [], + total: res.total || 0, + })); +} + +// 添加素材 +export function uploadMaterial(data: { + typeId: number; + name: string; + label: string[]; + fileUrl: string; +}): Promise { + return request("/v1/knowledge/add", data, "POST"); +} + +// 删除素材 +export function deleteMaterial(id: number): Promise { + return request("/v1/knowledge/delete", { id }, "DELETE"); +} + +// 获取调用者列表(接口未提供) +export function getCallerList( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + params: { + knowledgeBaseId: number; + page?: number; + limit?: number; + }, +): Promise { + // 注意:实际接口未提供,需要后端补充 + console.warn("getCallerList 接口未提供"); + return Promise.resolve({ + list: [], + total: 0, + }); +} + +// 更新知识库配置(使用编辑接口) +export function updateKnowledgeBaseConfig(data: { + id: number; + name?: string; + description?: string; + label?: string[]; + aiCallEnabled?: boolean; + useIndependentPrompt?: boolean; + independentPrompt?: string; +}): Promise { + return request( + "/v1/knowledge/editType", + { + id: data.id, + name: data.name || "", + description: data.description || "", + label: data.label || [], + prompt: data.useIndependentPrompt ? data.independentPrompt || "" : "", + }, + "POST", + ); +} diff --git a/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/data.ts b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/data.ts new file mode 100644 index 00000000..b477d105 --- /dev/null +++ b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/data.ts @@ -0,0 +1,48 @@ +// AI知识库详情相关类型定义 +import type { KnowledgeBase, Caller } from "../list/data"; + +export type { KnowledgeBase, Caller }; + +// 素材类型(对应接口的 knowledge) +export interface Material { + id: number; + typeId: number; // 知识库类型ID + name: string; // 文件名 + label: string[]; // 标签 + companyId: number; + userId: number; + createTime: number; + updateTime: number; + isDel: number; + delTime: number; + documentId: string; // 文档ID + fileUrl: string; // 文件URL + type?: KnowledgeBase; // 关联的知识库类型信息 + // 前端扩展字段 + fileName?: string; // 映射自 name + fileSize?: number; // 文件大小(前端计算) + fileType?: string; // 文件类型(从 name 提取) + filePath?: string; // 映射自 fileUrl + tags?: string[]; // 映射自 label + uploadTime?: string; // 映射自 createTime + uploaderId?: number; // 映射自 userId + uploaderName?: string; +} + +// 知识库详情响应 +export interface KnowledgeBaseDetailResponse extends KnowledgeBase { + materials: Material[]; + callers: Caller[]; +} + +// 素材列表响应 +export interface MaterialListResponse { + list: Material[]; + total: number; +} + +// 调用者列表响应 +export interface CallerListResponse { + list: Caller[]; + total: number; +} diff --git a/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/index.module.scss b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/index.module.scss new file mode 100644 index 00000000..ed1fbafa --- /dev/null +++ b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/index.module.scss @@ -0,0 +1,481 @@ +// 详情页容器 +.detailPage { + background: #f5f5f5; + min-height: 100vh; +} + +// Tab容器 +.tabContainer { + background: #fff; + border-bottom: 1px solid #f0f0f0; +} + +.tabs { + display: flex; + padding: 0 16px; +} + +.tab { + flex: 1; + padding: 14px 0; + text-align: center; + font-size: 15px; + color: #666; + border-bottom: 2px solid transparent; + cursor: pointer; + transition: all 0.2s; + background: none; + border: none; + outline: none; + + &:active { + opacity: 0.7; + } +} + +.tabActive { + color: #1890ff; + font-weight: 600; + border-bottom-color: #1890ff; +} + +// 知识库信息卡片 +.infoCard { + background: #fff; + margin: 12px 16px; + border-radius: 12px; + padding: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +} + +.infoHeader { + display: flex; + align-items: flex-start; + gap: 12px; + margin-bottom: 16px; +} + +.infoIcon { + width: 56px; + height: 56px; + border-radius: 12px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 28px; + flex-shrink: 0; +} + +.infoContent { + flex: 1; + min-width: 0; +} + +.infoName { + font-size: 18px; + font-weight: 600; + color: #222; + margin-bottom: 6px; +} + +.infoDescription { + font-size: 13px; + color: #888; + line-height: 1.5; +} + +.infoStats { + display: flex; + justify-content: space-between; + padding: 12px 0; + border-top: 1px solid #f0f0f0; + border-bottom: 1px solid #f0f0f0; + margin-bottom: 16px; +} + +.statItem { + flex: 1; + text-align: center; +} + +.statValue { + font-size: 20px; + font-weight: 600; + color: #1890ff; + margin-bottom: 4px; +} + +.statValueSuccess { + color: #52c41a; +} + +.statLabel { + font-size: 12px; + color: #888; +} + +.infoTags { + margin-bottom: 16px; +} + +.tagTitle { + font-size: 13px; + color: #666; + margin-bottom: 8px; + font-weight: 500; +} + +.tags { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.tag { + padding: 4px 12px; + border-radius: 10px; + font-size: 12px; + background: rgba(24, 144, 255, 0.1); + color: #1890ff; + border: 1px solid rgba(24, 144, 255, 0.2); +} + +// 配置区域 +.configSection { + margin-bottom: 16px; +} + +.configItem { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 0; + border-bottom: 1px solid #f5f5f5; + + &:last-child { + border-bottom: none; + } +} + +.configLabel { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #333; +} + +.configIcon { + font-size: 16px; + color: #1890ff; +} + +.configDescription { + font-size: 12px; + color: #888; + margin-top: 4px; +} + +// 功能说明列表 +.featureList { + background: #f9f9f9; + border-radius: 8px; + padding: 12px; +} + +.featureItem { + display: flex; + align-items: flex-start; + gap: 8px; + font-size: 13px; + color: #666; + line-height: 1.6; + margin-bottom: 8px; + + &:last-child { + margin-bottom: 0; + } +} + +.featureIcon { + color: #52c41a; + margin-top: 2px; + flex-shrink: 0; +} + +// 调用者名单 +.callerSection { + margin-top: 16px; +} + +.sectionHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.sectionTitle { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + font-weight: 500; + color: #333; +} + +.sectionCount { + font-size: 13px; + color: #888; + font-weight: normal; +} + +.callerList { + background: #f9f9f9; + border-radius: 8px; + padding: 8px; +} + +.callerItem { + display: flex; + align-items: center; + gap: 10px; + padding: 8px; + background: #fff; + border-radius: 6px; + margin-bottom: 6px; + + &:last-child { + margin-bottom: 0; + } +} + +.callerAvatar { + width: 40px; + height: 40px; + border-radius: 50%; + flex-shrink: 0; +} + +.callerInfo { + flex: 1; + min-width: 0; +} + +.callerName { + font-size: 14px; + font-weight: 500; + color: #333; + margin-bottom: 2px; +} + +.callerRole { + font-size: 12px; + color: #888; +} + +.callerTime { + font-size: 11px; + color: #999; + white-space: nowrap; +} + +// 素材列表 +.materialSection { + padding: 12px 16px; +} + +.uploadButton { + width: 100%; + margin-bottom: 16px; +} + +.materialList { + display: flex; + flex-direction: column; + gap: 12px; +} + +.materialItem { + background: #fff; + border-radius: 12px; + padding: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + border: 1px solid #ececec; + display: flex; + align-items: center; + gap: 12px; +} + +.materialIcon { + width: 48px; + height: 48px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + flex-shrink: 0; +} + +.fileIcon { + background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); + color: #fff; +} + +.videoIcon { + background: linear-gradient(135deg, #a855f7 0%, #9333ea 100%); + color: #fff; +} + +.docIcon { + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + color: #fff; +} + +.materialContent { + flex: 1; + min-width: 0; +} + +.materialHeader { + display: flex; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 6px; +} + +.materialName { + font-size: 14px; + font-weight: 500; + color: #333; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + margin-right: 8px; +} + +.materialMenu { + font-size: 16px; + color: #888; + cursor: pointer; + padding: 2px; + flex-shrink: 0; +} + +.materialMeta { + display: flex; + align-items: center; + gap: 12px; + font-size: 12px; + color: #888; +} + +.materialSize { + display: flex; + align-items: center; + gap: 4px; +} + +.materialDate { + display: flex; + align-items: center; + gap: 4px; +} + +.materialTags { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 6px; +} + +.materialTag { + padding: 2px 8px; + border-radius: 8px; + font-size: 11px; + background: rgba(0, 0, 0, 0.05); + color: #666; +} + +// 底部按钮组 +.bottomActions { + display: flex; + gap: 12px; + padding: 16px; + background: #fff; + border-top: 1px solid #f0f0f0; +} + +.actionButton { + flex: 1; + padding: 12px; + border: 1px solid #d9d9d9; + border-radius: 8px; + background: #fff; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; + + &:active { + opacity: 0.7; + } +} + +.editButton { + color: #1890ff; + border-color: #1890ff; +} + +.deleteButton { + color: #ff4d4f; + border-color: #ff4d4f; +} + +// 空状态 +.empty { + text-align: center; + padding: 60px 20px; + color: #bbb; +} + +.emptyIcon { + font-size: 64px; + color: #d9d9d9; + margin-bottom: 16px; +} + +.emptyText { + font-size: 14px; + color: #999; +} + +// 编辑提示词弹窗 +.promptEditModal { + .promptTextarea { + width: 100%; + min-height: 200px; + padding: 12px; + border: 1px solid #d9d9d9; + border-radius: 8px; + font-size: 14px; + line-height: 1.6; + resize: vertical; + font-family: inherit; + + &:focus { + outline: none; + border-color: #1890ff; + } + } + + .promptHint { + font-size: 12px; + color: #888; + margin-top: 8px; + line-height: 1.5; + } +} diff --git a/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/index.tsx b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/index.tsx new file mode 100644 index 00000000..4d2705ed --- /dev/null +++ b/Cunkebao/src/pages/mobile/workspace/ai-knowledge/detail/index.tsx @@ -0,0 +1,648 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { + Button, + Switch, + message, + Spin, + Dropdown, + Modal, + Input, + Upload, +} from "antd"; +import { + BookOutlined, + CheckCircleOutlined, + UserOutlined, + UploadOutlined, + FileOutlined, + VideoCameraOutlined, + FileTextOutlined, + MoreOutlined, + EditOutlined, + DeleteOutlined, + SettingOutlined, + ApiOutlined, + BulbOutlined, + CalendarOutlined, + DatabaseOutlined, +} from "@ant-design/icons"; +import Layout from "@/components/Layout/Layout"; +import NavCommon from "@/components/NavCommon"; +import style from "./index.module.scss"; +import { + getKnowledgeBaseDetail, + getMaterialList, + deleteMaterial, + updateKnowledgeBaseConfig, + uploadMaterial, +} from "./api"; +import { deleteKnowledgeBase } from "../list/api"; +import type { KnowledgeBase, Material, Caller } from "./data"; + +type TabType = "info" | "materials"; + +const AIKnowledgeDetail: React.FC = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [activeTab, setActiveTab] = useState("info"); + const [loading, setLoading] = useState(false); + const [knowledgeBase, setKnowledgeBase] = useState( + null, + ); + const [materials, setMaterials] = useState([]); + const [callers, setCallers] = useState([]); + const [promptEditVisible, setPromptEditVisible] = useState(false); + const [independentPrompt, setIndependentPrompt] = useState(""); + + useEffect(() => { + if (id) { + fetchDetail(); + } + }, [id]); + + const fetchDetail = async () => { + if (!id) return; + setLoading(true); + try { + const detail = await getKnowledgeBaseDetail(Number(id)); + setKnowledgeBase(detail); + setCallers(detail.callers || []); + setIndependentPrompt(detail.independentPrompt || ""); + + // 获取素材列表 + const materialRes = await getMaterialList({ + knowledgeBaseId: Number(id), + page: 1, + limit: 100, + }); + + // 转换素材数据格式 + const transformedMaterials = (materialRes.list || []).map( + (item: any) => ({ + ...item, + fileName: item.name, + tags: item.label || [], + filePath: item.fileUrl, + uploadTime: item.createTime + ? new Date(item.createTime * 1000).toLocaleDateString("zh-CN") + : "-", + uploaderId: item.userId, + fileType: item.name?.split(".").pop() || "file", + fileSize: 0, // 接口未返回,需要前端计算或后端补充 + }), + ); + + setMaterials(transformedMaterials); + + // 更新知识库的素材数量 + if (detail) { + setKnowledgeBase({ + ...detail, + materialCount: transformedMaterials.length, + }); + } + } catch (error) { + message.error("获取详情失败"); + navigate(-1); + } finally { + setLoading(false); + } + }; + + const handleAICallToggle = async (checked: boolean) => { + if (!id || !knowledgeBase) return; + + // 系统预设不允许修改 + if (knowledgeBase.type === 0) { + message.warning("系统预设知识库不可修改"); + return; + } + + try { + await updateKnowledgeBaseConfig({ + id: Number(id), + name: knowledgeBase.name, + description: knowledgeBase.description, + label: knowledgeBase.tags || knowledgeBase.label || [], + aiCallEnabled: checked, + useIndependentPrompt: knowledgeBase.useIndependentPrompt, + independentPrompt: knowledgeBase.independentPrompt || "", + }); + message.success(checked ? "已启用AI调用" : "已关闭AI调用"); + setKnowledgeBase(prev => + prev ? { ...prev, aiCallEnabled: checked } : null, + ); + } catch (error) { + message.error("操作失败"); + } + }; + + const handleIndependentPromptToggle = async (checked: boolean) => { + if (!id || !knowledgeBase) return; + + // 系统预设不允许修改 + if (knowledgeBase.type === 0) { + message.warning("系统预设知识库不可修改"); + return; + } + + if (checked) { + // 启用时打开编辑弹窗 + setPromptEditVisible(true); + } else { + // 禁用时直接更新 + try { + await updateKnowledgeBaseConfig({ + id: Number(id), + name: knowledgeBase.name, + description: knowledgeBase.description, + label: knowledgeBase.tags || knowledgeBase.label || [], + useIndependentPrompt: false, + independentPrompt: "", + }); + message.success("已关闭独立提示词"); + setKnowledgeBase(prev => + prev + ? { + ...prev, + useIndependentPrompt: false, + independentPrompt: "", + prompt: null, + } + : null, + ); + } catch (error) { + message.error("操作失败"); + } + } + }; + + const handlePromptSave = async () => { + if (!id || !knowledgeBase) return; + if (!independentPrompt.trim()) { + message.error("请输入提示词内容"); + return; + } + + try { + await updateKnowledgeBaseConfig({ + id: Number(id), + name: knowledgeBase.name, + description: knowledgeBase.description, + label: knowledgeBase.tags || knowledgeBase.label || [], + useIndependentPrompt: true, + independentPrompt: independentPrompt.trim(), + }); + message.success("保存成功"); + setKnowledgeBase(prev => + prev + ? { + ...prev, + useIndependentPrompt: true, + independentPrompt: independentPrompt.trim(), + prompt: independentPrompt.trim(), + } + : null, + ); + setPromptEditVisible(false); + } catch (error) { + message.error("保存失败"); + } + }; + + const handleDeleteKnowledge = async () => { + if (!id || !knowledgeBase) return; + + // 系统预设不允许删除 + if (knowledgeBase.type === 0) { + message.warning("系统预设知识库不可删除"); + return; + } + + Modal.confirm({ + title: "确认删除", + content: "删除后数据无法恢复,确定要删除该知识库吗?", + okText: "确定", + cancelText: "取消", + okButtonProps: { danger: true }, + onOk: async () => { + try { + await deleteKnowledgeBase(Number(id)); + message.success("删除成功"); + navigate(-1); + } catch (error) { + message.error("删除失败"); + } + }, + }); + }; + + const handleDeleteMaterial = async (materialId: number) => { + Modal.confirm({ + title: "确认删除", + content: "确定要删除该素材吗?", + okText: "确定", + cancelText: "取消", + okButtonProps: { danger: true }, + onOk: async () => { + try { + await deleteMaterial(materialId); + message.success("删除成功"); + setMaterials(prev => prev.filter(m => m.id !== materialId)); + } catch (error) { + message.error("删除失败"); + } + }, + }); + }; + + const handleUpload = async (file: File) => { + if (!id) return; + + try { + // 注意:这里需要先上传文件获取 fileUrl + // 实际项目中应该有单独的文件上传接口 + // 这里暂时使用占位实现 + message.loading("正在上传文件...", 0); + + // TODO: 调用文件上传接口获取 fileUrl + // const fileUrl = await uploadFile(file); + + // 临时方案:直接使用文件名作为占位 + const fileUrl = `temp://${file.name}`; + + await uploadMaterial({ + typeId: Number(id), + name: file.name, + label: [], // 可以后续添加标签编辑功能 + fileUrl: fileUrl, + }); + + message.destroy(); + message.success("上传成功"); + fetchDetail(); + } catch (error) { + message.destroy(); + message.error("上传失败"); + } + }; + + const getFileIcon = (fileType: string) => { + const type = fileType.toLowerCase(); + if (["mp4", "avi", "mov", "wmv"].includes(type)) { + return ( +
+ +
+ ); + } else if (["doc", "docx", "pdf", "txt"].includes(type)) { + return ( +
+ +
+ ); + } else { + return ( +
+ +
+ ); + } + }; + + const formatFileSize = (bytes: number) => { + if (bytes < 1024) return bytes + " B"; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; + return (bytes / (1024 * 1024)).toFixed(1) + " MB"; + }; + + const renderInfoTab = () => { + if (!knowledgeBase) return null; + + const isSystemPreset = knowledgeBase.type === 0; // 系统预设只读 + + return ( + <> +
+
+
+ +
+
+
+ {knowledgeBase.name} + {isSystemPreset && ( + + (系统预设) + + )} +
+ {knowledgeBase.description && ( +
+ {knowledgeBase.description} +
+ )} +
+
+ +
+
+
+ {knowledgeBase.materialCount || 0} +
+
素材总数
+
+
+
+ {knowledgeBase.aiCallEnabled ? "启用" : "关闭"} +
+
AI状态
+
+
+
+ {knowledgeBase.tags?.length || 0} +
+
标签数
+
+
+ + {knowledgeBase.tags && knowledgeBase.tags.length > 0 && ( +
+
内容库标签
+
+ {knowledgeBase.tags.map((tag, index) => ( + + {tag} + + ))} +
+
+ )} + +
+
+
+
+ + AI调用配置 +
+
+ AI助手可以使用此内容库的素材 +
+
+ +
+ +
+
+
+ + 支持智能应答和推荐 +
+
+
+ +
+
+
+ + 实时响应用户查询 +
+
+
+
+ +
+
+ + 与各知识库的回复基本合用 +
+
+ + 确保所有知识库回复的一致性和专业度 +
+
+ + {callers.length > 0 && ( +
+
+
+ + 调用者名单 + {callers.length} +
+
+
+ {callers.slice(0, 3).map(caller => ( +
+ {caller.name} +
+
{caller.name}
+
{caller.role}
+
+
+ 调用 {caller.callCount} 次 · {caller.lastCallTime} +
+
+ ))} +
+
+ )} +
+ + {/* 系统预设不显示编辑和删除按钮 */} + {!isSystemPreset && ( +
+ + +
+ )} + + ); + }; + + const renderMaterialsTab = () => { + const isSystemPreset = knowledgeBase?.type === 0; // 系统预设只读 + + return ( +
+ {/* 系统预设不显示上传按钮 */} + {!isSystemPreset && ( + { + handleUpload(file); + return false; + }} + > + + + )} + +
+ {materials.length > 0 ? ( + materials.map(material => ( +
+ {getFileIcon(material.fileType)} +
+
+
+ {material.fileName} +
+ {/* 系统预设不显示删除按钮 */} + {!isSystemPreset && ( + , + label: "删除", + danger: true, + }, + ], + onClick: () => handleDeleteMaterial(material.id), + }} + trigger={["click"]} + placement="bottomRight" + > + + + )} +
+
+
+ + {formatFileSize(material.fileSize)} +
+
+ + {material.uploadTime} +
+
+ {material.tags && material.tags.length > 0 && ( +
+ {material.tags.map((tag, index) => ( + + {tag} + + ))} +
+ )} +
+
+ )) + ) : ( +
+
+ +
+
暂无素材
+
+ )} +
+
+ ); + }; + + return ( + + navigate("/workspace/ai-knowledge")} + /> +
+
+ + +
+
+ + } + > +
+ {loading ? ( +
+ +
+ ) : ( + <> + {activeTab === "info" && renderInfoTab()} + {activeTab === "materials" && renderMaterialsTab()} + + )} +
+ + {/* 编辑独立提示词弹窗 */} + setPromptEditVisible(false)} + onOk={handlePromptSave} + okText="保存" + cancelText="取消" + className={style.promptEditModal} + > +