Merge remote-tracking branch 'origin/master' into ji
This commit is contained in:
commit
2193c4484b
36 files changed
+1577
-225
No files matched your search
@@ -91,7 +91,7 @@ The frontend (`frontend-web/`) is a React 19 + Vite SPA:
|
||||
- **Shims**: `src/shims/` provides `next/link` and `next/navigation` polyfills (original code was Next.js)
|
||||
- **Login URL params** (`LoginPage.tsx`, frontend-only — the backend never sees these): besides the auth params (`ticket`/`userId`/`env`/`theme`/`yUserId`/`token`/`access_token`/`authToken`/`password`), the page supports a **task deep-link** via `?taskId=&goPath=`. After login it calls the consumer API `GET {VITE_AIPI_CONSUMER_URL}/taskAnalyseSearch/cop-task-detail?taskId=` (header `Authorization: Bearer admin`, auto-injected by `utils/request.ts`), stashes the full `data` (`id/overview/taskDirection/taskName/taskContent`) into `sessionStorage["login.copTaskDetail"]`, then routes by `goPath`: base codes `3qfx`/`rwfx`/`xdfx` → that business's configured **single agent** Q&A inside a **任务工作区** (resolved via `/api/business-mapping`; `resolveTaskDeeplinkTarget` calls `persistTaskContext({taskId, agentId, openingText})` and navigates to the `…/chats` **index**, which opens the latest task conversation or 新建 — a 1:N task↔对话 workspace, NOT `pendingAgentMessage`); the `-A` suffix (`3qfx-A`/…) → **multi-agent roundtable** with `roundtablePrefill.businessCode` so the page auto-selects the configured 业务链条. **xdfx's deep-link `?taskId=` is actually an 行动id, not a 任务id** — `resolveDeeplinkTaskId(goPath, rawId)` (called in `LoginPage` before everything) detects `xdfx`/`xdfx-A` and resolves it via `GET {action_detail_url}/{actionId}` → `data.taskId` (`fetchTaskIdFromAction`; `action_test`+`mock.action_detail` for offline testing); the resolved 任务id then drives `cop-task-detail`, the purpose tree, and the 1:N binding (other businesses pass the id through unchanged). **xdfx additionally partitions its chat records by 行动id (action_id), NOT by task_id**: `resolveTaskDeeplinkTarget` sets `partitionByAction:true` + keeps the raw action_id as `TaskContext.actionId`, so new threads are tagged `metadata.{taskId,actionId}` (taskId kept only to preserve the backend「任务深链对话全局共享」semantics, which keys off `metadata.taskId`) and the conversation list filters by `actionId` (+`agent_id`) — different actions of the same task get separate lists. xdfx's first-question opening text also appends `行动id(action_id)为…` (see `buildOpeningText` / `taskThreadMetadata`). goPath→businessCode: `3qfx`→`3Q`, `rwfx`→`6BF`, `xdfx`→`7BF`. The roundtable target also threads the URL `taskId` into `roundtablePrefill.taskId` (`resolveTaskDeeplinkTarget(goPath, detail, taskId)`), which turns on two taskId-scoped behaviors: (1) **separate, unpartitioned per-task storage** — taskId 会商聊天记录 live in their **own** store/table (`roundtable_task_drafts`), keyed **only by `task_id`** (one taskId = many records, shared across users, **no user 分权** at all; `created_by` is audit-only), kept entirely out of the per-user personal store (`roundtable_drafts`, now strictly per-user) so the two never mix. Backend router `/api/roundtable-task-drafts` (`GET /by-task/{id}` + `/by-task/{id}/latest`, `POST`, `GET/PUT/DELETE /{id}` — all user-agnostic); frontend client `roundtable-planning/api/task-drafts.ts`, and `useDraftPersistence`/`useRoundtableDrafts` route **all** CRUD to it when a `taskId` is present (else the personal `drafts.ts`). The matching **background jobs** are shared by task too: `roundtable_jobs.task_id` marks a task job; reads/dedup/stream/resume/cancel pass `?task_id=` so any user opening the task sees the same running 研讨 (progress/flowchart), and the executor writes the final report back to the task-draft store. Existing rows are moved over by `scripts/migrate_roundtable_task_drafts.py` (migration `20260618_01`). The history dropdown filters to the task and 新建 keeps the binding; (2) **Step-3 auto structured output** — after the `roundtable-summary` agent writes the Markdown report it automatically runs a second round emitting a `flow-json` flowchart (`useStep3Summary` `autoFlow`+`chainLevels`), whose hierarchy follows the business chain's stage goals (任务 → 目的 → 行为体 → …; coordinatorPrompt-specified hierarchy wins) and whose nodes never carry 阶段编号/阶段名 (the prior bug). **Both targets render embedded under the 任务管理 layout** — nested routes under `task-management/<business-path>/{roundtable,agent/:agent_id/chats}` in `StrategyRoutes.tsx` (`<business-path>` = `plan-task-sentiment` / `plan-task/plan-task-fx` / `plan-task/plan-task-action`, matching the COH中任务 menu paths in `sidebar-menu.ts`) so the global sidebar keeps 任务管理 → COH中任务 → the matching business highlighted. **All three single-agent businesses share `TaskFxWorkspace`** (`task-components/TaskFxWorkspace.tsx`): left = a「当前任务」card (`readCopTaskDetail()` 任务名称 + 简介, desensitized, 留存 above 新建对话) + per-task 1:N conversation list (`useTaskThreads(taskId, agentId, actionId?)` — filtered by **both** `metadata.taskId` AND `metadata.agent_id` so the three businesses' conversations staLine truncated
|
||||
- **iframe 嵌入模式 (外部系统用 iframe 套用深链问答/会商)** — 三个**按标签页**开关,全部由深链 URL 参数开启、存进同一份 `sessionStorage` 会话(`embed.session`,键名常量在 `core/embed/embed-session.ts`),跨登录跳转/刷新/页内导航存活,**绝不跨标签污染**同浏览器另开的正常系统页。核心隔离手法:用 `sessionStorage`(按标签页/浏览上下文隔离,iframe 即便同源也有独立一份)而非 `localStorage`(跨标签共享)。开关:(1) **`?embed=1`(可带 `&theme=`)** — 去全局 chrome(`PageLayout` 的 `pageSidebarAllowed && !isEmbedActive()` → 隐藏 Header + PageSidebar)+ 按外部主题色**强制**明/暗:走 next-themes 的 `forcedTheme`(`App.tsx` `AppProviders` 调 `resolveForcedEmbedTheme`,**不写** localStorage `strategy-theme`),离散映射 `dark-blue`/`dark`→深、其余→浅;嵌入侧**绝不**调 `setTheme`/`persistSidebarEnabled`(那俩落 localStorage 跨标签污染,`LoginPage` 据 `readEmbedParam` 跳过)。(2) **`?hideJump=1`** — 隐藏深链底部「快捷跳转」栏 `TaskJumpBar` 的两部分(底部「下一步」等按钮 + 右侧垂直居中悬浮菜单),单智能体问答 + 会商共用该栏,`isJumpBarHidden()` 为真时整条 `return null`。(3) **登录用户隔离** — 嵌入态下鉴权/身份键(`deerflow.auth` token、`userInfo`、`login.*`、`tools_token`、maxkey/userDetail token)全部经 `core/auth/scoped-storage.ts` 的 `authStorage()`(嵌入→sessionStorage / 正常→localStorage)读写,使 iframe 里登录的用户与正常登录用户**完全隔离**、互不覆盖;所有 token 读取都过 `getStoredAuth()`/`getAuthorizationHeaderValue()`(API 客户端 `core/api/*` 全走这俩),改中心函数即让全体调用方自动隔离;跨标签账号变更监听 `AuthSyncWatcher`(`core/auth/sync.tsx`)在嵌入态禁用。嵌入会话在 `main.tsx` 渲染前 `syncEmbedSessionFromCurrentUrl()` 同步启用(早于任何鉴权写入,避免首帧 effect 顺序漏写 localStorage);`AppProviders` 的 effect 再按 SPA 导航同步。`embed=0`/`hideJump=0` 可显式关闭。正常(非嵌入)标签页所有 `isXxx()` 恒 false,行为**零变化**。第三个开关 `?isolate=1`(**独立登录会话**)把同一套**登录用户隔离**单独提供给**普通(带 chrome)标签页**——只切换鉴权存储、不去 chrome / 不强制主题 / 不动跳转栏:`authStorage()` 的条件从「仅嵌入」放宽为 `isEmbedActive() || isIsolatedSession()`,`AuthSyncWatcher` 的跨标签监听对嵌入态**或**独立态都即时忽略(handler 内实时判,运行时开关也即刻生效)。除 URL 参数外,**设置 → 外观 →「登录会话」** 有一个 Switch「独立登录会话(本标签页)」运行时切换(`setIsolatedSession`,`appearance-settings-page.tsx`):**开启时先 `migrateAuthToSession()`**(`scoped-storage.ts`,把 localStorage 的受保护键 `deerflow.auth`/`userInfo`/`userId`/`tools_token` + `login.*` 前缀快照搬进 sessionStorage)再翻标志,故无感不登出;此后本标签页登录态独立——别处登录/换号影响不到它,它换号也影响不到别人,但关掉该标签即结束(需重登)。开启给 toast 提示。
|
||||
- **操作日志(Y-Log)上报** — 用户的关键操作(登录登出/查询/查看/增删改/启停/导入导出/问答等)成功后,**前端直连**外部接口 `POST {write_url}`(默认 `https://ch1.b.uat.4.cn/api/y-log/write`)记一条审计日志。核心模块 `src/core/log/`:`y-log.ts`(`writeYLog(params)` + `OptType` 枚举 0/1/3..12;**异步即发即忘、永不 await、永不抛错、失败静默**,自动把完整 url 转相对路径去 ip/host、对象入参/返回自动 `JSON.stringify` + 50k 裁剪、`optResult` 恒 0 只报成功),`y-log-config.ts`(从公开只读 `GET /api/public/config/y-log` 拉 `{enabled, writeUrl}`,模块级缓存 + 去重 + 默认兜底;`App.tsx` `AppProviders` 启动预热 `fetchYLogConfig()`),`y-log-token.ts`(token1 = token 登录的 `?authToken=`,经 [[scoped-storage]] 的 `authStorage*` 存取,嵌入态自动隔离)。**双门控**:仅当①存在 token1(即用 token 登录,`LoginPage` 成功后 `persistYLogToken(authToken)`,登出 `account-settings-page.tsx` 先报后清 token1)且②配置 `enabled && writeUrl` 才上报;否则一律不发。`optDetail` 统一「B1-CM助手-…」(**禁内部框架名**)。埋点集中在各 API client 的成功路径:`strategy-components/api/{sensitive-words,menu-overrides,task-buttons,skill-addresses,light-apps}.ts`、`roundtable-planning/api/{drafts,task-drafts,roundtable-jobs}.ts`、`core/{agents,scheduled-tasks,knowledge}/api.ts`、`core/api/markdown-docx-export.ts`,以及问答完成钩子(`ChatPage`/`AgentChatPage` 的 `useThreadStream` `onFinish`,用最终汇总文本作 `returnParam`,optType 12)。后端仅新增公开只读配置:`deerflow/config/y_log_config.py`(`YLogConfig{enabled,write_url}`)挂到 `AppConfig.y_log`,`app/gateway/routers/public_config.py` 加 `GET /api/public/config/y-log`,地址写在 `config.yaml` 的 `y_log` 段(mtime 热重载,前端无需重建);**不做后端代理**。Tests:`tests/test_y_log_config.py`。
|
||||
- **操作日志(Y-Log)上报** — 用户的关键操作(登录登出/查询/查看/增删改/启停/导入导出/问答等)成功后,**前端直连**外部接口 `POST {write_url}`(默认 `https://ch1.b.uat.4.cn/api/y-log/write`)记一条审计日志。核心模块 `src/core/log/`:`y-log.ts`(`writeYLog(params)` + `OptType` 枚举 0/1/3..12;**异步即发即忘、永不 await、永不抛错、失败静默**,自动把完整 url 转相对路径去 ip/host、对象入参/返回自动 `JSON.stringify` + 50k 裁剪、`optResult` 恒 0 只报成功),`y-log-config.ts`(从公开只读 `GET /api/public/config/y-log` 拉 `{enabled, writeUrl}`,模块级缓存 + 去重 + 默认兜底;`App.tsx` `AppProviders` 启动预热 `fetchYLogConfig()`),`y-log-token.ts`(token1 = token 登录的 `?authToken=`,经 [[scoped-storage]] 的 `authStorage*` 存取,嵌入态自动隔离)。**双门控**:仅当①存在 token1(即用 token 登录,`LoginPage` 成功后 `persistYLogToken(authToken)`,登出 `account-settings-page.tsx` 先报后清 token1)且②配置 `enabled && writeUrl` 才上报;否则一律不发。`optDetail` 统一「B1-CM助手-…」(**禁内部框架名**)。埋点**广覆盖**各 API client 的成功路径——原则是「一个用户操作(查看/查询/新增/编辑/删除/启停/导入导出)= 一条日志」,每个 API client 的**单条详情 GET(查看→view 4)/ 列表查询 GET(search 3)/ 新增 POST(create 9)/ 改 PUT·PATCH(edit 5)/ 删 DELETE(delete 6)/ 启停(enabled 11 / disabled 10)/ 导入导出(import 7 / export 8)**都各落一条,仅跳过高频 typeahead/状态轮询/纯 bootstrap config 读。覆盖文件:`strategy-components/api/{sensitive-words,menu-overrides,task-buttons,skill-addresses,light-apps,business-mapping}.ts`、`roundtable-planning/api/{drafts,task-drafts,roundtable-jobs,recommend,chains,multi-agent,dashboard-sessions}.ts`、`core/{agents,scheduled-tasks,knowledge,positions,tags,studio,system-settings,memory,mcp,models,custom-prompts,user-preferences,notifications,recommended-questions,user-qa,curator,admin,thread-shares,roundtable-draft-shares,tool-metrics,llm-metrics,note,artifacts}/api.ts`、`core/api/markdown-docx-export.ts`。**问答(Q&A)改为「一次大模型调用 = 一条日志」**:在共享的 `core/threads/hooks.ts` `useThreadStream` 的 `onLangChainEvent` 里监听 `on_chat_model_end`,每次模型调用结束就上报一条(optType 12,`reqParam` 取本次 input 的最后一条 user 文本 + 模型名,`returnParam` 取本次输出文本)——一个问答轮次里 lead agent / 子代理的**每次** LLM 调用都各记一条,覆盖所有走 `useThreadStream` 的页面(`ChatPage`/`AgentChatPage`/创建智能体/iframe/会商等);`ChatPage`/`AgentChatPage` 的 `onFinish` 仍额外保留一条轮次汇总。后端仅新增公开只读配置:`deerflow/config/y_log_config.py`(`YLogConfig{enabled,write_url}`)挂到 `AppConfig.y_log`,`app/gateway/routers/public_config.py` 加 `GET /api/public/config/y-log`,地址写在 `config.yaml` 的 `y_log` 段(mtime 热重载,前端无需重建);**不做后端代理**。Tests:`tests/test_y_log_config.py`。
|
||||
|
||||
### Configuration
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
/** A conversation thread owned by some user (admin view). */
|
||||
export interface AdminThread {
|
||||
@@ -172,22 +173,32 @@ async function parse<T>(res: Response, what: string): Promise<T> {
|
||||
}
|
||||
|
||||
export async function listUserThreads(userId: string): Promise<AdminThread[]> {
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/threads?limit=200`),
|
||||
"Failed to load threads",
|
||||
);
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/threads?limit=200`;
|
||||
const result = await parse<AdminThread[]>(await apiFetch(url), "Failed to load threads");
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-管理后台-查询用户会话列表",
|
||||
url,
|
||||
reqParam: { userId },
|
||||
returnParam: { count: result.length },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function listUserThreadMessages(
|
||||
userId: string,
|
||||
threadId: string,
|
||||
): Promise<AdminMessage[]> {
|
||||
return parse(
|
||||
await apiFetch(
|
||||
`${base()}/${encodeURIComponent(userId)}/threads/${encodeURIComponent(threadId)}/messages`,
|
||||
),
|
||||
"Failed to load messages",
|
||||
);
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/threads/${encodeURIComponent(threadId)}/messages`;
|
||||
const result = await parse<AdminMessage[]>(await apiFetch(url), "Failed to load messages");
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-管理后台-查看会话消息",
|
||||
url,
|
||||
reqParam: { userId, threadId },
|
||||
returnParam: { count: result.length },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function listUserQa(
|
||||
@@ -200,19 +211,30 @@ export async function listUserQa(
|
||||
});
|
||||
const query = (params.q ?? "").trim();
|
||||
if (query) search.set("q", query);
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/qa?${search}`),
|
||||
"Failed to load user Q&A",
|
||||
);
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/qa?${search}`;
|
||||
const result = await parse<AdminUserQaPageResponse>(await apiFetch(url), "Failed to load user Q&A");
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-管理后台-查询用户问答",
|
||||
url,
|
||||
reqParam: { userId, ...params },
|
||||
returnParam: { total: result.total, count: result.items.length },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getUserMemory(
|
||||
userId: string,
|
||||
): Promise<AdminMemoryResponse> {
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/memory`),
|
||||
"Failed to load memory",
|
||||
);
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/memory`;
|
||||
const result = await parse<AdminMemoryResponse>(await apiFetch(url), "Failed to load memory");
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-管理后台-查看用户记忆",
|
||||
url,
|
||||
reqParam: { userId },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getUserLeaderboard(
|
||||
@@ -229,10 +251,15 @@ export async function getUserLeaderboard(
|
||||
until: range.until,
|
||||
limit: String(limit),
|
||||
});
|
||||
return parse(
|
||||
await apiFetch(`${base()}/leaderboard?${params}`),
|
||||
"Failed to load leaderboard",
|
||||
);
|
||||
const url = `${base()}/leaderboard?${params}`;
|
||||
const result = await parse<AdminLeaderboardResponse>(await apiFetch(url), "Failed to load leaderboard");
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-管理后台-查询用户排行榜",
|
||||
url,
|
||||
reqParam: { ...range, limit },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,12 +280,18 @@ export async function refreshTodayLeaderboard(
|
||||
until: range.until,
|
||||
limit: String(limit),
|
||||
});
|
||||
return parse(
|
||||
await apiFetch(`${base()}/leaderboard/refresh-today?${params}`, {
|
||||
method: "POST",
|
||||
}),
|
||||
const url = `${base()}/leaderboard/refresh-today?${params}`;
|
||||
const result = await parse<AdminLeaderboardResponse>(
|
||||
await apiFetch(url, { method: "POST" }),
|
||||
"Failed to refresh today's leaderboard",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-管理后台-刷新今日排行榜",
|
||||
url,
|
||||
reqParam: { ...range, limit },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getUserAnalytics(
|
||||
@@ -272,10 +305,15 @@ export async function getUserAnalytics(
|
||||
until: range.until,
|
||||
limit: String(limit),
|
||||
});
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/analytics?${params}`),
|
||||
"Failed to load user analytics",
|
||||
);
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/analytics?${params}`;
|
||||
const result = await parse<AdminUserAnalyticsResponse>(await apiFetch(url), "Failed to load user analytics");
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-管理后台-查看用户分析",
|
||||
url,
|
||||
reqParam: { userId, ...range, limit },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function generateUserLeaderboardReport(
|
||||
@@ -290,12 +328,19 @@ export async function generateUserLeaderboardReport(
|
||||
limit: String(limit),
|
||||
});
|
||||
if (modelName) params.set("model_name", modelName);
|
||||
return parse(
|
||||
await apiFetch(`${base()}/leaderboard/report?${params}`, {
|
||||
method: "POST",
|
||||
}),
|
||||
const url = `${base()}/leaderboard/report?${params}`;
|
||||
const result = await parse<AdminLeaderboardReportResponse>(
|
||||
await apiFetch(url, { method: "POST" }),
|
||||
"Failed to generate leaderboard report",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-管理后台-生成排行榜报告",
|
||||
url,
|
||||
reqParam: { ...range, limit, modelName },
|
||||
returnParam: { generated_at: result.generated_at, model_name: result.model_name },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseSseFrame(frame: string): AdminLeaderboardReportStreamEvent | null {
|
||||
@@ -335,7 +380,8 @@ export async function streamUserLeaderboardReport({
|
||||
limit: String(limit),
|
||||
});
|
||||
if (modelName) params.set("model_name", modelName);
|
||||
const response = await apiFetch(`${base()}/leaderboard/report/stream?${params}`, {
|
||||
const url = `${base()}/leaderboard/report/stream?${params}`;
|
||||
const response = await apiFetch(url, {
|
||||
method: "POST",
|
||||
signal,
|
||||
});
|
||||
@@ -366,6 +412,12 @@ export async function streamUserLeaderboardReport({
|
||||
const parsed = parseSseFrame(tail);
|
||||
if (parsed) onEvent(parsed);
|
||||
}
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-管理后台-流式生成排行榜报告",
|
||||
url,
|
||||
reqParam: { ...range, limit, modelName },
|
||||
});
|
||||
}
|
||||
|
||||
export async function addAdminMemoryEntry(
|
||||
@@ -373,14 +425,22 @@ export async function addAdminMemoryEntry(
|
||||
content: string,
|
||||
agent: string | null,
|
||||
): Promise<AdminMemoryBucket> {
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/memory/entries`, {
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/memory/entries`;
|
||||
const result = await parse<AdminMemoryBucket>(
|
||||
await apiFetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ content, agent }),
|
||||
}),
|
||||
"Failed to add memory entry",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-管理后台-新增记忆条目",
|
||||
url,
|
||||
reqParam: { userId, content, agent },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function replaceAdminMemoryEntry(
|
||||
@@ -389,14 +449,22 @@ export async function replaceAdminMemoryEntry(
|
||||
content: string,
|
||||
agent: string | null,
|
||||
): Promise<AdminMemoryBucket> {
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/memory/entries`, {
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/memory/entries`;
|
||||
const result = await parse<AdminMemoryBucket>(
|
||||
await apiFetch(url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ old_text: oldText, content, agent }),
|
||||
}),
|
||||
"Failed to replace memory entry",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-管理后台-编辑记忆条目",
|
||||
url,
|
||||
reqParam: { userId, old_text: oldText, content, agent },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function deleteAdminMemoryEntry(
|
||||
@@ -406,13 +474,18 @@ export async function deleteAdminMemoryEntry(
|
||||
): Promise<AdminMemoryBucket> {
|
||||
const params = new URLSearchParams({ old_text: oldText });
|
||||
if (agent) params.set("agent", agent);
|
||||
return parse(
|
||||
await apiFetch(
|
||||
`${base()}/${encodeURIComponent(userId)}/memory/entries?${params}`,
|
||||
{ method: "DELETE" },
|
||||
),
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/memory/entries?${params}`;
|
||||
const result = await parse<AdminMemoryBucket>(
|
||||
await apiFetch(url, { method: "DELETE" }),
|
||||
"Failed to delete memory entry",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-管理后台-删除记忆条目",
|
||||
url,
|
||||
reqParam: { userId, old_text: oldText, agent },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── V1 Memory CRUD ──────────────────────────────────────────────────────────
|
||||
@@ -424,14 +497,22 @@ export async function updateAdminV1Section(
|
||||
summary: string,
|
||||
agent: string | null,
|
||||
): Promise<AdminMemoryBucket> {
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/memory/v1/sections`, {
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/memory/v1/sections`;
|
||||
const result = await parse<AdminMemoryBucket>(
|
||||
await apiFetch(url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ section_type: sectionType, field, summary, agent }),
|
||||
}),
|
||||
"Failed to update V1 section",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-管理后台-编辑记忆段落",
|
||||
url,
|
||||
reqParam: { userId, section_type: sectionType, field, summary, agent },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addAdminV1Fact(
|
||||
@@ -441,14 +522,22 @@ export async function addAdminV1Fact(
|
||||
confidence: number,
|
||||
agent: string | null,
|
||||
): Promise<AdminMemoryBucket> {
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/memory/v1/facts`, {
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/memory/v1/facts`;
|
||||
const result = await parse<AdminMemoryBucket>(
|
||||
await apiFetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ content, category, confidence, agent }),
|
||||
}),
|
||||
"Failed to add V1 fact",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-管理后台-新增记忆事实",
|
||||
url,
|
||||
reqParam: { userId, content, category, confidence, agent },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateAdminV1Fact(
|
||||
@@ -457,14 +546,22 @@ export async function updateAdminV1Fact(
|
||||
updates: { content?: string; category?: string; confidence?: number },
|
||||
agent: string | null,
|
||||
): Promise<AdminMemoryBucket> {
|
||||
return parse(
|
||||
await apiFetch(`${base()}/${encodeURIComponent(userId)}/memory/v1/facts/${encodeURIComponent(factId)}`, {
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/memory/v1/facts/${encodeURIComponent(factId)}`;
|
||||
const result = await parse<AdminMemoryBucket>(
|
||||
await apiFetch(url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ...updates, agent }),
|
||||
}),
|
||||
"Failed to update V1 fact",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-管理后台-编辑记忆事实",
|
||||
url,
|
||||
reqParam: { userId, factId, ...updates, agent },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function deleteAdminV1Fact(
|
||||
@@ -475,11 +572,16 @@ export async function deleteAdminV1Fact(
|
||||
const params = new URLSearchParams();
|
||||
if (agent) params.set("agent", agent);
|
||||
const qs = params.toString() ? `?${params}` : "";
|
||||
return parse(
|
||||
await apiFetch(
|
||||
`${base()}/${encodeURIComponent(userId)}/memory/v1/facts/${encodeURIComponent(factId)}${qs}`,
|
||||
{ method: "DELETE" },
|
||||
),
|
||||
const url = `${base()}/${encodeURIComponent(userId)}/memory/v1/facts/${encodeURIComponent(factId)}${qs}`;
|
||||
const result = await parse<AdminMemoryBucket>(
|
||||
await apiFetch(url, { method: "DELETE" }),
|
||||
"Failed to delete V1 fact",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-管理后台-删除记忆事实",
|
||||
url,
|
||||
reqParam: { userId, factId, agent },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -119,7 +119,15 @@ export async function listAllAgentsAdmin(): Promise<Agent[]> {
|
||||
export async function getAgent(id: string): Promise<Agent> {
|
||||
const res = await apiFetch(`${getBackendBaseURL()}/api/agents/${id}`);
|
||||
if (!res.ok) throw new Error(`Agent '${id}' not found`);
|
||||
return res.json() as Promise<Agent>;
|
||||
const agent = (await res.json()) as Agent;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-智能体-查看",
|
||||
url: `/api/agents/${id}`,
|
||||
reqParam: { id },
|
||||
returnParam: agent,
|
||||
});
|
||||
return agent;
|
||||
}
|
||||
|
||||
export async function createAgent(request: CreateAgentRequest): Promise<Agent> {
|
||||
@@ -183,7 +191,14 @@ export async function deleteAgent(id: string): Promise<void> {
|
||||
export async function getAgentSelector(): Promise<AgentSelectorResponse> {
|
||||
const res = await apiFetch(`${getBackendBaseURL()}/api/agents/selector`);
|
||||
if (!res.ok) throw new Error(`Failed to load agent selector: ${res.statusText}`);
|
||||
return res.json() as Promise<AgentSelectorResponse>;
|
||||
const selector = (await res.json()) as AgentSelectorResponse;
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-智能体-选择器",
|
||||
url: `/api/agents/selector`,
|
||||
returnParam: selector,
|
||||
});
|
||||
return selector;
|
||||
}
|
||||
|
||||
export async function favoriteAgent(id: string): Promise<void> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import { urlOfArtifact } from "./utils";
|
||||
|
||||
@@ -26,7 +27,8 @@ export async function updateArtifactContent({
|
||||
content: string;
|
||||
isMock?: boolean;
|
||||
}) {
|
||||
const res = await apiFetch(urlOfArtifact({ filepath, threadId, isMock }), {
|
||||
const url = urlOfArtifact({ filepath, threadId, isMock });
|
||||
const res = await apiFetch(url, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -36,5 +38,11 @@ export async function updateArtifactContent({
|
||||
if (!res.ok) {
|
||||
throw new Error(await errorMessageFromResponse(res));
|
||||
}
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-产物-编辑产物内容",
|
||||
url,
|
||||
reqParam: { filepath, threadId },
|
||||
});
|
||||
return res.text();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import type {
|
||||
CuratorConfig,
|
||||
@@ -27,12 +28,21 @@ export interface CuratorConfigUpdatePayload {
|
||||
export async function updateCuratorConfig(
|
||||
payload: CuratorConfigUpdatePayload,
|
||||
): Promise<CuratorConfig> {
|
||||
const response = await apiFetch(`${getBackendBaseURL()}/api/curator/config`, {
|
||||
const url = `${getBackendBaseURL()}/api/curator/config`;
|
||||
const response = await apiFetch(url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
return response.json() as Promise<CuratorConfig>;
|
||||
const result = (await response.json()) as CuratorConfig;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-策展-更新配置",
|
||||
url,
|
||||
reqParam: payload,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface RunCuratorResult {
|
||||
@@ -42,13 +52,26 @@ export interface RunCuratorResult {
|
||||
}
|
||||
|
||||
export async function runCurator(): Promise<RunCuratorResult> {
|
||||
const response = await apiFetch(`${getBackendBaseURL()}/api/curator/run`, {
|
||||
method: "POST",
|
||||
const url = `${getBackendBaseURL()}/api/curator/run`;
|
||||
const response = await apiFetch(url, { method: "POST" });
|
||||
const result = (await response.json()) as RunCuratorResult;
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-策展-手动触发策展",
|
||||
url,
|
||||
returnParam: result,
|
||||
});
|
||||
return response.json() as Promise<RunCuratorResult>;
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getCuratorLogs(): Promise<CuratorLogs> {
|
||||
const response = await apiFetch(`${getBackendBaseURL()}/api/curator/logs`);
|
||||
return response.json() as Promise<CuratorLogs>;
|
||||
const url = `${getBackendBaseURL()}/api/curator/logs`;
|
||||
const response = await apiFetch(url);
|
||||
const result = (await response.json()) as CuratorLogs;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-策展-查看运行日志",
|
||||
url,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
/** A user-defined reusable prompt, bound to the account (stored server-side). */
|
||||
export interface CustomPrompt {
|
||||
@@ -27,6 +28,12 @@ async function listUserPrompts(): Promise<CustomPrompt[]> {
|
||||
const res = await apiFetch(baseUrl());
|
||||
if (!res.ok) throw new Error(`Failed to load prompts: ${res.statusText}`);
|
||||
const data = (await res.json()) as { prompts: CustomPrompt[] };
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-自定义提示词-查询",
|
||||
url: "/api/user-prompts",
|
||||
returnParam: data.prompts,
|
||||
});
|
||||
return data.prompts;
|
||||
}
|
||||
|
||||
@@ -44,7 +51,15 @@ async function createUserPrompt(input: {
|
||||
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
||||
throw new Error(err.detail ?? `Failed to create prompt: ${res.statusText}`);
|
||||
}
|
||||
return res.json() as Promise<CustomPrompt>;
|
||||
const created = (await res.json()) as CustomPrompt;
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-自定义提示词-新增",
|
||||
url: "/api/user-prompts",
|
||||
reqParam: input,
|
||||
returnParam: created,
|
||||
});
|
||||
return created;
|
||||
}
|
||||
|
||||
async function updateUserPrompt(
|
||||
@@ -60,7 +75,15 @@ async function updateUserPrompt(
|
||||
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
||||
throw new Error(err.detail ?? `Failed to update prompt: ${res.statusText}`);
|
||||
}
|
||||
return res.json() as Promise<CustomPrompt>;
|
||||
const updated = (await res.json()) as CustomPrompt;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-自定义提示词-编辑",
|
||||
url: `/api/user-prompts/${id}`,
|
||||
reqParam: input,
|
||||
returnParam: updated,
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
async function deleteUserPrompt(id: string): Promise<void> {
|
||||
@@ -71,6 +94,12 @@ async function deleteUserPrompt(id: string): Promise<void> {
|
||||
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
||||
throw new Error(err.detail ?? `Failed to delete prompt: ${res.statusText}`);
|
||||
}
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-自定义提示词-删除",
|
||||
url: `/api/user-prompts/${id}`,
|
||||
reqParam: { id },
|
||||
});
|
||||
}
|
||||
|
||||
/** Reactive list of the current user's saved prompts (server-backed). */
|
||||
|
||||
@@ -56,9 +56,17 @@ export async function listKnowledgeNotes(params: KnowledgeListParams = {}) {
|
||||
}
|
||||
|
||||
export async function getKnowledgeNote(noteId: string) {
|
||||
return parseResponse<KnowledgeNote>(
|
||||
const result = await parseResponse<KnowledgeNote>(
|
||||
await apiFetch(`${baseUrl()}/notes/${encodeURIComponent(noteId)}`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-知识库-查看",
|
||||
url: `${baseUrl()}/notes/${encodeURIComponent(noteId)}`,
|
||||
reqParam: { noteId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function createKnowledgeNote(data: {
|
||||
@@ -254,9 +262,17 @@ export async function resolveNoteWikilinks(noteId: string) {
|
||||
|
||||
/** Fetch a raw vault meta/hub Markdown page (no DB note) by its path. */
|
||||
export async function fetchVaultPage(path: string) {
|
||||
return parseResponse<VaultPage>(
|
||||
const result = await parseResponse<VaultPage>(
|
||||
await apiFetch(`${baseUrl()}/vault-page?path=${encodeURIComponent(path)}`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-知识库-Vault页面查看",
|
||||
url: `${baseUrl()}/vault-page?path=${encodeURIComponent(path)}`,
|
||||
reqParam: { path },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function exportKnowledgeGraph() {
|
||||
@@ -295,6 +311,13 @@ export async function listNoteVersions(noteId: string) {
|
||||
const res = await parseResponse<{ items: KnowledgeVersion[] }>(
|
||||
await apiFetch(`${baseUrl()}/notes/${encodeURIComponent(noteId)}/versions`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-知识库-历史版本列表",
|
||||
url: `${baseUrl()}/notes/${encodeURIComponent(noteId)}/versions`,
|
||||
reqParam: { noteId },
|
||||
returnParam: res.items,
|
||||
});
|
||||
return res.items;
|
||||
}
|
||||
|
||||
@@ -307,23 +330,44 @@ export async function listDuplicates() {
|
||||
const res = await parseResponse<{ groups: DuplicateGroup[] }>(
|
||||
await apiFetch(`${baseUrl()}/duplicates`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-知识库-重复项列表",
|
||||
url: `${baseUrl()}/duplicates`,
|
||||
returnParam: res.groups,
|
||||
});
|
||||
return res.groups;
|
||||
}
|
||||
|
||||
export async function mergeKnowledgeNotes(primaryId: string, duplicateIds: string[]) {
|
||||
return parseResponse<{ note: KnowledgeNote }>(
|
||||
const result = await parseResponse<{ note: KnowledgeNote }>(
|
||||
await apiFetch(`${baseUrl()}/merge`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ primary_id: primaryId, duplicate_ids: duplicateIds }),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-知识库-合并",
|
||||
url: `${baseUrl()}/merge`,
|
||||
reqParam: { primary_id: primaryId, duplicate_ids: duplicateIds },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function reindexKnowledge() {
|
||||
return parseResponse<{ reindexed: number; embedding_enabled: number }>(
|
||||
const result = await parseResponse<{ reindexed: number; embedding_enabled: number }>(
|
||||
await apiFetch(`${baseUrl()}/reindex`, { method: "POST" }),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-知识库-重建索引",
|
||||
url: `${baseUrl()}/reindex`,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface KnowledgeGraph {
|
||||
@@ -332,7 +376,14 @@ export interface KnowledgeGraph {
|
||||
}
|
||||
|
||||
export async function getKnowledgeGraph() {
|
||||
return parseResponse<KnowledgeGraph>(await apiFetch(`${baseUrl()}/graph`));
|
||||
const result = await parseResponse<KnowledgeGraph>(await apiFetch(`${baseUrl()}/graph`));
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-知识库-图谱查看",
|
||||
url: `${baseUrl()}/graph`,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- graph blacklist ---
|
||||
@@ -348,26 +399,48 @@ export async function getGraphBlacklist() {
|
||||
const res = await parseResponse<{ items: GraphBlacklistItem[] }>(
|
||||
await apiFetch(`${baseUrl()}/graph/blacklist`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-知识库-图谱黑名单列表",
|
||||
url: `${baseUrl()}/graph/blacklist`,
|
||||
returnParam: res.items,
|
||||
});
|
||||
return res.items;
|
||||
}
|
||||
|
||||
/** Blacklist node labels (entity names / note titles); they disappear from the graph. */
|
||||
export async function addGraphBlacklist(labels: string[]) {
|
||||
return parseResponse<{ added: number; items: GraphBlacklistItem[] }>(
|
||||
const result = await parseResponse<{ added: number; items: GraphBlacklistItem[] }>(
|
||||
await apiFetch(`${baseUrl()}/graph/blacklist`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ labels }),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-知识库-图谱黑名单新增",
|
||||
url: `${baseUrl()}/graph/blacklist`,
|
||||
reqParam: { labels },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function removeGraphBlacklist(entryId: string) {
|
||||
return parseResponse<{ ok: boolean }>(
|
||||
const result = await parseResponse<{ ok: boolean }>(
|
||||
await apiFetch(`${baseUrl()}/graph/blacklist/${encodeURIComponent(entryId)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-知识库-图谱黑名单删除",
|
||||
url: `${baseUrl()}/graph/blacklist/${encodeURIComponent(entryId)}`,
|
||||
reqParam: { entryId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- folders (user-managed directories) ---
|
||||
@@ -376,6 +449,12 @@ export async function listKnowledgeFolders() {
|
||||
const res = await parseResponse<{ items: KnowledgeFolder[] }>(
|
||||
await apiFetch(`${baseUrl()}/folders`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-知识库-目录列表",
|
||||
url: `${baseUrl()}/folders`,
|
||||
returnParam: res.items,
|
||||
});
|
||||
return res.items;
|
||||
}
|
||||
|
||||
@@ -387,6 +466,13 @@ export async function createKnowledgeFolder(path: string) {
|
||||
body: JSON.stringify({ path }),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-知识库-目录新建",
|
||||
url: `${baseUrl()}/folders`,
|
||||
reqParam: { path },
|
||||
returnParam: res.folder,
|
||||
});
|
||||
return res.folder;
|
||||
}
|
||||
|
||||
@@ -398,16 +484,31 @@ export async function renameKnowledgeFolder(folderId: string, path: string) {
|
||||
body: JSON.stringify({ path }),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-知识库-目录重命名",
|
||||
url: `${baseUrl()}/folders/${encodeURIComponent(folderId)}`,
|
||||
reqParam: { folderId, path },
|
||||
returnParam: res.folder,
|
||||
});
|
||||
return res.folder;
|
||||
}
|
||||
|
||||
export async function deleteKnowledgeFolder(folderId: string, reassignTo?: string) {
|
||||
const qs = reassignTo ? `?reassign_to=${encodeURIComponent(reassignTo)}` : "";
|
||||
return parseResponse<{ ok: boolean }>(
|
||||
const result = await parseResponse<{ ok: boolean }>(
|
||||
await apiFetch(`${baseUrl()}/folders/${encodeURIComponent(folderId)}${qs}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-知识库-目录删除",
|
||||
url: `${baseUrl()}/folders/${encodeURIComponent(folderId)}${qs}`,
|
||||
reqParam: { folderId, reassignTo },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- extraction prompt templates ---
|
||||
@@ -420,6 +521,13 @@ export async function listExtractTemplates(scope?: ExtractTemplateScope, enabled
|
||||
const res = await parseResponse<{ items: KnowledgeExtractTemplate[] }>(
|
||||
await apiFetch(`${baseUrl()}/extract-templates${q ? `?${q}` : ""}`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-知识库-抽取模板列表",
|
||||
url: `${baseUrl()}/extract-templates${q ? `?${q}` : ""}`,
|
||||
reqParam: { scope, enabledOnly },
|
||||
returnParam: res.items,
|
||||
});
|
||||
return res.items;
|
||||
}
|
||||
|
||||
@@ -440,6 +548,13 @@ export async function createExtractTemplate(data: ExtractTemplateInput) {
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-知识库-抽取模板新建",
|
||||
url: `${baseUrl()}/extract-templates`,
|
||||
reqParam: data,
|
||||
returnParam: res.template,
|
||||
});
|
||||
return res.template;
|
||||
}
|
||||
|
||||
@@ -454,13 +569,28 @@ export async function updateExtractTemplate(
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-知识库-抽取模板修改",
|
||||
url: `${baseUrl()}/extract-templates/${encodeURIComponent(templateId)}`,
|
||||
reqParam: { templateId, data },
|
||||
returnParam: res.template,
|
||||
});
|
||||
return res.template;
|
||||
}
|
||||
|
||||
export async function deleteExtractTemplate(templateId: string) {
|
||||
return parseResponse<{ ok: boolean }>(
|
||||
const result = await parseResponse<{ ok: boolean }>(
|
||||
await apiFetch(`${baseUrl()}/extract-templates/${encodeURIComponent(templateId)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-知识库-抽取模板删除",
|
||||
url: `${baseUrl()}/extract-templates/${encodeURIComponent(templateId)}`,
|
||||
reqParam: { templateId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
export interface LlmMetric {
|
||||
id: string;
|
||||
@@ -54,14 +55,21 @@ function buildQuery(filters: LlmMetricsListQuery): string {
|
||||
export async function loadLlmMetrics(
|
||||
query: LlmMetricsListQuery,
|
||||
): Promise<LlmMetricsListResponse> {
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/admin/llm-metrics${buildQuery(query)}`,
|
||||
);
|
||||
const url = `${getBackendBaseURL()}/api/admin/llm-metrics${buildQuery(query)}`;
|
||||
const response = await apiFetch(url);
|
||||
if (!response.ok) {
|
||||
const detail = await response.json().catch(() => ({ detail: response.statusText }));
|
||||
throw new Error(detail.detail ?? "Failed to load LLM metrics");
|
||||
}
|
||||
return (await response.json()) as LlmMetricsListResponse;
|
||||
const result = (await response.json()) as LlmMetricsListResponse;
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-模型指标-查询调用列表",
|
||||
url,
|
||||
reqParam: query,
|
||||
returnParam: { total: result.total, count: result.items.length },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,13 +79,18 @@ export async function loadLlmMetrics(
|
||||
export async function downloadLlmMetricsExcel(
|
||||
filters: LlmMetricsFilters,
|
||||
): Promise<void> {
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/admin/llm-metrics/export${buildQuery(filters)}`,
|
||||
);
|
||||
const requestUrl = `${getBackendBaseURL()}/api/admin/llm-metrics/export${buildQuery(filters)}`;
|
||||
const response = await apiFetch(requestUrl);
|
||||
if (!response.ok) {
|
||||
const detail = await response.json().catch(() => ({ detail: response.statusText }));
|
||||
throw new Error(detail.detail ?? "Failed to export LLM metrics");
|
||||
}
|
||||
writeYLog({
|
||||
optType: OptType.export,
|
||||
optDetail: "B1-CM助手-模型指标-导出Excel",
|
||||
url: requestUrl,
|
||||
reqParam: filters,
|
||||
});
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const filename = (() => {
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import type { MCPConfig } from "./types";
|
||||
|
||||
export async function loadMCPConfig() {
|
||||
const response = await apiFetch(`${getBackendBaseURL()}/api/mcp/config`);
|
||||
return response.json() as Promise<MCPConfig>;
|
||||
const data = (await response.json()) as MCPConfig;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-MCP服务-配置查看",
|
||||
url: "/api/mcp/config",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateMCPConfig(config: MCPConfig) {
|
||||
@@ -16,5 +24,13 @@ export async function updateMCPConfig(config: MCPConfig) {
|
||||
},
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
return response.json();
|
||||
const data = await response.json();
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-MCP服务-配置编辑",
|
||||
url: "/api/mcp/config",
|
||||
reqParam: config,
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { apiFetch } from "../api";
|
||||
import { getBackendBaseURL } from "../config";
|
||||
import { OptType, writeYLog } from "../log/y-log";
|
||||
|
||||
import type {
|
||||
MemorySearchMode,
|
||||
@@ -54,7 +55,15 @@ export async function loadMemoryV2(agent?: string): Promise<MemoryV2Data> {
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/memory/v2${agentQuery(agent)}`,
|
||||
);
|
||||
return readJson<MemoryV2Data>(response, "Failed to fetch memory");
|
||||
const data = await readJson<MemoryV2Data>(response, "Failed to fetch memory");
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-记忆-查看",
|
||||
url: `/api/memory/v2${agentQuery(agent)}`,
|
||||
reqParam: { agent },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function addMemoryEntry(
|
||||
@@ -70,7 +79,15 @@ export async function addMemoryEntry(
|
||||
body: JSON.stringify({ target, content }),
|
||||
},
|
||||
);
|
||||
return readJson<MemoryV2Data>(response, "Failed to add memory entry");
|
||||
const data = await readJson<MemoryV2Data>(response, "Failed to add memory entry");
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-记忆-新增",
|
||||
url: `/api/memory/v2/entries${agentQuery(agent)}`,
|
||||
reqParam: { target, content, agent },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function replaceMemoryEntry(
|
||||
@@ -87,7 +104,15 @@ export async function replaceMemoryEntry(
|
||||
body: JSON.stringify({ target, old_text: oldText, content }),
|
||||
},
|
||||
);
|
||||
return readJson<MemoryV2Data>(response, "Failed to replace memory entry");
|
||||
const data = await readJson<MemoryV2Data>(response, "Failed to replace memory entry");
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-记忆-编辑",
|
||||
url: `/api/memory/v2/entries${agentQuery(agent)}`,
|
||||
reqParam: { target, old_text: oldText, content, agent },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteMemoryEntry(
|
||||
@@ -103,14 +128,29 @@ export async function deleteMemoryEntry(
|
||||
`${getBackendBaseURL()}/api/memory/v2/entries?${params.toString()}`,
|
||||
{ method: "DELETE" },
|
||||
);
|
||||
return readJson<MemoryV2Data>(response, "Failed to delete memory entry");
|
||||
const data = await readJson<MemoryV2Data>(response, "Failed to delete memory entry");
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-记忆-删除",
|
||||
url: `/api/memory/v2/entries?${params.toString()}`,
|
||||
reqParam: { target, old_text: oldText, agent },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
// --- 配置 ---
|
||||
|
||||
export async function loadMemoryConfig(): Promise<MemoryV2ConfigResponse> {
|
||||
const response = await apiFetch(`${getBackendBaseURL()}/api/memory/v2/config`);
|
||||
return readJson<MemoryV2ConfigResponse>(response, "Failed to fetch memory config");
|
||||
const data = await readJson<MemoryV2ConfigResponse>(response, "Failed to fetch memory config");
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-记忆-配置查看",
|
||||
url: "/api/memory/v2/config",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function patchMemoryConfig(
|
||||
@@ -121,7 +161,15 @@ export async function patchMemoryConfig(
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ overrides }),
|
||||
});
|
||||
return readJson<MemoryV2ConfigResponse>(response, "Failed to update memory config");
|
||||
const data = await readJson<MemoryV2ConfigResponse>(response, "Failed to update memory config");
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-记忆-配置编辑",
|
||||
url: "/api/memory/v2/config",
|
||||
reqParam: { overrides },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
// --- Hindsight 检索 ---
|
||||
@@ -139,5 +187,13 @@ export async function searchMemory(
|
||||
body: JSON.stringify({ query, mode }),
|
||||
},
|
||||
);
|
||||
return readJson<MemorySearchResult>(response, "Failed to search memory");
|
||||
const data = await readJson<MemorySearchResult>(response, "Failed to search memory");
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-记忆-检索",
|
||||
url: `/api/memory/v2/search${agentQuery(agent)}`,
|
||||
reqParam: { query, mode, agent },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
import { getBackendBaseURL } from "../config";
|
||||
import { apiFetch } from "../api";
|
||||
import { OptType, writeYLog } from "../log/y-log";
|
||||
|
||||
import type { ModelsResponse } from "./types";
|
||||
|
||||
export async function loadModels(): Promise<ModelsResponse> {
|
||||
const res = await apiFetch(`${getBackendBaseURL()}/api/models`);
|
||||
const data = (await res.json()) as Partial<ModelsResponse>;
|
||||
return {
|
||||
const result: ModelsResponse = {
|
||||
models: data.models ?? [],
|
||||
token_usage: data.token_usage ?? { enabled: false },
|
||||
};
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-模型-查询",
|
||||
url: "/api/models",
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getNoteApiBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import {
|
||||
ensureNoteAuth,
|
||||
@@ -76,7 +77,15 @@ export async function createSearchSpace(
|
||||
throw new Error(`创建笔记失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as SearchSpace;
|
||||
const result = (await response.json()) as SearchSpace;
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-笔记-新建笔记空间",
|
||||
url: "/api/v1/searchspaces",
|
||||
reqParam: { name: input.name, description: input.description ?? "" },
|
||||
returnParam: { id: result.id, name: result.name },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function listSearchSpaces(
|
||||
@@ -90,14 +99,21 @@ export async function listSearchSpaces(
|
||||
order_by: query.order_by ?? "created_at",
|
||||
});
|
||||
|
||||
const response = await noteApiFetch(`/api/v1/searchspaces?${params.toString()}`, {
|
||||
method: "GET",
|
||||
});
|
||||
const path = `/api/v1/searchspaces?${params.toString()}`;
|
||||
const response = await noteApiFetch(path, { method: "GET" });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取笔记列表失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data: unknown = await response.json();
|
||||
return Array.isArray(data) ? (data as SearchSpace[]) : [];
|
||||
const list = Array.isArray(data) ? (data as SearchSpace[]) : [];
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-笔记-查询笔记空间列表",
|
||||
url: path,
|
||||
reqParam: query,
|
||||
returnParam: { count: list.length },
|
||||
});
|
||||
return list;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import type {
|
||||
NotificationItem,
|
||||
@@ -24,6 +25,13 @@ export async function listNotifications(
|
||||
const res = await apiFetch(url);
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to list notifications");
|
||||
const data = (await res.json()) as NotificationListResponse;
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-通知-查询通知列表",
|
||||
url,
|
||||
reqParam: options,
|
||||
returnParam: { count: data.items.length },
|
||||
});
|
||||
return data.items;
|
||||
}
|
||||
|
||||
@@ -35,26 +43,39 @@ export async function getUnreadCount(): Promise<number> {
|
||||
}
|
||||
|
||||
export async function markNotificationRead(id: string): Promise<void> {
|
||||
const res = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/notifications/${encodeURIComponent(id)}/read`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
const url = `${getBackendBaseURL()}/api/notifications/${encodeURIComponent(id)}/read`;
|
||||
const res = await apiFetch(url, { method: "POST" });
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to mark notification read");
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-通知-标记已读",
|
||||
url,
|
||||
reqParam: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function markAllNotificationsRead(): Promise<number> {
|
||||
const res = await apiFetch(`${getBackendBaseURL()}/api/notifications/read_all`, {
|
||||
method: "POST",
|
||||
});
|
||||
const url = `${getBackendBaseURL()}/api/notifications/read_all`;
|
||||
const res = await apiFetch(url, { method: "POST" });
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to mark all read");
|
||||
const data = (await res.json()) as { updated: number };
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-通知-全部标记已读",
|
||||
url,
|
||||
returnParam: data,
|
||||
});
|
||||
return data.updated;
|
||||
}
|
||||
|
||||
export async function deleteNotification(id: string): Promise<void> {
|
||||
const res = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/notifications/${encodeURIComponent(id)}`,
|
||||
{ method: "DELETE" },
|
||||
);
|
||||
const url = `${getBackendBaseURL()}/api/notifications/${encodeURIComponent(id)}`;
|
||||
const res = await apiFetch(url, { method: "DELETE" });
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to delete notification");
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-通知-删除通知",
|
||||
url,
|
||||
reqParam: { id },
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import type {
|
||||
Position,
|
||||
@@ -24,6 +25,12 @@ export async function listPositions(): Promise<Position[]> {
|
||||
const res = await apiFetch(base());
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to list positions");
|
||||
const data = (await res.json()) as PositionListResponse;
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-岗位-查询",
|
||||
url: "/api/positions",
|
||||
returnParam: data.positions,
|
||||
});
|
||||
return data.positions;
|
||||
}
|
||||
|
||||
@@ -34,7 +41,15 @@ export async function createPosition(input: PositionCreateInput): Promise<Positi
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to create position");
|
||||
return (await res.json()) as Position;
|
||||
const created = (await res.json()) as Position;
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-岗位-新增",
|
||||
url: "/api/positions",
|
||||
reqParam: input,
|
||||
returnParam: created,
|
||||
});
|
||||
return created;
|
||||
}
|
||||
|
||||
export async function updatePosition(id: string, input: PositionUpdateInput): Promise<Position> {
|
||||
@@ -44,18 +59,39 @@ export async function updatePosition(id: string, input: PositionUpdateInput): Pr
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to update position");
|
||||
return (await res.json()) as Position;
|
||||
const updated = (await res.json()) as Position;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-岗位-编辑",
|
||||
url: `/api/positions/${id}`,
|
||||
reqParam: input,
|
||||
returnParam: updated,
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
export async function deletePosition(id: string): Promise<void> {
|
||||
const res = await apiFetch(`${base()}/${encodeURIComponent(id)}`, { method: "DELETE" });
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to delete position");
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-岗位-删除",
|
||||
url: `/api/positions/${id}`,
|
||||
reqParam: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function getTagBindings(positionId: string): Promise<TagBindings> {
|
||||
const res = await apiFetch(`${base()}/${encodeURIComponent(positionId)}/tag-bindings`);
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to load tag bindings");
|
||||
const data = (await res.json()) as TagBindingsResponse;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-岗位-标签绑定查看",
|
||||
url: `/api/positions/${positionId}/tag-bindings`,
|
||||
reqParam: { positionId },
|
||||
returnParam: data.bindings,
|
||||
});
|
||||
return data.bindings;
|
||||
}
|
||||
|
||||
@@ -67,6 +103,13 @@ export async function setTagBindings(positionId: string, bindings: TagBindings):
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to save tag bindings");
|
||||
const data = (await res.json()) as TagBindingsResponse;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-岗位-标签绑定保存",
|
||||
url: `/api/positions/${positionId}/tag-bindings`,
|
||||
reqParam: { positionId, bindings },
|
||||
returnParam: data.bindings,
|
||||
});
|
||||
return data.bindings;
|
||||
}
|
||||
|
||||
@@ -74,6 +117,13 @@ export async function listMembers(positionId: string): Promise<PositionMember[]>
|
||||
const res = await apiFetch(`${base()}/${encodeURIComponent(positionId)}/members`);
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to list members");
|
||||
const data = (await res.json()) as { members: PositionMember[] };
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-岗位-成员查询",
|
||||
url: `/api/positions/${positionId}/members`,
|
||||
reqParam: { positionId },
|
||||
returnParam: data.members,
|
||||
});
|
||||
return data.members;
|
||||
}
|
||||
|
||||
@@ -85,6 +135,13 @@ export async function addMembers(positionId: string, userIds: string[]): Promise
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to add members");
|
||||
const data = (await res.json()) as { members: PositionMember[] };
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-岗位-添加成员",
|
||||
url: `/api/positions/${positionId}/members`,
|
||||
reqParam: { positionId, userIds },
|
||||
returnParam: data.members,
|
||||
});
|
||||
return data.members;
|
||||
}
|
||||
|
||||
@@ -96,6 +153,13 @@ export async function removeMembers(positionId: string, userIds: string[]): Prom
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to remove members");
|
||||
const data = (await res.json()) as { members: PositionMember[] };
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-岗位-移除成员",
|
||||
url: `/api/positions/${positionId}/members`,
|
||||
reqParam: { positionId, userIds },
|
||||
returnParam: data.members,
|
||||
});
|
||||
return data.members;
|
||||
}
|
||||
|
||||
@@ -103,6 +167,13 @@ export async function resyncPosition(positionId: string): Promise<number> {
|
||||
const res = await apiFetch(`${base()}/${encodeURIComponent(positionId)}/resync`, { method: "POST" });
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to resync position");
|
||||
const data = (await res.json()) as { synced: number };
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-岗位-同步",
|
||||
url: `/api/positions/${positionId}/resync`,
|
||||
reqParam: { positionId },
|
||||
returnParam: data,
|
||||
});
|
||||
return data.synced;
|
||||
}
|
||||
|
||||
@@ -117,6 +188,12 @@ export async function listPositionUsers(): Promise<PositionUser[]> {
|
||||
const res = await apiFetch(`${base()}/users`);
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to list users");
|
||||
const data = (await res.json()) as { users: PositionUser[] };
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-岗位-用户查询",
|
||||
url: "/api/positions/users",
|
||||
returnParam: data.users,
|
||||
});
|
||||
return data.users;
|
||||
}
|
||||
|
||||
@@ -127,4 +204,10 @@ export async function setUserPosition(userId: string, positionId: string | null)
|
||||
body: JSON.stringify({ position_id: positionId }),
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to set user position");
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-岗位-设置用户岗位",
|
||||
url: `/api/positions/users/${userId}`,
|
||||
reqParam: { userId, positionId },
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import type {
|
||||
CreateQuestionRequest,
|
||||
@@ -12,16 +13,25 @@ const BASE = () => `${getBackendBaseURL()}/api/recommended-questions`;
|
||||
export async function listRecommendedQuestions(all = false): Promise<RecommendedQuestion[]> {
|
||||
// ``all=true`` is the admin management view: every question, unfiltered by the
|
||||
// caller's 岗位 (position). The chat input uses the default (filtered) view.
|
||||
const res = await apiFetch(`${BASE()}${all ? "?all=1" : ""}`);
|
||||
const url = `${BASE()}${all ? "?all=1" : ""}`;
|
||||
const res = await apiFetch(url);
|
||||
if (!res.ok) throw new Error(`Failed to load questions: ${res.statusText}`);
|
||||
const data = (await res.json()) as { questions: RecommendedQuestion[] };
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-推荐问题-查询列表",
|
||||
url,
|
||||
reqParam: { all },
|
||||
returnParam: { count: data.questions.length },
|
||||
});
|
||||
return data.questions;
|
||||
}
|
||||
|
||||
export async function createRecommendedQuestion(
|
||||
body: CreateQuestionRequest,
|
||||
): Promise<RecommendedQuestion> {
|
||||
const res = await apiFetch(BASE(), {
|
||||
const url = BASE();
|
||||
const res = await apiFetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
@@ -30,14 +40,23 @@ export async function createRecommendedQuestion(
|
||||
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
||||
throw new Error(err.detail ?? `Failed to create question: ${res.statusText}`);
|
||||
}
|
||||
return res.json() as Promise<RecommendedQuestion>;
|
||||
const result = (await res.json()) as RecommendedQuestion;
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-推荐问题-新增问题",
|
||||
url,
|
||||
reqParam: body,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateRecommendedQuestion(
|
||||
id: string,
|
||||
body: UpdateQuestionRequest,
|
||||
): Promise<RecommendedQuestion> {
|
||||
const res = await apiFetch(`${BASE()}/${id}`, {
|
||||
const url = `${BASE()}/${id}`;
|
||||
const res = await apiFetch(url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
@@ -46,23 +65,45 @@ export async function updateRecommendedQuestion(
|
||||
const err = (await res.json().catch(() => ({}))) as { detail?: string };
|
||||
throw new Error(err.detail ?? `Failed to update question: ${res.statusText}`);
|
||||
}
|
||||
return res.json() as Promise<RecommendedQuestion>;
|
||||
const result = (await res.json()) as RecommendedQuestion;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-推荐问题-编辑问题",
|
||||
url,
|
||||
reqParam: { id, ...body },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function deleteRecommendedQuestion(id: string): Promise<void> {
|
||||
const res = await apiFetch(`${BASE()}/${id}`, { method: "DELETE" });
|
||||
const url = `${BASE()}/${id}`;
|
||||
const res = await apiFetch(url, { method: "DELETE" });
|
||||
if (!res.ok) throw new Error(`Failed to delete question: ${res.statusText}`);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-推荐问题-删除问题",
|
||||
url,
|
||||
reqParam: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function reorderRecommendedQuestions(
|
||||
ids: string[],
|
||||
): Promise<RecommendedQuestion[]> {
|
||||
const res = await apiFetch(`${getBackendBaseURL()}/api/recommended-questions-reorder`, {
|
||||
const url = `${getBackendBaseURL()}/api/recommended-questions-reorder`;
|
||||
const res = await apiFetch(url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ids }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`Failed to reorder questions: ${res.statusText}`);
|
||||
const data = (await res.json()) as { questions: RecommendedQuestion[] };
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-推荐问题-调整排序",
|
||||
url,
|
||||
reqParam: { ids },
|
||||
});
|
||||
return data.questions;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
// Share codes share the same alphabet/shape as conversation shares, so reuse the
|
||||
// extractor instead of duplicating the regex.
|
||||
export { extractShareCode } from "@/core/thread-shares/api";
|
||||
@@ -86,7 +87,15 @@ export async function createDraftShare(
|
||||
body: JSON.stringify({ draft_id: draftId, mode }),
|
||||
});
|
||||
if (!response.ok) throw await parseError(response, "创建分享失败");
|
||||
return response.json() as Promise<DraftShare>;
|
||||
const result = (await response.json()) as DraftShare;
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-会商草稿分享-创建分享",
|
||||
url: baseUrl(),
|
||||
reqParam: { draft_id: draftId, mode },
|
||||
returnParam: { share_code: result.share_code, mode: result.mode },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** List the current user's own roundtable-draft share links. */
|
||||
@@ -94,34 +103,57 @@ export async function listDraftShares(): Promise<DraftShare[]> {
|
||||
const response = await apiFetch(baseUrl());
|
||||
if (!response.ok) throw await parseError(response, "加载分享列表失败");
|
||||
const json = (await response.json()) as { shares: DraftShare[] };
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-会商草稿分享-查询分享列表",
|
||||
url: baseUrl(),
|
||||
returnParam: { count: json.shares.length },
|
||||
});
|
||||
return json.shares;
|
||||
}
|
||||
|
||||
/** Revoke a share link so it can no longer be previewed, imported, or viewed. */
|
||||
export async function revokeDraftShare(shareCode: string): Promise<void> {
|
||||
const response = await apiFetch(`${baseUrl()}/${encodeURIComponent(shareCode)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
const url = `${baseUrl()}/${encodeURIComponent(shareCode)}`;
|
||||
const response = await apiFetch(url, { method: "DELETE" });
|
||||
if (!response.ok) throw await parseError(response, "撤销分享失败");
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-会商草稿分享-撤销分享",
|
||||
url,
|
||||
reqParam: { shareCode },
|
||||
});
|
||||
}
|
||||
|
||||
/** Preview a share link before importing it. */
|
||||
export async function previewDraftShare(shareCode: string): Promise<DraftSharePreview> {
|
||||
const response = await apiFetch(
|
||||
`${baseUrl()}/${encodeURIComponent(shareCode)}/preview`,
|
||||
);
|
||||
const url = `${baseUrl()}/${encodeURIComponent(shareCode)}/preview`;
|
||||
const response = await apiFetch(url);
|
||||
if (!response.ok) throw await parseError(response, "无法读取该分享");
|
||||
return response.json() as Promise<DraftSharePreview>;
|
||||
const result = (await response.json()) as DraftSharePreview;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-会商草稿分享-预览分享",
|
||||
url,
|
||||
reqParam: { shareCode },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Import the roundtable draft behind a share link into the current account. */
|
||||
export async function importDraftShare(shareCode: string): Promise<DraftImportResult> {
|
||||
const response = await apiFetch(
|
||||
`${baseUrl()}/${encodeURIComponent(shareCode)}/import`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
const url = `${baseUrl()}/${encodeURIComponent(shareCode)}/import`;
|
||||
const response = await apiFetch(url, { method: "POST" });
|
||||
if (!response.ok) throw await parseError(response, "导入失败");
|
||||
return response.json() as Promise<DraftImportResult>;
|
||||
const result = (await response.json()) as DraftImportResult;
|
||||
writeYLog({
|
||||
optType: OptType.import,
|
||||
optDetail: "B1-CM助手-会商草稿分享-导入分享",
|
||||
url,
|
||||
reqParam: { shareCode },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -93,9 +93,17 @@ export async function createScheduledTask(data: {
|
||||
}
|
||||
|
||||
export async function listScheduledTaskRuns(taskId: string) {
|
||||
return parseResponse<ScheduledTaskRun[]>(
|
||||
const result = await parseResponse<ScheduledTaskRun[]>(
|
||||
await apiFetch(`${baseUrl()}/${encodeURIComponent(taskId)}/runs`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-定时任务-运行记录列表",
|
||||
url: `${baseUrl()}/${encodeURIComponent(taskId)}/runs`,
|
||||
reqParam: { taskId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getScheduledTaskRun(runId: string) {
|
||||
@@ -113,18 +121,34 @@ export async function getScheduledTaskRun(runId: string) {
|
||||
}
|
||||
|
||||
export async function deleteScheduledTaskRun(runId: string) {
|
||||
return parseResponse<{ success: boolean }>(
|
||||
const result = await parseResponse<{ success: boolean }>(
|
||||
await apiFetch(`${baseUrl()}/runs/${encodeURIComponent(runId)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-定时任务-删除运行记录",
|
||||
url: `${baseUrl()}/runs/${encodeURIComponent(runId)}`,
|
||||
reqParam: { runId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Agent messages for a run's underlying agent run — used to inspect a stuck/running run. */
|
||||
export async function getScheduledTaskRunMessages(runId: string) {
|
||||
return parseResponse<ScheduledTaskRunMessagesResponse>(
|
||||
const result = await parseResponse<ScheduledTaskRunMessagesResponse>(
|
||||
await apiFetch(`${baseUrl()}/runs/${encodeURIComponent(runId)}/messages`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-定时任务-运行消息",
|
||||
url: `${baseUrl()}/runs/${encodeURIComponent(runId)}/messages`,
|
||||
reqParam: { runId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function scheduledTaskRunDownloadUrl(runId: string) {
|
||||
@@ -136,9 +160,17 @@ export function scheduledTaskRunFileDownloadUrl(runId: string, fileIndex: number
|
||||
}
|
||||
|
||||
export async function getScheduledTaskRunFileContent(runId: string, fileIndex: number) {
|
||||
return parseResponse<ScheduledTaskResultFileContent>(
|
||||
const result = await parseResponse<ScheduledTaskResultFileContent>(
|
||||
await apiFetch(`${baseUrl()}/runs/${encodeURIComponent(runId)}/files/${fileIndex}`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-定时任务-运行文件查看",
|
||||
url: `${baseUrl()}/runs/${encodeURIComponent(runId)}/files/${fileIndex}`,
|
||||
reqParam: { runId, fileIndex },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateScheduledTaskRunFile(
|
||||
@@ -146,13 +178,21 @@ export async function updateScheduledTaskRunFile(
|
||||
fileIndex: number,
|
||||
content: string,
|
||||
) {
|
||||
return parseResponse<ScheduledTaskRunFileUpdateResponse>(
|
||||
const result = await parseResponse<ScheduledTaskRunFileUpdateResponse>(
|
||||
await apiFetch(`${baseUrl()}/runs/${encodeURIComponent(runId)}/files/${fileIndex}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ content }),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-定时任务-运行文件编辑",
|
||||
url: `${baseUrl()}/runs/${encodeURIComponent(runId)}/files/${fileIndex}`,
|
||||
reqParam: { runId, fileIndex },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function filenameFromContentDisposition(header: string | null): string | null {
|
||||
@@ -212,6 +252,12 @@ export async function downloadScheduledTaskRun(runId: string) {
|
||||
await apiFetch(scheduledTaskRunDownloadUrl(runId)),
|
||||
"scheduled-task-result.zip",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.export,
|
||||
optDetail: "B1-CM助手-定时任务-下载运行结果",
|
||||
url: scheduledTaskRunDownloadUrl(runId),
|
||||
reqParam: { runId },
|
||||
});
|
||||
}
|
||||
|
||||
export async function downloadScheduledTaskRunFile(runId: string, fileIndex: number) {
|
||||
@@ -219,24 +265,45 @@ export async function downloadScheduledTaskRunFile(runId: string, fileIndex: num
|
||||
await apiFetch(scheduledTaskRunFileDownloadUrl(runId, fileIndex)),
|
||||
"scheduled-task-file",
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.export,
|
||||
optDetail: "B1-CM助手-定时任务-下载运行文件",
|
||||
url: scheduledTaskRunFileDownloadUrl(runId, fileIndex),
|
||||
reqParam: { runId, fileIndex },
|
||||
});
|
||||
}
|
||||
|
||||
export async function getScheduledTaskDeliveryProfile() {
|
||||
return parseResponse<ScheduledTaskDeliveryProfile>(
|
||||
const result = await parseResponse<ScheduledTaskDeliveryProfile>(
|
||||
await apiFetch(`${baseUrl()}/delivery-profile`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-定时任务-投递配置查看",
|
||||
url: `${baseUrl()}/delivery-profile`,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateScheduledTaskDeliveryProfile(data: {
|
||||
element_user_id?: string | null;
|
||||
}) {
|
||||
return parseResponse<ScheduledTaskDeliveryProfile>(
|
||||
const result = await parseResponse<ScheduledTaskDeliveryProfile>(
|
||||
await apiFetch(`${baseUrl()}/delivery-profile`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-定时任务-投递配置修改",
|
||||
url: `${baseUrl()}/delivery-profile`,
|
||||
reqParam: data,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateScheduledTask(
|
||||
@@ -339,53 +406,101 @@ export async function listPublishedScheduledTasks(params: { limit?: number; offs
|
||||
if (params.limit != null) query.set("limit", String(params.limit));
|
||||
if (params.offset != null) query.set("offset", String(params.offset));
|
||||
const suffix = query.toString();
|
||||
return parseResponse<PublishedScheduledTask[]>(
|
||||
const result = await parseResponse<PublishedScheduledTask[]>(
|
||||
await apiFetch(`${baseUrl()}/published${suffix ? `?${suffix}` : ""}`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-定时任务-已发布列表",
|
||||
url: `${baseUrl()}/published${suffix ? `?${suffix}` : ""}`,
|
||||
reqParam: params,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function publishScheduledTask(taskId: string) {
|
||||
return parseResponse<ScheduledTask>(
|
||||
const result = await parseResponse<ScheduledTask>(
|
||||
await apiFetch(`${baseUrl()}/${encodeURIComponent(taskId)}/publish`, {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.export,
|
||||
optDetail: "B1-CM助手-定时任务-发布",
|
||||
url: `${baseUrl()}/${encodeURIComponent(taskId)}/publish`,
|
||||
reqParam: { taskId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function unpublishScheduledTask(taskId: string) {
|
||||
return parseResponse<ScheduledTask>(
|
||||
const result = await parseResponse<ScheduledTask>(
|
||||
await apiFetch(`${baseUrl()}/${encodeURIComponent(taskId)}/unpublish`, {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-定时任务-取消发布",
|
||||
url: `${baseUrl()}/${encodeURIComponent(taskId)}/unpublish`,
|
||||
reqParam: { taskId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function subscribeScheduledTask(taskId: string, notifyElement: boolean) {
|
||||
return parseResponse<ScheduledTaskSubscription>(
|
||||
const result = await parseResponse<ScheduledTaskSubscription>(
|
||||
await apiFetch(`${baseUrl()}/${encodeURIComponent(taskId)}/subscribe`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ notify_element: notifyElement }),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-定时任务-订阅",
|
||||
url: `${baseUrl()}/${encodeURIComponent(taskId)}/subscribe`,
|
||||
reqParam: { taskId, notify_element: notifyElement },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateScheduledTaskSubscription(taskId: string, notifyElement: boolean) {
|
||||
return parseResponse<ScheduledTaskSubscription>(
|
||||
const result = await parseResponse<ScheduledTaskSubscription>(
|
||||
await apiFetch(`${baseUrl()}/${encodeURIComponent(taskId)}/subscribe`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ notify_element: notifyElement }),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-定时任务-订阅修改",
|
||||
url: `${baseUrl()}/${encodeURIComponent(taskId)}/subscribe`,
|
||||
reqParam: { taskId, notify_element: notifyElement },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function unsubscribeScheduledTask(taskId: string) {
|
||||
return parseResponse<{ success: boolean }>(
|
||||
const result = await parseResponse<{ success: boolean }>(
|
||||
await apiFetch(`${baseUrl()}/${encodeURIComponent(taskId)}/subscribe`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-定时任务-取消订阅",
|
||||
url: `${baseUrl()}/${encodeURIComponent(taskId)}/subscribe`,
|
||||
reqParam: { taskId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Admin (cross-user management) ---
|
||||
@@ -395,16 +510,32 @@ export async function listAllScheduledTasks(search?: string) {
|
||||
const qs = new URLSearchParams();
|
||||
if (search) qs.set("search", search);
|
||||
const query = qs.toString();
|
||||
return parseResponse<ScheduledTask[]>(
|
||||
const result = await parseResponse<ScheduledTask[]>(
|
||||
await apiFetch(`${baseUrl()}/admin/all${query ? `?${query}` : ""}`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-定时任务-全部任务列表",
|
||||
url: `${baseUrl()}/admin/all${query ? `?${query}` : ""}`,
|
||||
reqParam: { search },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** List runs for any task regardless of owner. Admin-only endpoint. */
|
||||
export async function adminListScheduledTaskRuns(taskId: string) {
|
||||
return parseResponse<ScheduledTaskRun[]>(
|
||||
const result = await parseResponse<ScheduledTaskRun[]>(
|
||||
await apiFetch(`${baseUrl()}/admin/${encodeURIComponent(taskId)}/runs`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-定时任务-运行记录列表",
|
||||
url: `${baseUrl()}/admin/${encodeURIComponent(taskId)}/runs`,
|
||||
reqParam: { taskId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Update any task regardless of owner. Admin-only endpoint. */
|
||||
@@ -421,52 +552,99 @@ export async function adminUpdateScheduledTask(
|
||||
require_markdown: boolean;
|
||||
}>,
|
||||
) {
|
||||
return parseResponse<ScheduledTask>(
|
||||
const result = await parseResponse<ScheduledTask>(
|
||||
await apiFetch(`${baseUrl()}/admin/${encodeURIComponent(taskId)}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-定时任务-修改",
|
||||
url: `${baseUrl()}/admin/${encodeURIComponent(taskId)}`,
|
||||
reqParam: { taskId, data },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Delete any task regardless of owner. Admin-only endpoint. */
|
||||
export async function adminDeleteScheduledTask(taskId: string) {
|
||||
return parseResponse<{ success: boolean }>(
|
||||
const result = await parseResponse<{ success: boolean }>(
|
||||
await apiFetch(`${baseUrl()}/admin/${encodeURIComponent(taskId)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-定时任务-删除",
|
||||
url: `${baseUrl()}/admin/${encodeURIComponent(taskId)}`,
|
||||
reqParam: { taskId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Trigger an immediate run of any task regardless of owner. Admin-only. */
|
||||
export async function adminTriggerScheduledTask(taskId: string) {
|
||||
return parseResponse<{ success: boolean; task: ScheduledTask }>(
|
||||
const result = await parseResponse<{ success: boolean; task: ScheduledTask }>(
|
||||
await apiFetch(`${baseUrl()}/admin/${encodeURIComponent(taskId)}/trigger`, {
|
||||
method: "POST",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-定时任务-手动运行",
|
||||
url: `${baseUrl()}/admin/${encodeURIComponent(taskId)}/trigger`,
|
||||
reqParam: { taskId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- HTML page tasks ---
|
||||
|
||||
/** Fetch the generated HTML document for an ``html_page`` run. */
|
||||
export async function getScheduledTaskRunHtml(runId: string) {
|
||||
return parseResponse<ScheduledTaskRunHtml>(
|
||||
const result = await parseResponse<ScheduledTaskRunHtml>(
|
||||
await apiFetch(`${baseUrl()}/runs/${encodeURIComponent(runId)}/html`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-定时任务-运行HTML查看",
|
||||
url: `${baseUrl()}/runs/${encodeURIComponent(runId)}/html`,
|
||||
reqParam: { runId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
const favoritesUrl = () => `${getBackendBaseURL()}/api/html-page-favorites`;
|
||||
|
||||
export async function listHtmlPageFavorites() {
|
||||
return parseResponse<HtmlPageFavorite[]>(await apiFetch(favoritesUrl()));
|
||||
const result = await parseResponse<HtmlPageFavorite[]>(await apiFetch(favoritesUrl()));
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-HTML收藏-列表",
|
||||
url: `/api/html-page-favorites`,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getHtmlPageFavorite(favoriteId: string) {
|
||||
return parseResponse<HtmlPageFavorite>(
|
||||
const result = await parseResponse<HtmlPageFavorite>(
|
||||
await apiFetch(`${favoritesUrl()}/${encodeURIComponent(favoriteId)}`),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-HTML收藏-查看",
|
||||
url: `/api/html-page-favorites/${encodeURIComponent(favoriteId)}`,
|
||||
reqParam: { favoriteId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function createHtmlPageFavorite(data: {
|
||||
@@ -475,19 +653,35 @@ export async function createHtmlPageFavorite(data: {
|
||||
description?: string | null;
|
||||
tags?: string[];
|
||||
}) {
|
||||
return parseResponse<HtmlPageFavorite>(
|
||||
const result = await parseResponse<HtmlPageFavorite>(
|
||||
await apiFetch(favoritesUrl(), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-HTML收藏-新建",
|
||||
url: `/api/html-page-favorites`,
|
||||
reqParam: data,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function deleteHtmlPageFavorite(favoriteId: string) {
|
||||
return parseResponse<{ success: boolean }>(
|
||||
const result = await parseResponse<{ success: boolean }>(
|
||||
await apiFetch(`${favoritesUrl()}/${encodeURIComponent(favoriteId)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-HTML收藏-删除",
|
||||
url: `/api/html-page-favorites/${encodeURIComponent(favoriteId)}`,
|
||||
reqParam: { favoriteId },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getNotebookApiBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
import type { SourceItem, StudioNotebook } from "./types";
|
||||
|
||||
function base(): string {
|
||||
@@ -30,7 +31,14 @@ export async function fetchStudioNotebooks(
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) throw new Error(`获取空间列表失败:${res.status}`);
|
||||
return res.json() as Promise<StudioNotebook[]>;
|
||||
const notebooks = (await res.json()) as StudioNotebook[];
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-工作室-空间查询",
|
||||
url: "/api/v1/searchspaces",
|
||||
returnParam: notebooks,
|
||||
});
|
||||
return notebooks;
|
||||
}
|
||||
|
||||
export async function createSearchSpace(
|
||||
@@ -52,6 +60,12 @@ export async function createSearchSpace(
|
||||
const body = await res.json().catch(() => null);
|
||||
throw new Error(body?.detail || `创建失败:${res.status}`);
|
||||
}
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-工作室-新建空间",
|
||||
url: "/api/v1/searchspaces",
|
||||
reqParam: { name },
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadSourceText(
|
||||
@@ -75,6 +89,12 @@ export async function uploadSourceText(
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error(`上传失败:${res.status}`);
|
||||
writeYLog({
|
||||
optType: OptType.import,
|
||||
optDetail: "B1-CM助手-工作室-上传文字来源",
|
||||
url: "/api/v1/documents/textupload",
|
||||
reqParam: { search_space_id: notebookId, title: title || "粘贴的文字" },
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteSearchSpace(
|
||||
@@ -88,6 +108,12 @@ export async function deleteSearchSpace(
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) throw new Error(`删除失败:${res.status}`);
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-工作室-删除空间",
|
||||
url: `/api/v1/searchspaces/${id}`,
|
||||
reqParam: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function renameSearchSpace(
|
||||
@@ -106,6 +132,12 @@ export async function renameSearchSpace(
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
if (!res.ok) throw new Error(`重命名失败:${res.status}`);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-工作室-重命名空间",
|
||||
url: `/api/v1/searchspaces/${id}`,
|
||||
reqParam: { id, name },
|
||||
});
|
||||
}
|
||||
|
||||
export interface ChunkRead {
|
||||
@@ -134,7 +166,15 @@ export async function fetchDocumentByChunk(
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) throw new Error(`获取文档失败:${res.status}`);
|
||||
return res.json() as Promise<DocumentWithChunks>;
|
||||
const doc = (await res.json()) as DocumentWithChunks;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-工作室-查看文档",
|
||||
url: `/api/v1/documents/by-chunk/${chunkId}`,
|
||||
reqParam: { chunkId },
|
||||
returnParam: { id: doc.id, title: doc.title, file_type: doc.file_type },
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
||||
export async function uploadSourceFiles(
|
||||
@@ -157,6 +197,15 @@ export async function uploadSourceFiles(
|
||||
body: formData,
|
||||
});
|
||||
if (!res.ok) throw new Error(`上传失败:${res.status}`);
|
||||
writeYLog({
|
||||
optType: OptType.import,
|
||||
optDetail: "B1-CM助手-工作室-上传文件来源",
|
||||
url: "/api/v1/documents/fileupload",
|
||||
reqParam: {
|
||||
search_space_id: notebookId,
|
||||
files: files.map((f) => f.name),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchNotebookSources(
|
||||
@@ -171,5 +220,13 @@ export async function fetchNotebookSources(
|
||||
{ headers: { Authorization: `Bearer ${token}` } },
|
||||
);
|
||||
if (!res.ok) throw new Error(`获取来源失败:${res.status}`);
|
||||
return res.json() as Promise<SourceItem[]>;
|
||||
const sources = (await res.json()) as SourceItem[];
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-工作室-来源查询",
|
||||
url: `/api/v1/source/search-space/${notebookId}?limit=100`,
|
||||
reqParam: { notebookId },
|
||||
returnParam: sources,
|
||||
});
|
||||
return sources;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
export interface PromptPrefixSettings {
|
||||
enabled: boolean;
|
||||
@@ -20,7 +21,14 @@ export async function loadPromptPrefixSettings(): Promise<PromptPrefixSettings>
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load prompt prefix settings: ${response.status}`);
|
||||
}
|
||||
return (await response.json()) as PromptPrefixSettings;
|
||||
const data = (await response.json()) as PromptPrefixSettings;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-提示词前缀查看",
|
||||
url: "/api/system-settings/prompt-prefix",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updatePromptPrefixSettings(
|
||||
@@ -40,7 +48,15 @@ export async function updatePromptPrefixSettings(
|
||||
.catch(() => ({ detail: "Failed to update prompt prefix" }));
|
||||
throw new Error(detail.detail ?? "Failed to update prompt prefix");
|
||||
}
|
||||
return (await response.json()) as PromptPrefixSettings;
|
||||
const data = (await response.json()) as PromptPrefixSettings;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-系统设置-提示词前缀编辑",
|
||||
url: "/api/system-settings/prompt-prefix",
|
||||
reqParam: body,
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export interface PrivateSquareStatus {
|
||||
@@ -86,7 +102,14 @@ export async function loadPrivateSquareStatus(): Promise<PrivateSquareStatus> {
|
||||
`Failed to load private square settings: ${response.status}`,
|
||||
);
|
||||
}
|
||||
return (await response.json()) as PrivateSquareStatus;
|
||||
const data = (await response.json()) as PrivateSquareStatus;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-私密广场查看",
|
||||
url: "/api/system-settings/private-square",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export interface PublishSquare {
|
||||
@@ -112,7 +135,14 @@ export async function loadPublishSquares(): Promise<PublishSquaresSettings> {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load publish squares: ${response.status}`);
|
||||
}
|
||||
return (await response.json()) as PublishSquaresSettings;
|
||||
const data = (await response.json()) as PublishSquaresSettings;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-发布广场查看",
|
||||
url: "/api/system-settings/publish-squares",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updatePublishSquares(
|
||||
@@ -132,7 +162,15 @@ export async function updatePublishSquares(
|
||||
.catch(() => ({ detail: "Failed to update publish squares" }));
|
||||
throw new Error(detail.detail ?? "Failed to update publish squares");
|
||||
}
|
||||
return (await response.json()) as PublishSquaresSettings;
|
||||
const data = (await response.json()) as PublishSquaresSettings;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-系统设置-发布广场编辑",
|
||||
url: "/api/system-settings/publish-squares",
|
||||
reqParam: body,
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export type ThemeMode = "light" | "dark" | "lightblue";
|
||||
@@ -162,7 +200,14 @@ export async function loadAppearanceSettings(): Promise<AppearanceSettings> {
|
||||
`Failed to load appearance settings: ${response.status}`,
|
||||
);
|
||||
}
|
||||
return (await response.json()) as AppearanceSettings;
|
||||
const data = (await response.json()) as AppearanceSettings;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-外观查看",
|
||||
url: "/api/system-settings/appearance",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateAppearanceSettings(
|
||||
@@ -182,7 +227,15 @@ export async function updateAppearanceSettings(
|
||||
.catch(() => ({ detail: "Failed to update appearance" }));
|
||||
throw new Error(detail.detail ?? "Failed to update appearance");
|
||||
}
|
||||
return (await response.json()) as AppearanceSettings;
|
||||
const data = (await response.json()) as AppearanceSettings;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-系统设置-外观编辑",
|
||||
url: "/api/system-settings/appearance",
|
||||
reqParam: body,
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function loadCitationDisplaySettings(): Promise<CitationDisplaySettings> {
|
||||
@@ -194,7 +247,14 @@ export async function loadCitationDisplaySettings(): Promise<CitationDisplaySett
|
||||
`Failed to load citation display settings: ${response.status}`,
|
||||
);
|
||||
}
|
||||
return (await response.json()) as CitationDisplaySettings;
|
||||
const data = (await response.json()) as CitationDisplaySettings;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-引用展示查看",
|
||||
url: "/api/system-settings/citation-display",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function loadLeaderboardSettings(): Promise<LeaderboardSettings> {
|
||||
@@ -204,7 +264,14 @@ export async function loadLeaderboardSettings(): Promise<LeaderboardSettings> {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load leaderboard settings: ${response.status}`);
|
||||
}
|
||||
return (await response.json()) as LeaderboardSettings;
|
||||
const data = (await response.json()) as LeaderboardSettings;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-排行榜查看",
|
||||
url: "/api/system-settings/leaderboard",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateLeaderboardSettings(
|
||||
@@ -224,7 +291,15 @@ export async function updateLeaderboardSettings(
|
||||
.catch(() => ({ detail: "Failed to update leaderboard settings" }));
|
||||
throw new Error(detail.detail ?? "Failed to update leaderboard settings");
|
||||
}
|
||||
return (await response.json()) as LeaderboardSettings;
|
||||
const data = (await response.json()) as LeaderboardSettings;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-系统设置-排行榜编辑",
|
||||
url: "/api/system-settings/leaderboard",
|
||||
reqParam: body,
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateCitationDisplaySettings(
|
||||
@@ -244,7 +319,15 @@ export async function updateCitationDisplaySettings(
|
||||
.catch(() => ({ detail: "Failed to update citation display" }));
|
||||
throw new Error(detail.detail ?? "Failed to update citation display");
|
||||
}
|
||||
return (await response.json()) as CitationDisplaySettings;
|
||||
const data = (await response.json()) as CitationDisplaySettings;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-系统设置-引用展示编辑",
|
||||
url: "/api/system-settings/citation-display",
|
||||
reqParam: body,
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export interface SkillCompressionSettings {
|
||||
@@ -268,7 +351,14 @@ export async function loadSkillCompressionSettings(): Promise<SkillCompressionSe
|
||||
`Failed to load skill compression settings: ${response.status}`,
|
||||
);
|
||||
}
|
||||
return (await response.json()) as SkillCompressionSettings;
|
||||
const data = (await response.json()) as SkillCompressionSettings;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-技能压缩查看",
|
||||
url: "/api/system-settings/skill-compression",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateSkillCompressionSettings(
|
||||
@@ -288,7 +378,15 @@ export async function updateSkillCompressionSettings(
|
||||
.catch(() => ({ detail: "Failed to update skill compression" }));
|
||||
throw new Error(detail.detail ?? "Failed to update skill compression");
|
||||
}
|
||||
return (await response.json()) as SkillCompressionSettings;
|
||||
const data = (await response.json()) as SkillCompressionSettings;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-系统设置-技能压缩编辑",
|
||||
url: "/api/system-settings/skill-compression",
|
||||
reqParam: body,
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/** 「按类型聚合」模块(zzJC / zzXD)选中的智能体标签配置(需求 0616 #3/#4)。 */
|
||||
@@ -306,7 +404,15 @@ export async function loadAgentModuleConfig(
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load agent module config: ${response.status}`);
|
||||
}
|
||||
return (await response.json()) as AgentModuleConfig;
|
||||
const data = (await response.json()) as AgentModuleConfig;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-智能体模块查看",
|
||||
url: `/api/system-settings/agent-modules/${key}`,
|
||||
reqParam: { key },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updateAgentModuleConfig(
|
||||
@@ -327,7 +433,15 @@ export async function updateAgentModuleConfig(
|
||||
.catch(() => ({ detail: "Failed to update agent module config" }));
|
||||
throw new Error(detail.detail ?? "Failed to update agent module config");
|
||||
}
|
||||
return (await response.json()) as AgentModuleConfig;
|
||||
const data = (await response.json()) as AgentModuleConfig;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-系统设置-智能体模块编辑",
|
||||
url: `/api/system-settings/agent-modules/${key}`,
|
||||
reqParam: { key, tag_ids: tagIds },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export interface LogSystemSettings {
|
||||
@@ -343,7 +457,14 @@ export async function loadLogSystemSettings(): Promise<LogSystemSettings> {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load log system settings: ${response.status}`);
|
||||
}
|
||||
return (await response.json()) as LogSystemSettings;
|
||||
const data = (await response.json()) as LogSystemSettings;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-系统设置-日志系统查看",
|
||||
url: "/api/system-settings/log-system",
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function updatePrivateSquareSettings(
|
||||
@@ -363,5 +484,14 @@ export async function updatePrivateSquareSettings(
|
||||
.catch(() => ({ detail: "Failed to update private square" }));
|
||||
throw new Error(detail.detail ?? "Failed to update private square");
|
||||
}
|
||||
return (await response.json()) as PrivateSquareStatus;
|
||||
const data = (await response.json()) as PrivateSquareStatus;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-系统设置-私密广场编辑",
|
||||
url: "/api/system-settings/private-square",
|
||||
// omit `password` (secret) from the logged request params
|
||||
reqParam: { enabled: body.enabled, password_set: body.password != null },
|
||||
returnParam: data,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import type {
|
||||
Tag,
|
||||
@@ -22,6 +23,13 @@ export async function listTags(tagType?: TagType): Promise<Tag[]> {
|
||||
const res = await apiFetch(`${base()}${query}`);
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to list tags");
|
||||
const data = (await res.json()) as TagListResponse;
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-标签-查询",
|
||||
url: `/api/tags${query}`,
|
||||
reqParam: { tagType },
|
||||
returnParam: data.tags,
|
||||
});
|
||||
return data.tags;
|
||||
}
|
||||
|
||||
@@ -32,7 +40,15 @@ export async function createTag(input: TagCreateInput): Promise<Tag> {
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to create tag");
|
||||
return (await res.json()) as Tag;
|
||||
const created = (await res.json()) as Tag;
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-标签-新增",
|
||||
url: "/api/tags",
|
||||
reqParam: input,
|
||||
returnParam: created,
|
||||
});
|
||||
return created;
|
||||
}
|
||||
|
||||
export async function updateTag(id: string, input: TagUpdateInput): Promise<Tag> {
|
||||
@@ -42,7 +58,15 @@ export async function updateTag(id: string, input: TagUpdateInput): Promise<Tag>
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to update tag");
|
||||
return (await res.json()) as Tag;
|
||||
const updated = (await res.json()) as Tag;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-标签-编辑",
|
||||
url: `/api/tags/${id}`,
|
||||
reqParam: input,
|
||||
returnParam: updated,
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
export async function deleteTag(id: string): Promise<void> {
|
||||
@@ -50,6 +74,12 @@ export async function deleteTag(id: string): Promise<void> {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to delete tag");
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-标签-删除",
|
||||
url: `/api/tags/${id}`,
|
||||
reqParam: { id },
|
||||
});
|
||||
}
|
||||
|
||||
export async function getResourceTags(
|
||||
@@ -61,6 +91,13 @@ export async function getResourceTags(
|
||||
);
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to load resource tags");
|
||||
const data = (await res.json()) as TagListResponse;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-标签-资源标签查看",
|
||||
url: `/api/tags/assignments/${targetType}/${targetId}`,
|
||||
reqParam: { targetType, targetId },
|
||||
returnParam: data.tags,
|
||||
});
|
||||
return data.tags;
|
||||
}
|
||||
|
||||
@@ -79,5 +116,12 @@ export async function setResourceTags(
|
||||
);
|
||||
if (!res.ok) throw await parseDetail(res, "Failed to update resource tags");
|
||||
const data = (await res.json()) as TagListResponse;
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-标签-资源标签设置",
|
||||
url: `/api/tags/assignments/${targetType}/${targetId}`,
|
||||
reqParam: { targetType, targetId, tagIds },
|
||||
returnParam: data.tags,
|
||||
});
|
||||
return data.tags;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { Message } from "@langchain/langgraph-sdk";
|
||||
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
/**
|
||||
* Share modes:
|
||||
@@ -65,7 +66,15 @@ export async function createShare(
|
||||
body: JSON.stringify({ thread_id: threadId, mode }),
|
||||
});
|
||||
if (!response.ok) throw await parseError(response, "创建分享失败");
|
||||
return response.json() as Promise<ThreadShare>;
|
||||
const result = (await response.json()) as ThreadShare;
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-会话分享-创建分享",
|
||||
url: baseUrl(),
|
||||
reqParam: { thread_id: threadId, mode },
|
||||
returnParam: { share_code: result.share_code, mode: result.mode },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** List the current user's own share links. */
|
||||
@@ -73,34 +82,57 @@ export async function listShares(): Promise<ThreadShare[]> {
|
||||
const response = await apiFetch(baseUrl());
|
||||
if (!response.ok) throw await parseError(response, "加载分享列表失败");
|
||||
const json = (await response.json()) as { shares: ThreadShare[] };
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-会话分享-查询分享列表",
|
||||
url: baseUrl(),
|
||||
returnParam: { count: json.shares.length },
|
||||
});
|
||||
return json.shares;
|
||||
}
|
||||
|
||||
/** Revoke a share link so it can no longer be previewed or imported. */
|
||||
export async function revokeShare(shareCode: string): Promise<void> {
|
||||
const response = await apiFetch(`${baseUrl()}/${encodeURIComponent(shareCode)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
const url = `${baseUrl()}/${encodeURIComponent(shareCode)}`;
|
||||
const response = await apiFetch(url, { method: "DELETE" });
|
||||
if (!response.ok) throw await parseError(response, "撤销分享失败");
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-会话分享-撤销分享",
|
||||
url,
|
||||
reqParam: { shareCode },
|
||||
});
|
||||
}
|
||||
|
||||
/** Preview a share link before importing it. */
|
||||
export async function previewShare(shareCode: string): Promise<SharePreview> {
|
||||
const response = await apiFetch(
|
||||
`${baseUrl()}/${encodeURIComponent(shareCode)}/preview`,
|
||||
);
|
||||
const url = `${baseUrl()}/${encodeURIComponent(shareCode)}/preview`;
|
||||
const response = await apiFetch(url);
|
||||
if (!response.ok) throw await parseError(response, "无法读取该分享");
|
||||
return response.json() as Promise<SharePreview>;
|
||||
const result = (await response.json()) as SharePreview;
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-会话分享-预览分享",
|
||||
url,
|
||||
reqParam: { shareCode },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Import every conversation behind a share link into the current account. */
|
||||
export async function importShare(shareCode: string): Promise<ImportResult> {
|
||||
const response = await apiFetch(
|
||||
`${baseUrl()}/${encodeURIComponent(shareCode)}/import`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
const url = `${baseUrl()}/${encodeURIComponent(shareCode)}/import`;
|
||||
const response = await apiFetch(url, { method: "POST" });
|
||||
if (!response.ok) throw await parseError(response, "导入失败");
|
||||
return response.json() as Promise<ImportResult>;
|
||||
const result = (await response.json()) as ImportResult;
|
||||
writeYLog({
|
||||
optType: OptType.import,
|
||||
optDetail: "B1-CM助手-会话分享-导入分享",
|
||||
url,
|
||||
reqParam: { shareCode },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getStoredAuth } from "../auth";
|
||||
import { rememberAskTime } from "../chat-timestamps";
|
||||
import { getBackendBaseURL, getLangGraphBaseURL } from "../config";
|
||||
import { useI18n } from "../i18n/hooks";
|
||||
import { OptType, writeYLog } from "../log/y-log";
|
||||
import type { FileInMessage } from "../messages/utils";
|
||||
import type { LocalSettings } from "../settings";
|
||||
import { isThreadRagModeLocked, lockThreadRagMode, useLocalSettings } from "../settings";
|
||||
@@ -231,6 +232,84 @@ function buildCurrentUserLabel(): string {
|
||||
return userId ? userId : "未知用户";
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 `on_chat_model_end` 事件的 output 里尽力取出模型输出文本。
|
||||
*
|
||||
* output 形态可能是 AIMessage(`{ content: string | parts[] }`),也可能是
|
||||
* LangChain 的 `{ generations: [[{ text, message }]] }`。全部容错,取不到返回空串。
|
||||
*/
|
||||
function textFromChatModelOutput(output: unknown): string {
|
||||
if (output == null) return "";
|
||||
if (typeof output === "string") return output;
|
||||
if (typeof output !== "object") return String(output);
|
||||
const o = output as Record<string, unknown>;
|
||||
if ("content" in o) {
|
||||
const c = o.content;
|
||||
if (typeof c === "string") return c;
|
||||
if (Array.isArray(c)) {
|
||||
const parts = c
|
||||
.map((p) =>
|
||||
p && typeof p === "object" && "text" in p
|
||||
? String((p as { text?: unknown }).text ?? "")
|
||||
: "",
|
||||
)
|
||||
.filter(Boolean);
|
||||
if (parts.length) return parts.join("");
|
||||
}
|
||||
}
|
||||
if (Array.isArray(o.generations)) {
|
||||
const texts: string[] = [];
|
||||
const collect = (gen: unknown) => {
|
||||
if (Array.isArray(gen)) {
|
||||
gen.forEach(collect);
|
||||
return;
|
||||
}
|
||||
if (gen && typeof gen === "object") {
|
||||
const gg = gen as Record<string, unknown>;
|
||||
if (typeof gg.text === "string" && gg.text) texts.push(gg.text);
|
||||
else if (gg.message) {
|
||||
const t = textFromChatModelOutput(gg.message);
|
||||
if (t) texts.push(t);
|
||||
}
|
||||
}
|
||||
};
|
||||
collect(o.generations);
|
||||
if (texts.length) return texts.join("");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 `on_chat_model_*` 事件的 input 里尽力取出最后一条 user/human 文本,
|
||||
* 作为本次大模型调用的「问题」。input 可能是 `{ messages: BaseMessage[] | BaseMessage[][] }`。
|
||||
*/
|
||||
function lastHumanFromChatModelInput(input: unknown): string {
|
||||
try {
|
||||
const raw =
|
||||
input && typeof input === "object" && "messages" in input
|
||||
? (input as { messages?: unknown }).messages
|
||||
: input;
|
||||
const flat: unknown[] = [];
|
||||
const push = (m: unknown) => {
|
||||
if (Array.isArray(m)) m.forEach(push);
|
||||
else if (m != null) flat.push(m);
|
||||
};
|
||||
push(raw);
|
||||
for (let i = flat.length - 1; i >= 0; i--) {
|
||||
const m = flat[i];
|
||||
if (!m || typeof m !== "object") continue;
|
||||
const type = (m as Record<string, unknown>).type ?? (m as Record<string, unknown>).role;
|
||||
if (type === "human" || type === "user") {
|
||||
const text = textFromChatModelOutput(m);
|
||||
if (text) return text;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
/* 取不到忽略 */
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function useThreadStream({
|
||||
threadId,
|
||||
context,
|
||||
@@ -333,6 +412,28 @@ export function useThreadStream({
|
||||
data: event.data,
|
||||
});
|
||||
}
|
||||
// 操作日志(Y-Log):每一次大模型调用结束都各上报一条(一次 LLM 调用 = 一条),
|
||||
// 让问答日志尽量「多、全」——一个问答轮次里 lead agent / 子代理的每次模型调用
|
||||
// 都会落一条。门控与失败静默都在 writeYLog 内部处理,绝不影响问答。
|
||||
if (event.event === "on_chat_model_end") {
|
||||
try {
|
||||
const data = (event.data ?? {}) as {
|
||||
input?: unknown;
|
||||
output?: unknown;
|
||||
};
|
||||
const answer = textFromChatModelOutput(data.output);
|
||||
const question = lastHumanFromChatModelInput(data.input);
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-智能问答",
|
||||
url: `/api/threads/${threadIdRef.current ?? ""}/runs/stream`,
|
||||
reqParam: { model: event.name ?? "", question },
|
||||
returnParam: { data: answer },
|
||||
});
|
||||
} catch {
|
||||
/* 上报绝不影响问答 */
|
||||
}
|
||||
}
|
||||
},
|
||||
onUpdateEvent(data) {
|
||||
const updates: Array<Partial<AgentThreadState> | null> = Object.values(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
/** Coarse failure buckets, mirrored from the backend classifier. */
|
||||
export const TOOL_ERROR_CATEGORIES = [
|
||||
@@ -98,39 +99,58 @@ function buildQuery(filters: ToolMetricsListQuery): string {
|
||||
export async function loadToolMetrics(
|
||||
query: ToolMetricsListQuery,
|
||||
): Promise<ToolMetricsListResponse> {
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/admin/tool-metrics${buildQuery(query)}`,
|
||||
);
|
||||
const url = `${getBackendBaseURL()}/api/admin/tool-metrics${buildQuery(query)}`;
|
||||
const response = await apiFetch(url);
|
||||
if (!response.ok) {
|
||||
const detail = await response.json().catch(() => ({ detail: response.statusText }));
|
||||
throw new Error(detail.detail ?? "Failed to load tool metrics");
|
||||
}
|
||||
return (await response.json()) as ToolMetricsListResponse;
|
||||
const result = (await response.json()) as ToolMetricsListResponse;
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-工具指标-查询调用列表",
|
||||
url,
|
||||
reqParam: query,
|
||||
returnParam: { total: result.total, count: result.items.length },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function loadToolMetricsSummary(
|
||||
filters: ToolMetricsFilters,
|
||||
): Promise<ToolMetricsSummary> {
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/admin/tool-metrics/summary${buildQuery(filters)}`,
|
||||
);
|
||||
const url = `${getBackendBaseURL()}/api/admin/tool-metrics/summary${buildQuery(filters)}`;
|
||||
const response = await apiFetch(url);
|
||||
if (!response.ok) {
|
||||
const detail = await response.json().catch(() => ({ detail: response.statusText }));
|
||||
throw new Error(detail.detail ?? "Failed to load tool metrics summary");
|
||||
}
|
||||
return (await response.json()) as ToolMetricsSummary;
|
||||
const result = (await response.json()) as ToolMetricsSummary;
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-工具指标-查询汇总统计",
|
||||
url,
|
||||
reqParam: filters,
|
||||
returnParam: { total: result.total, error_total: result.error_total },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function downloadToolMetricsExcel(
|
||||
filters: ToolMetricsFilters,
|
||||
): Promise<void> {
|
||||
const response = await apiFetch(
|
||||
`${getBackendBaseURL()}/api/admin/tool-metrics/export${buildQuery(filters)}`,
|
||||
);
|
||||
const requestUrl = `${getBackendBaseURL()}/api/admin/tool-metrics/export${buildQuery(filters)}`;
|
||||
const response = await apiFetch(requestUrl);
|
||||
if (!response.ok) {
|
||||
const detail = await response.json().catch(() => ({ detail: response.statusText }));
|
||||
throw new Error(detail.detail ?? "Failed to export tool metrics");
|
||||
}
|
||||
writeYLog({
|
||||
optType: OptType.export,
|
||||
optDetail: "B1-CM助手-工具指标-导出Excel",
|
||||
url: requestUrl,
|
||||
reqParam: filters,
|
||||
});
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const filename = (() => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
/** Per-user UI preferences, bound to the account (stored server-side). */
|
||||
export interface UserPreferences {
|
||||
@@ -36,7 +37,15 @@ async function updateUserPreferences(
|
||||
throw new Error(err.detail ?? `Failed to update preferences: ${res.statusText}`);
|
||||
}
|
||||
const data = (await res.json()) as Partial<UserPreferences>;
|
||||
return { hide_admin_prompt_prefix: !!data.hide_admin_prompt_prefix };
|
||||
const result = { hide_admin_prompt_prefix: !!data.hide_admin_prompt_prefix };
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-用户偏好-编辑",
|
||||
url: "/api/user-preferences",
|
||||
reqParam: input,
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Reactive current-user preferences (server-backed, follows the account). */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
export interface UserQaItem {
|
||||
run_id: string;
|
||||
@@ -40,8 +41,14 @@ export async function listMyQa(
|
||||
});
|
||||
const query = (params.q ?? "").trim();
|
||||
if (query) search.set("q", query);
|
||||
return parse(
|
||||
await apiFetch(`${getBackendBaseURL()}/api/users/me/qa?${search}`),
|
||||
"Failed to load my Q&A",
|
||||
);
|
||||
const url = `${getBackendBaseURL()}/api/users/me/qa?${search}`;
|
||||
const result = await parse<UserQaPageResponse>(await apiFetch(url), "Failed to load my Q&A");
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-用户问答-查询我的问答",
|
||||
url,
|
||||
reqParam: params,
|
||||
returnParam: { total: result.total, count: result.items.length },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import type { BusinessChain, ChainSeat } from "@/roundtable-planning/lib/business-chain";
|
||||
import type { SeatMode } from "@/roundtable-planning/lib/seat-mode";
|
||||
@@ -154,7 +155,15 @@ export async function listChains(scope: ChainScope = "mine"): Promise<BusinessCh
|
||||
const url = scope === "mine" ? BASE() : `${BASE()}?scope=${scope}`;
|
||||
const res = await apiFetch(url, { method: "GET" });
|
||||
const data = await readJson<{ chains: ServerChain[] }>(res, "list chains");
|
||||
return (data.chains ?? []).map(toChain);
|
||||
const chains = (data.chains ?? []).map(toChain);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-业务链条-查询",
|
||||
url,
|
||||
reqParam: { scope },
|
||||
returnParam: { count: chains.length },
|
||||
});
|
||||
return chains;
|
||||
}
|
||||
|
||||
/** 发布 / 取消发布一条链条(仅 owner 生效)。返回更新后的链条,404 返回 null。 */
|
||||
@@ -167,7 +176,15 @@ export async function getChain(id: string): Promise<BusinessChain | null> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(id)}`, { method: "GET" });
|
||||
if (res.status === 404) return null;
|
||||
const data = await readJson<ServerChain>(res, "get chain");
|
||||
return toChain(data);
|
||||
const chain = toChain(data);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-业务链条-查看",
|
||||
url: `${BASE()}/${id}`,
|
||||
reqParam: { id },
|
||||
returnParam: { id: chain.id, title: chain.title },
|
||||
});
|
||||
return chain;
|
||||
}
|
||||
|
||||
export async function createChain(payload: ChainCreatePayload): Promise<BusinessChain> {
|
||||
@@ -177,7 +194,15 @@ export async function createChain(payload: ChainCreatePayload): Promise<Business
|
||||
body: JSON.stringify(fromCreate(payload)),
|
||||
});
|
||||
const data = await readJson<ServerChain>(res, "create chain");
|
||||
return toChain(data);
|
||||
const chain = toChain(data);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-业务链条-新建",
|
||||
url: BASE(),
|
||||
reqParam: { title: payload.title, seats: payload.seats?.length ?? 0 },
|
||||
returnParam: { id: chain.id, title: chain.title },
|
||||
});
|
||||
return chain;
|
||||
}
|
||||
|
||||
export async function updateChain(id: string, patch: ChainUpdatePayload): Promise<BusinessChain | null> {
|
||||
@@ -188,7 +213,20 @@ export async function updateChain(id: string, patch: ChainUpdatePayload): Promis
|
||||
});
|
||||
if (res.status === 404) return null;
|
||||
const data = await readJson<ServerChain>(res, "update chain");
|
||||
return toChain(data);
|
||||
const chain = toChain(data);
|
||||
writeYLog({
|
||||
optType: patch.isPublic === true ? OptType.enabled : patch.isPublic === false ? OptType.disabled : OptType.edit,
|
||||
optDetail:
|
||||
patch.isPublic === true
|
||||
? "B1-CM助手-业务链条-发布"
|
||||
: patch.isPublic === false
|
||||
? "B1-CM助手-业务链条-取消发布"
|
||||
: "B1-CM助手-业务链条-编辑",
|
||||
url: `${BASE()}/${id}`,
|
||||
reqParam: { id, ...patch },
|
||||
returnParam: { id: chain.id, title: chain.title },
|
||||
});
|
||||
return chain;
|
||||
}
|
||||
|
||||
export async function deleteChain(id: string): Promise<void> {
|
||||
@@ -197,6 +235,12 @@ export async function deleteChain(id: string): Promise<void> {
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new Error(`delete chain failed (${res.status}): ${text}`);
|
||||
}
|
||||
writeYLog({
|
||||
optType: OptType.delete,
|
||||
optDetail: "B1-CM助手-业务链条-删除",
|
||||
url: `${BASE()}/${id}`,
|
||||
reqParam: { id },
|
||||
});
|
||||
}
|
||||
|
||||
// ── 提示词优化 ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
export interface DashboardSession {
|
||||
taskId: string;
|
||||
@@ -44,7 +45,15 @@ export async function getDashboardSessionByTask(taskId: string): Promise<Dashboa
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new Error(`get dashboard session failed (${res.status}): ${text}`);
|
||||
}
|
||||
return toClient((await res.json()) as ServerDashboardSession);
|
||||
const session = toClient((await res.json()) as ServerDashboardSession);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-大屏会话-查看",
|
||||
url: `${BASE()}/by-task/${taskId}`,
|
||||
reqParam: { taskId },
|
||||
returnParam: { taskId: session.taskId, updatedAt: session.updatedAt },
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
/** upsert 该 taskId 的会话(只传需要更新的字段)。 */
|
||||
@@ -64,5 +73,13 @@ export async function putDashboardSessionByTask(
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new Error(`put dashboard session failed (${res.status}): ${text}`);
|
||||
}
|
||||
return toClient((await res.json()) as ServerDashboardSession);
|
||||
const session = toClient((await res.json()) as ServerDashboardSession);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-大屏会话-保存",
|
||||
url: `${BASE()}/by-task/${taskId}`,
|
||||
reqParam: { taskId, hasTranscript: patch.transcript !== undefined, hasReportJson: patch.reportJson !== undefined },
|
||||
returnParam: { taskId: session.taskId, updatedAt: session.updatedAt },
|
||||
});
|
||||
return session;
|
||||
}
|
||||
@@ -337,7 +337,15 @@ export async function getDraft(id: string): Promise<DraftRecord | null> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(id)}`, { method: "GET" });
|
||||
if (res.status === 404) return null;
|
||||
const data = await readJson<ServerDraftFull>(res, "get draft");
|
||||
return toRecord(data);
|
||||
const record = toRecord(data);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-会商-查看草稿",
|
||||
url: `${BASE()}/${id}`,
|
||||
reqParam: { id },
|
||||
returnParam: { id: record.id, title: record.title },
|
||||
});
|
||||
return record;
|
||||
}
|
||||
|
||||
export async function createDraft(payload: DraftCreatePayload): Promise<DraftRecord> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
import { resolveAllowMultiple } from "@/roundtable-planning/lib/clarification";
|
||||
import { reportRoundtableError, stageForRoundtableAgent } from "@/roundtable-planning/api/diagnostics-report";
|
||||
@@ -921,7 +922,7 @@ export async function listSessionArtifacts(
|
||||
modified_at: string;
|
||||
}>;
|
||||
};
|
||||
return {
|
||||
const result = {
|
||||
threadId: json.thread_id,
|
||||
files: (json.files ?? []).map((f) => ({
|
||||
path: f.path,
|
||||
@@ -931,6 +932,14 @@ export async function listSessionArtifacts(
|
||||
modifiedAt: f.modified_at,
|
||||
})),
|
||||
};
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-会商产物-查询",
|
||||
url: `/api/threads/${threadId}/artifacts`,
|
||||
reqParam: { threadId },
|
||||
returnParam: { count: result.files.length },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
import type { Agent } from "@/core/agents";
|
||||
|
||||
import type { IntentSummary } from "./intent";
|
||||
@@ -268,7 +269,15 @@ export async function listRecommendHistory(draftId: string): Promise<RecommendHi
|
||||
throw new Error(`list recommend history failed (${res.status}): ${text}`);
|
||||
}
|
||||
const data = (await res.json()) as { history: ServerHistoryItem[] };
|
||||
return (data.history ?? []).map(toHistoryItem);
|
||||
const items = (data.history ?? []).map(toHistoryItem);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-席位推荐-查询历史",
|
||||
url: `/api/roundtable-drafts/${draftId}/recommendations`,
|
||||
reqParam: { draftId },
|
||||
returnParam: { count: items.length },
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Append one recommendation run to a draft's history. */
|
||||
@@ -296,7 +305,15 @@ export async function saveRecommendHistory(
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new Error(`save recommend history failed (${res.status}): ${text}`);
|
||||
}
|
||||
return toHistoryItem((await res.json()) as ServerHistoryItem);
|
||||
const item = toHistoryItem((await res.json()) as ServerHistoryItem);
|
||||
writeYLog({
|
||||
optType: OptType.create,
|
||||
optDetail: "B1-CM助手-席位推荐-保存历史",
|
||||
url: `/api/roundtable-drafts/${draftId}/recommendations`,
|
||||
reqParam: { draftId, status: payload.status, picks: payload.picks?.length ?? 0 },
|
||||
returnParam: { id: item.id },
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -189,7 +189,15 @@ export async function resumeJob(jobId: string, answer: string, taskId?: string |
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ answer }),
|
||||
});
|
||||
return mapJob(await readJson<Record<string, unknown>>(res, "resume job"));
|
||||
const job = mapJob(await readJson<Record<string, unknown>>(res, "resume job"));
|
||||
writeYLog({
|
||||
optType: OptType.other,
|
||||
optDetail: "B1-CM助手-会商-回答澄清续跑",
|
||||
url: `${BASE()}/${jobId}/resume`,
|
||||
reqParam: { jobId, taskId: taskId ?? null },
|
||||
returnParam: { id: job.id, status: job.status },
|
||||
});
|
||||
return job;
|
||||
}
|
||||
|
||||
/** 取消作业。`taskId` 非空 → task 共享取消(不分权)。 */
|
||||
|
||||
@@ -50,7 +50,15 @@ export async function getLatestTaskDraftByTask(taskId: string): Promise<DraftRec
|
||||
const res = await apiFetch(`${BASE()}/by-task/${encodeURIComponent(taskId)}/latest`, { method: "GET" });
|
||||
if (res.status === 404) return null;
|
||||
const data = await readJson<ServerDraftFull>(res, "get latest task draft");
|
||||
return toRecord(data);
|
||||
const record = toRecord(data);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-会商任务-查看最新草稿",
|
||||
url: `${BASE()}/by-task/${taskId}/latest`,
|
||||
reqParam: { taskId },
|
||||
returnParam: { id: record.id, title: record.title },
|
||||
});
|
||||
return record;
|
||||
}
|
||||
|
||||
/** Full task draft by id (不分权). Returns null on 404. */
|
||||
@@ -58,7 +66,15 @@ export async function getTaskDraft(id: string): Promise<DraftRecord | null> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(id)}`, { method: "GET" });
|
||||
if (res.status === 404) return null;
|
||||
const data = await readJson<ServerDraftFull>(res, "get task draft");
|
||||
return toRecord(data);
|
||||
const record = toRecord(data);
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-会商任务-查看草稿",
|
||||
url: `${BASE()}/${id}`,
|
||||
reqParam: { id },
|
||||
returnParam: { id: record.id, title: record.title },
|
||||
});
|
||||
return record;
|
||||
}
|
||||
|
||||
/** Create a task draft. `payload.taskId` is required. */
|
||||
|
||||
@@ -1111,12 +1111,19 @@ export default function RoundtablePlanningPage() {
|
||||
if (!step1.intentThreadId || step1.intentReady) return;
|
||||
const title = prefill.taskTitle?.trim();
|
||||
const goal = prefill.taskGoal?.trim();
|
||||
const isXdfx = prefill.businessCode === "7BF";
|
||||
// xdfx(行动分析):除任务名称/描述外,再带上「行动名称」一起交给任务理解智能体。
|
||||
const actionName = prefill.businessCode === "7BF" ? prefill.actionName?.trim() : "";
|
||||
const actionName = isXdfx ? prefill.actionName?.trim() : "";
|
||||
// 末尾附上任务 id(taskId),供智能体识别任务;
|
||||
// xdfx(行动分析)额外附上「行动id(actionId)」。
|
||||
const taskId = prefill.taskId?.trim();
|
||||
const actionId = isXdfx ? prefill.actionId?.trim() : "";
|
||||
const text = [
|
||||
title ? `任务名称:${title}` : "",
|
||||
goal ? `任务描述:${goal}` : "",
|
||||
actionName ? `行动名称:${actionName}` : "",
|
||||
taskId ? `任务id(taskId)为${taskId}` : "",
|
||||
actionId ? `行动id(actionId)为${actionId}` : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { OptType, writeYLog } from "@/core/log/y-log";
|
||||
|
||||
export interface BusinessMapping {
|
||||
businessCode: string;
|
||||
@@ -63,7 +64,14 @@ async function readJson<T>(res: Response, ctx: string): Promise<T> {
|
||||
export async function listBusinessMappings(): Promise<BusinessMapping[]> {
|
||||
const res = await apiFetch(BASE(), { method: "GET" });
|
||||
const data = await readJson<{ mappings: ServerMapping[] }>(res, "list business mappings");
|
||||
return (data.mappings ?? []).map(toMapping);
|
||||
const mappings = (data.mappings ?? []).map(toMapping);
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-业务映射-列表",
|
||||
url: "/api/business-mapping",
|
||||
returnParam: mappings,
|
||||
});
|
||||
return mappings;
|
||||
}
|
||||
|
||||
/** 更新某业务行的智能体 / 业务链选择。404 返回 null。 */
|
||||
@@ -83,5 +91,13 @@ export async function updateBusinessMapping(
|
||||
});
|
||||
if (res.status === 404) return null;
|
||||
const data = await readJson<ServerMapping>(res, "update business mapping");
|
||||
return toMapping(data);
|
||||
const mapping = toMapping(data);
|
||||
writeYLog({
|
||||
optType: OptType.edit,
|
||||
optDetail: "B1-CM助手-业务映射-修改",
|
||||
url: `${BASE()}/${encodeURIComponent(businessCode)}`,
|
||||
reqParam: { businessCode, patch },
|
||||
returnParam: mapping,
|
||||
});
|
||||
return mapping;
|
||||
}
|
||||
@@ -170,7 +170,15 @@ export async function listLightApps(params: ListLightAppsParams = {}): Promise<L
|
||||
|
||||
export async function getLightApp(appId: string): Promise<LightApp> {
|
||||
const res = await apiFetch(`${BASE()}/${encodeURIComponent(appId)}`, { method: "GET" });
|
||||
return toApp(await readJson<ServerApp>(res, "get light app"));
|
||||
const app = toApp(await readJson<ServerApp>(res, "get light app"));
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-轻应用-查看",
|
||||
url: `${BASE()}/${encodeURIComponent(appId)}`,
|
||||
reqParam: { appId },
|
||||
returnParam: app,
|
||||
});
|
||||
return app;
|
||||
}
|
||||
|
||||
export async function createLightApp(input: LightAppInput): Promise<LightApp> {
|
||||
|
||||
@@ -242,7 +242,15 @@ export async function previewReplacements(replacements: Replacement[]): Promise<
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ replacements }),
|
||||
});
|
||||
return toPreviewResult(await readJson<any>(res, "预览"));
|
||||
const result = toPreviewResult(await readJson<any>(res, "预览"));
|
||||
writeYLog({
|
||||
optType: OptType.view,
|
||||
optDetail: "B1-CM助手-技能地址管理-预览",
|
||||
url: `${BASE()}/preview`,
|
||||
reqParam: { replacements },
|
||||
returnParam: result,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 流式应用:逐文件上报进度,返回最终结果。`expectedTotal` 做乐观锁。 */
|
||||
@@ -280,13 +288,20 @@ export async function applyReplacementsStream(
|
||||
export async function listBackups(): Promise<BackupEntry[]> {
|
||||
const res = await apiFetch(`${BASE()}/backups`, { method: "GET" });
|
||||
const data = await readJson<{ backups: any[] }>(res, "读取备份");
|
||||
return (data.backups ?? []).map((b) => ({
|
||||
const backups = (data.backups ?? []).map((b) => ({
|
||||
backupId: b.backup_id,
|
||||
files: b.files ?? 0,
|
||||
createdAt: b.created_at ?? "",
|
||||
replacedTotal: b.replaced_total ?? 0,
|
||||
replacements: (b.replacements ?? []).map((r: any) => ({ from: r.from, to: r.to, kind: r.kind })),
|
||||
}));
|
||||
writeYLog({
|
||||
optType: OptType.search,
|
||||
optDetail: "B1-CM助手-技能地址管理-修改记录",
|
||||
url: `${BASE()}/backups`,
|
||||
returnParam: backups,
|
||||
});
|
||||
return backups;
|
||||
}
|
||||
|
||||
export async function rollbackBackup(backupId: string): Promise<{ restored: number }> {
|
||||
|
||||
Reference in new issue
Block a user