From 9e0cb3f27a2d07ee77de03a9d834436d42e7c398 Mon Sep 17 00:00:00 2001 From: Disciple_C Date: Wed, 21 Oct 2026 17:27:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=90=E8=90=A5=E5=B9=B3=E5=8F=B0=20?= =?UTF-8?q?ai=20=E5=8F=A3=E5=AD=90=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 4 +- AI平台运营对接接口清单.md | 328 ++++++++ src/assets/styles/pageLayout.module.less | 26 + src/config/fallbackRoutes.ts | 9 + .../components/AiAgentEditModal.module.less | 21 + .../ai-agent/components/AiAgentEditModal.tsx | 187 +++++ .../components/AiModelDict.module.less | 277 ++++++ .../ai-agent/components/AiModelDict.tsx | 791 ++++++++++++++++++ .../components/BlacklistAddModal.module.less | 21 + .../ai-agent/components/BlacklistAddModal.tsx | 159 ++++ .../ai-agent/components/BlacklistTab.tsx | 162 ++++ .../components/PointsAdjustModal.module.less | 21 + .../ai-agent/components/PointsAdjustModal.tsx | 167 ++++ .../system/ai-agent/components/PointsTab.tsx | 308 +++++++ .../system/ai-agent/hooks/useUserSearch.ts | 119 +++ src/pages/system/ai-agent/index.tsx | 117 +++ src/pages/system/ai-agent/model/config.ts | 59 ++ src/pages/system/ai-agent/model/services.ts | 352 ++++++++ .../ai-agent/model/useAiAgentColumns.ts | 93 ++ .../system/ai-agent/model/useAiAgentModel.ts | 87 ++ .../ai-agent/model/useBlacklistColumns.ts | 99 +++ .../ai-agent/model/useBlacklistModel.ts | 162 ++++ .../system/ai-agent/model/usePointsColumns.ts | 197 +++++ .../system/ai-agent/model/usePointsModel.ts | 241 ++++++ src/utils/routeIcon.tsx | 2 + 25 files changed, 4007 insertions(+), 2 deletions(-) create mode 100644 AI平台运营对接接口清单.md create mode 100644 src/pages/system/ai-agent/components/AiAgentEditModal.module.less create mode 100644 src/pages/system/ai-agent/components/AiAgentEditModal.tsx create mode 100644 src/pages/system/ai-agent/components/AiModelDict.module.less create mode 100644 src/pages/system/ai-agent/components/AiModelDict.tsx create mode 100644 src/pages/system/ai-agent/components/BlacklistAddModal.module.less create mode 100644 src/pages/system/ai-agent/components/BlacklistAddModal.tsx create mode 100644 src/pages/system/ai-agent/components/BlacklistTab.tsx create mode 100644 src/pages/system/ai-agent/components/PointsAdjustModal.module.less create mode 100644 src/pages/system/ai-agent/components/PointsAdjustModal.tsx create mode 100644 src/pages/system/ai-agent/components/PointsTab.tsx create mode 100644 src/pages/system/ai-agent/hooks/useUserSearch.ts create mode 100644 src/pages/system/ai-agent/index.tsx create mode 100644 src/pages/system/ai-agent/model/config.ts create mode 100644 src/pages/system/ai-agent/model/services.ts create mode 100644 src/pages/system/ai-agent/model/useAiAgentColumns.ts create mode 100644 src/pages/system/ai-agent/model/useAiAgentModel.ts create mode 100644 src/pages/system/ai-agent/model/useBlacklistColumns.ts create mode 100644 src/pages/system/ai-agent/model/useBlacklistModel.ts create mode 100644 src/pages/system/ai-agent/model/usePointsColumns.ts create mode 100644 src/pages/system/ai-agent/model/usePointsModel.ts diff --git a/.env.development b/.env.development index 03e2369..1a56665 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,6 @@ # 开发环境配置 # 基础API地址 -VITE_API_BASE_URL=http://cpms.op.nps.yunvip123.cn/cpms - +# VITE_API_BASE_URL=http://cpms.op.nps.yunvip123.cn/cpms +VITE_API_BASE_URL=http://192.168.2.166:8080/cpms # 是否开启本地调试 LOCAL_DEBUG=true diff --git a/AI平台运营对接接口清单.md b/AI平台运营对接接口清单.md new file mode 100644 index 0000000..baa6928 --- /dev/null +++ b/AI平台运营对接接口清单.md @@ -0,0 +1,328 @@ +# AI 平台运营对接接口清单(给前端) + +> 版本:v1.0(2026-10-15) 后端模块:cpms-operations-platform(运营管理端) +> 后端路由前缀:`admin/manager/ai` 鉴权:角色 `coo` + `ai:*` 权限(Shiro) +> 状态说明:✅=后端已有、前端待对接;🚧=后端缺口(需后端开发后再给前端对接) + +--- + +## 1. 已有接口(✅ 后端已实现,前端可直接对接) + +### 1.1 AI 场景配置(ai_agent_config) + +运营实时配置:场景开关 / 计费开关 / 倍率 / 每日免费额。改动**实时生效**(无缓存)。 + +#### ① 场景配置列表 + +- `GET /admin/manager/ai/agent/list` +- 权限:`ai.agent.view` +- 参数:无 +- 返回:`Result>` + +```json +[ + { + "id": 1, + "bizType": "matchImport", // 业务类型(唯一键) + "name": "对阵导入助手", // 展示名 + "enabled": 1, // 业务开关 1/0(0=前端隐藏入口、会话创建被拒) + "billingEnabled": 1, // 计费开关 1/0(0=该场景免费) + "billingMultiplier": 1.0, // 计费倍率(>0,促销可调) + "dailyFreePoints": 10, // 每日免费额度(口径=每日免费结算条数;0=无免费额) + "descr": "运营备注" + } +] +``` + +#### ② 更新场景配置 + +- `POST /admin/manager/ai/agent/update` +- 权限:`ai.agent.edit`(操作留痕 LogOperation) +- Body(JSON,id 必填,其余传了才更新): + +```json +{ + "id": 1, + "enabled": 1, + "billingEnabled": 1, + "billingMultiplier": 1.5, + "dailyFreePoints": 20, + "descr": "双11促销" +} +``` + +- 校验:倍率必须 >0;免费额 ≥0 +- 返回:`Result`("配置已更新") +- 🚧 注意:**目前没有"新增/删除场景配置"接口**(bizType 由代码注册,见 §3.2) + +### 1.2 积分账户与流水(ai_points_account / ai_points_transaction) + +#### ③ 账户分页 + +- `GET /admin/manager/ai/points/accounts` +- 权限:`ai.points.view` +- Query:`page`(默认1)、`size`(默认10)、`userId`(可选) +- 返回:`Result>`,行含 `userId / points(余额,可为负=欠款) / totalRecharged / totalConsumed` +- 口径说明:**筛选目前仅支持 userId**(接口注解里的"手机号筛选"未实现,勿按手机号出筛选项;如需后端补关联查询另行排期) +- 账户产生时机:初始表为空属正常态——用户**首次对话预检/结算或首次被调点**时自动建户(余额 0 起户) + +#### ④ 手工调点(充值/纠错) + +- `POST /admin/manager/ai/points/adjust` +- 权限:`ai.points.adjust`(留痕) +- **自动建户**:目标用户无账户时自动创建(0 余额起户后再原子加点,并发安全)——无需先引导用户消费 +- ⚠️ **userId 不校验真实性**:后端不校验 userId 是否存在于用户表,输错会静默创建孤儿账户—— + 前端调点弹窗务必做成**用户选择器**(按 userId/昵称搜索选中回填),不要裸文本输入 +- Body: + +```json +{ + "userId": 123, // 必填 + "points": 100, // 必填,非0;正=充/赠,负=扣 + "type": 1, // 可选 1=充值/赠送 2=调账;缺省按正负推导 + "descr": "客诉补偿" // 可选,审计备注 +} +``` + +- 返回:`Result`("调点成功") + +#### ⑤ 流水分页 + +- `GET /admin/manager/ai/points/transactions` +- 权限:`ai.points.view` +- Query:`page`、`size`、`userId`、`bizType`(场景)、`type`(1充值/2调账/3消费/4欠款)、`date`(yyyy-MM-dd) +- 返回:`Result>`,行含 `userId / bizType / type / changePoints / inputTokens / outputTokens / model / messageId / freeFlag / descr / createDate` + +### 1.3 AI 使用黑名单(✅ 后端已实现,前端可直接对接) + +拉黑期间禁止用户使用 AI 平台能力(对话三助手 + 对阵导入)。生效口径:`status=1` 且(未设到期时间或未到期);**解除不物理删**(status 置 0 保留历史),再次拉黑原地置回。拉黑**即时生效**(入口逐请求查库,无缓存)。 + +#### ⑪ 黑名单分页 + +- `GET /admin/manager/ai/blacklist/list` +- 权限:`ai.blacklist.view` +- Query:`page`(默认1)、`size`(默认10)、`userId`(可选)、`status`(可选 1/0) +- 返回:`Result>` + +```json +[ + { + "id": 1, + "userId": 123, + "reason": "恶意刷 AI(敏感信息预警 3 次)", + "status": 1, + "expireDate": "2026-11-15 00:00:00", // null=永久 + "operatorId": 88, + "createDate": "...", + "updateDate": "..." + } +] +``` + +#### ⑫ 拉黑 + +- `POST /admin/manager/ai/blacklist/add` +- 权限:`ai.blacklist.edit`(留痕) +- Body: + +```json +{ + "userId": 123, // 必填 + "reason": "恶意刷 AI", // 必填(审计要求) + "expireDate": "2026-11-15 00:00:00" // 可选(yyyy-MM-dd[ HH:mm:ss]);空=永久;必须晚于当前 +} +``` + +- 返回:`Result`("已拉黑(对话与导入入口即时拦截)") +- 前端建议同调点:userId 用**用户选择器**选中回填,不要裸文本输入 + +#### ⑬ 解除拉黑 + +- `POST /admin/manager/ai/blacklist/remove` +- 权限:`ai.blacklist.edit`(留痕) +- Body:`{"userId": 123}`;幂等(无拉黑记录也返回成功) +- 返回:`Result`("已解除拉黑") + +**被拦截用户看到的表现**(前端可据此定制文案): + +- 创建/续接会话:HTTP 403 JSON(`Result.error`,msg="您已被限制使用 AI 功能,如有疑问请联系管理员") +- 对话 SSE:`error` 事件 code=403,同文案 +- 导入(建任务/接管/提取/矩阵学习/提交):HTTP 500 JSON 业务异常,同文案 + +### 1.4 报表 + +#### ⑥ 计费报表(按日按场景汇总) + +- `GET /admin/manager/ai/billing/report` +- 权限:`ai.points.view` +- Query:`startDate`、`endDate`(均可选,yyyy-MM-dd) +- 返回行:`{ date, bizType, messageCount, totalTokens, totalPoints }`(按日期降序;消费+欠款流水聚合) + +#### ⑦ 直解格式统计报表 + +- `GET /admin/manager/ai/freeform/report?top=50` +- 权限:`ai.points.view` +- 返回行:`{ fingerprint, docNature, signalSet, headerSignature, useCount, lastUsedAt, highFrequency }` + (`highFrequency`=月频次≥5 的文档指纹,确定性解析器需求评估依据) + +--- + +## 2. 数据字典 ai_agent_model(🚧 运营平台**未对接**,当前直接改库) + +### 2.1 现状(重要) + +模型/网关/Key 的运行时热切换依赖**系统数据字典** `sys_dict_type` / `sys_dict_data`: + +- `sys_dict_type.dict_type = 'ai_agent_model'`(取第一条该类型的 id) +- `sys_dict_data`:`dict_type_id` 关联上表,`dict_label → dict_value` 键值对,同 label 按 `sort` 升序取第一条 +- **后端无任何字典维护接口**(全仓库无 SysDictType/DictData Controller),运营平台也没维护页——目前只能 DBA 直接改表 +- 生效机制:后端本地 **30s TTL 缓存**,改库后最长 30s 生效,无需重启 + +### 2.2 字典键值契约(维护页按此渲染表单) + +label 即配置键,value 即配置值。三个用途组,组内未配置的维度按「用途组 → 全局默认字典 → 配置文件 cpms.ai.*」回落: + +| label(dict_label) | value(dict_value) | 说明 | +| ------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `model` | 如 `deepseek-flash` | **全局默认模型**(对话/排赛/导入助手+流式:未单独配置用途组的助手均回落此项) | +| `baseUrl` | OpenAI 兼容网关地址 | 全局默认网关;缺省时按模型取内置映射(deepseek-flash→api.deepseek.com、qwen3.7-flash→maas.qianwenaiapi.com),再回落配置文件 | +| `apiKey` | sk-xxx | 全局默认 API Key(**敏感**,前端展示需脱敏/改写场景) | +| `thinking` | on / off / default | 全局思考模式(按厂商自动映射参数,见模型配置指南 §5);default=跟随网关默认 | +| `steward.model` | 同上 | 赛事管家模型(2026-10-16 分离:管家以查询问答为主,可配轻量高并发模型);整组未配置时回落全局默认 | +| `steward.baseUrl` | 同上 | 赛事管家网关 | +| `steward.apiKey` | 同上 | 赛事管家 Key | +| `steward.thinking` | 同 thinking | 管家思考模式;未配置回落全局 `thinking` | +| `extract.model` | 同上 | 提取组模型(导入直解提取+诊断);整组未配置时回落全局默认 | +| `extract.baseUrl` | 同上 | 提取组网关 | +| `extract.apiKey` | 同上 | 提取组 Key | +| `extract.thinking` | 同 thinking | 提取组思考模式;未配置回落全局 `thinking` | +| `failover.model` | 同上 | 备用网关模型;**未配置=故障转移功能整体关闭** | +| `failover.baseUrl` | 同上 | 备用网关地址 | +| `failover.apiKey` | 同上 | 备用网关 Key | +| `failover.thinking` | 同 thinking | 备用网关思考模式;未配置回落全局 `thinking` | + +其余运行参数(temperature/超时/maxTokens/maxToolHops/流式开关等)在配置文件 `cpms.ai.*`,不在字典范围。 + +### 2.3 接口(✅ 后端已实现,前端可直接对接) + +#### ⑧ 模型字典-读当前配置 + +- `GET /admin/manager/ai/model/dict` +- 权限:`ai.model.view` +- 参数:无。白名单 16 键**全量返回**(未配置的键 value=null,前端按此渲染完整表单) +- 返回:`Result>` + +```json +[ + { + "label": "extract.apiKey", + "value": "sk-****ab3f", // 敏感键脱敏(前3+****+后4);未配置 null + "sensitive": true, // true=apiKey 类:编辑时"留空=保留原值",禁止回传掩码 + "purpose": "extract", // chat=对话/工具(全局默认) extract=导入提取+诊断 failover=备用网关 + "remark": "提取组 API Key(敏感)" // 表单提示文案 + } +] +``` + +#### ⑨ 模型字典-整组保存(upsert) + +- `POST /admin/manager/ai/model/dict` +- 权限:`ai.model.edit`(留痕;事务性) +- Body: + +```json +{ + "items": [ + { "label": "model", "value": "deepseek-flash" }, + { "label": "extract.apiKey", "value": "" } // 敏感键传空=保留原值 + ] +} +``` + +- 校验:label 必须在白名单 16 键内;`*model` 必填(要回落请用重置);`*baseUrl` 须 http(s) 开头;`*thinking` 取值 on/off/default;**`*apiKey` 留空或回传脱敏掩码(含 `****`)均视为"未修改、保留原值"**——前端可整表单原样提交,无需清空未编辑的 Key 字段 +- 返回:`Result`("已保存(最长 30 秒生效)") + +#### ⑩ 模型字典-重置指定键 + +- `POST /admin/manager/ai/model/dict/reset` +- 权限:`ai.model.edit`(留痕) +- Body:`{"labels": ["extract.model", "failover.apiKey"]}`(须在白名单内) +- 语义:删除字典行,该键回落后一级(用途组 → 全局默认字典 → 配置文件);`failover.model` 重置=故障转移整体关闭 +- 返回:`Result`("已重置 N 项(最长 30 秒生效)") + +**生效机制**:后端本地 30s TTL 缓存,保存/重置后最长 30 秒生效、无需重启。页面保存成功文案建议提示这一点。 + +--- + +## 3. 其它后端缺口(🚧 排期后补充对接) + +### 3.1 AI 审计查询(数据已落库、无运营查询接口) + +运营排查客诉/审计需要,建议新增(只读,权限 `ai.audit.view`): + +| 数据表 | 内容 | 建议接口 | +| -------------------- | ---------------------------------------------------------------------- | ------------------------------------------------- | +| `ai_chat_session` | 会话:userId/bizType/activeAgent/tournamentId/status/model/totalTokens | `GET /admin/manager/ai/chat/sessions`(分页筛选) | +| `ai_chat_message` | 消息审计:sessionId/role/agentCode/content(已脱敏)/tokens | `GET /admin/manager/ai/chat/messages?sessionId=` | +| `ai_chat_card_event` | 卡片事件:cardType/refId/payload/version/delivered | 可并入消息详情或独立查询 | + +### 3.2 场景配置新增/下线 + +`ai_agent_config` 的 bizType 由代码注册(matchImport / tournamentSteward / scheduleAgent 等),目前运营只能改不能增删。若要页面化新增,需后端补 `agent/add` + 启动期注册校验。 + +### 3.3 模型单价维护(✅ 已上线:进数据字典 + 通用字典页维护) + +单价已从配置文件升级为**数据字典热切换**(与模型配置同模式): + +- 字典类型 `ai_billing_price`,label 契约:`{model}.input` / `{model}.output`(单价=点/千 token),以及兜底默认价 `default.input` / `default.output` +- **生效价回落链**:字典 → 配置文件 `cpms.ai.billing.models.{model}.*` → 内置默认(0.8/2.0);改字典最长 30s 生效 +- 读接口:`GET /admin/manager/ai/billing/prices`(权限 `ai.points.view`)——模型集合=字典已配 ∪ 计费流水出现过的模型,返回 `[{model, inputPrice, outputPrice}]`(生效价);漏配模型也在列,便于发现 +- 维护入口:**通用字典管理页**(§2.4)编辑 `ai_billing_price` 类型的数据行即可,无需 AI 专用页面 + +### 2.4 通用字典管理(✅ 已上线:全部 sys_dict 类型可维护,不限 AI) + +独立控制器 `admin/manager/dict/*`,权限 `sys.dict.view` / `sys.dict.edit`(coo 角色): + +| 接口 | 说明 | +| --------------------------- | ------------------------------------------------------------------------------------------------------------- | +| `GET types` | 类型全量列表(含每类型数据行数,sort 升序) | +| `POST type/save` | `{id?, dictType, dictName, remark, sort}`;id 空=新建(dictType 必填且唯一);**dictType 建后不可改** | +| `POST type/delete` | `{id}`;级联删除该类型全部数据行(删 AI 字典=回落配置文件语义) | +| `GET data/list?dictTypeId=` | 该类型数据行(sort 升序) | +| `POST data/save` | `{id?, dictTypeId, dictLabel, dictValue, sort, remark}`;同 type 下 label 唯一(更新排除自身);更新不换 type | +| `POST data/delete` | `{id}`;删除后该键回落消费方下一级配置 | + +返回行结构(VO):类型 `{id, dictType, dictName, remark, sort, dataCount}`;数据 `{id, dictTypeId, dictLabel, dictValue, sort, remark}`。 + +注意事项: + +- 消费方(模型字典/单价/系统其它字典)均有 30s TTL 缓存,保存/删除后最长 30s 生效,页面提示沿用 +- `ai_agent_model` 的 apiKey 类行在本页**明文展示**——敏感场景建议运营走 AI 专用页(`/admin/manager/ai/model/dict`,有脱敏与"留空=保留"保护);通用页保留明文是通用字典的语义(其他系统字典值无敏感问题) + +### 3.4 调试工具(低优先级) + +`/ai/debug/*`(赛程快照/校验/文件下载),仅在 `cpms.ai.debug-tools.enabled=true` 的环境暴露,带 secret 校验,暂无需前端页面。 + +--- + +## 4. 前端菜单与权限配置 + +| 菜单建议 | 权限标识 | 说明 | +| -------------------------- | ----------------------------------------- | -------------------------------------------------------- | +| AI 场景配置 | `ai.agent.view` / `ai.agent.edit` | 已有后端权限点 | +| 积分管理 | `ai.points.view` / `ai.points.adjust` | 已有后端权限点 | +| 计费报表 | `ai.points.view` | 复用 | +| 模型单价(✅ 已上线) | `ai.points.view` | `GET billing/prices`;编辑走通用字典页 | +| 直解报表 | `ai.points.view` | 复用 | +| AI 使用黑名单(✅ 已上线) | `ai.blacklist.view` / `ai.blacklist.edit` | 权限点需在运营平台权限表配置 | +| 通用字典管理(✅ 已上线) | `sys.dict.view` / `sys.dict.edit` | 覆盖全部 sys_dict 类型(含 AI 模型/单价字典) | +| 模型字典(✅ 已上线) | `ai.model.view` / `ai.model.edit` | AI 专用聚合视图(白名单校验/掩码);权限点需在权限表配置 | +| AI 审计(待后端) | `ai.audit.view` | 需后端随接口一并加 | + +角色:以上接口全部要求 `coo` 角色。 + +--- + +## 5. 参考:用户端 AI 接口(非运营平台,前端勿混用) + +用户端(小程序/管理后台用户侧)AI 能力走 `cpms-admin` 的 `/ai/*`:会话 `/ai/session`、对话 SSE `/ai/chat`、历史回放 `/ai/session/messages`、点数 `/ai/points/balance|transactions`、拍板卡 `/ai/draft/*`、转接 `/ai/session/transfer`、对阵导入 `/ai/import/*`。运营平台页面不调用这组接口。 diff --git a/src/assets/styles/pageLayout.module.less b/src/assets/styles/pageLayout.module.less index b5ba3fb..16637dc 100644 --- a/src/assets/styles/pageLayout.module.less +++ b/src/assets/styles/pageLayout.module.less @@ -4,6 +4,32 @@ flex-direction: column; height: 100%; + // Tabs 容器:撑满高度 + .tabs { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + + :global { + .ant-tabs-content-holder { + flex: 1; + min-height: 0; + overflow: hidden; + } + + .ant-tabs-content { + height: 100%; + } + + .ant-tabs-tabpane { + height: 100%; + display: flex; + flex-direction: column; + } + } + } + // 页面筛选区域:横向 flex,可换行 .filter { display: flex; diff --git a/src/config/fallbackRoutes.ts b/src/config/fallbackRoutes.ts index 67eb757..d311830 100644 --- a/src/config/fallbackRoutes.ts +++ b/src/config/fallbackRoutes.ts @@ -173,6 +173,15 @@ export const FALLBACK_MENU_NODES: MenuNode[] = [ icon: 'AiOutlineSetting', permission: 'system.params.view', }, + { + id: 'system_ai_agent', + name: 'AI 场景配置', + path: 'system/ai-agent', + component: 'system/ai-agent', + componentName: 'SystemAiAgent', + icon: 'AiOutlineRobot', + permission: 'system.ai.view', + }, { id: 'system_logs', name: '操作日志', diff --git a/src/pages/system/ai-agent/components/AiAgentEditModal.module.less b/src/pages/system/ai-agent/components/AiAgentEditModal.module.less new file mode 100644 index 0000000..42082e6 --- /dev/null +++ b/src/pages/system/ai-agent/components/AiAgentEditModal.module.less @@ -0,0 +1,21 @@ +.form { + padding-top: 8px; + + :global(.ant-form-item) { + margin-bottom: 16px; + } +} + +.tip { + margin-top: 4px; + font-size: 12px; + color: rgba(0, 0, 0, 0.45); + line-height: 1.5; +} + +.footer { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-bottom: 0; +} diff --git a/src/pages/system/ai-agent/components/AiAgentEditModal.tsx b/src/pages/system/ai-agent/components/AiAgentEditModal.tsx new file mode 100644 index 0000000..25f9429 --- /dev/null +++ b/src/pages/system/ai-agent/components/AiAgentEditModal.tsx @@ -0,0 +1,187 @@ +import { defineComponent, reactive } from 'vue'; +import { Modal, Form, Input, InputNumber, Switch, Button, message } from 'ant-design-vue'; +import { useEffect } from '@/hooks/useEffect'; +import type { AiAgentConfigVO, AiAgentUpdateParams } from '../model/services'; +import { MIN_MULTIPLIER, MAX_FREE_POINTS } from '../model/config'; +import styles from './AiAgentEditModal.module.less'; + +const { TextArea } = Input; + +interface AiAgentEditModalProps { + visible: boolean; + record: AiAgentConfigVO | null; + submitting: boolean; + onClose: () => void; + onSubmit: (params: AiAgentUpdateParams) => void; +} + +const DEFAULT_FORM = { + enabled: false, + billingEnabled: false, + billingMultiplier: 1, + dailyFreePoints: 0, + descr: '', +}; + +/** + * AI 场景配置编辑弹窗 + * + * 字段:业务开关 / 计费开关 / 计费倍率 / 每日免费额度 / 备注 + * 注意:场景名称和业务类型不可编辑(由代码注册) + */ +export default defineComponent({ + name: 'AiAgentEditModal', + props: { + visible: { type: Boolean, default: false }, + record: { type: Object, default: null }, + submitting: { type: Boolean, default: false }, + onClose: { type: Function, default: null }, + onSubmit: { type: Function, default: null }, + }, + setup(props: AiAgentEditModalProps) { + const formState = reactive({ ...DEFAULT_FORM }); + + /** 根据 record 初始化表单(与项目其他弹窗保持一致的写法) */ + const initFormFromRecord = (record: AiAgentConfigVO | null) => { + if (record) { + Object.assign(formState, { + enabled: String(record.enabled) === '1', + billingEnabled: String(record.billingEnabled) === '1', + billingMultiplier: Number(record.billingMultiplier) || 1, + dailyFreePoints: Number(record.dailyFreePoints) || 0, + descr: record.descr || '', + }); + } else { + Object.assign(formState, DEFAULT_FORM); + } + }; + + // 弹窗打开时回填表单(useEffect 内置 immediate,首次挂载也会执行) + useEffect(() => { + if (props.visible) { + initFormFromRecord(props.record); + } + }, [() => props.visible]); + + const handleSubmit = () => { + // 校验 + if (formState.billingMultiplier < MIN_MULTIPLIER) { + message.warning(`计费倍率必须大于 ${MIN_MULTIPLIER}`); + return; + } + if (formState.dailyFreePoints < 0 || formState.dailyFreePoints > MAX_FREE_POINTS) { + message.warning(`每日免费额度范围为 0 ~ ${MAX_FREE_POINTS}`); + return; + } + + // 构造提交参数(对齐文档 POST /admin/manager/ai/agent/update Body 格式) + const params: AiAgentUpdateParams = { + id: props.record!.id, + enabled: formState.enabled ? 1 : 0, + billingEnabled: formState.billingEnabled ? 1 : 0, + billingMultiplier: formState.billingMultiplier, + dailyFreePoints: formState.dailyFreePoints, + descr: formState.descr.trim(), + }; + + props.onSubmit?.(params); + }; + + return () => ( + +
+ {/* 场景信息(只读) */} + + + + + + + + {/* 业务开关 */} + + (formState.enabled = !!checked)} + checkedChildren="开启" + unCheckedChildren="关闭" + /> + + + {/* 计费开关 */} + + + (formState.billingEnabled = !!checked) + } + checkedChildren="计费" + unCheckedChildren="免费" + /> + + + {/* 计费倍率 */} + + + (formState.billingMultiplier = typeof value === 'number' ? value : 1) + } + min={MIN_MULTIPLIER} + step={0.1} + precision={2} + style={{ width: '100%' }} + /> +
倍率必须大于 0,如 1.0 = 原价,1.5 = 1.5 倍计费
+
+ + {/* 每日免费额度 */} + + + (formState.dailyFreePoints = typeof value === 'number' ? value : 0) + } + min={0} + max={MAX_FREE_POINTS} + step={1} + precision={0} + style={{ width: '100%' }} + /> +
每日免费结算条数,0 = 无免费额度
+
+ + {/* 备注 */} + +