feat(ui,docs): Agent 流式侧栏 + 结果区/MCP 点/确认条 + 官网文案

- 侧栏消费 Agent SSE:时间线边 append、正文 ▌、停止按钮
- 「在结果区打开」不抢编辑器 focus;MCP 活跃点;写操作确认条
- 文档/落地页补充 Ask|Agent 与 npx @rlwj-lab/opsxdb-mcp(不宣传默认可写)
This commit is contained in:
logan committed 2026-10-15 13:00:58 +08:00
1 parent 2336c9433c
commit 979efb606d
9 files changed
+580 -92

No files matched your search

+50 -3
View File
@@ -189,6 +189,14 @@ export type ConfirmRequest = {
items?: string[];
};
export type AiAgentQueryResultView = {
columns: string[];
rows: Record<string, unknown>[];
rowCount: number;
sql?: string;
tool?: string;
};
export type AiAgentStepView = {
tool: string;
args?: string;
@@ -196,6 +204,10 @@ export type AiAgentStepView = {
summary: string;
error?: string;
ms: number;
pending?: boolean;
needConfirm?: boolean;
confirmSql?: string;
result?: AiAgentQueryResultView;
};
export type AiMessage = {
@@ -205,6 +217,7 @@ export type AiMessage = {
at: string;
/** Agent 工具步骤时间线(仅 assistant) */
steps?: AiAgentStepView[];
streaming?: boolean;
};
export type AiChatMode = "ask" | "agent";
@@ -320,11 +333,14 @@ type State = {
updateActiveSql: (sql: string) => void;
/** AI 异步返回只写入原标签;用户编辑或切库后跳过。 */
updateTabSqlIfUnchanged: (id: string, originalSql: string, connectionId: string | undefined, database: string | undefined, sql: string) => boolean;
pushAi: (role: AiMessage["role"], content: string, steps?: AiAgentStepView[]) => void;
pushAi: (role: AiMessage["role"], content: string, steps?: AiAgentStepView[], extra?: Partial<AiMessage>) => string;
patchAiMessage: (id: string, patch: Partial<AiMessage> | ((m: AiMessage) => AiMessage)) => void;
setSession: (connectionId: string, sessionId: string | null) => void;
pushHistory: (item: Omit<QueryHistoryItem, "id" | "at"> & { at?: number }) => void;
clearHistory: () => void;
setResultSets: (items: ResultSetItem[]) => void;
/** AI「在结果区打开」:新开/切换结果页,不改 SQL Tab、不抢编辑器 focus */
openAiResultSet: (label: string, sql: string, result: QueryResult) => void;
selectResultSet: (id: string) => void;
/** C5/G3:局部更新某个结果集(翻页结果、视图持久化);若为激活项同步 result */
updateResultSet: (id: string, patch: Partial<Omit<ResultSetItem, "id">>) => void;
@@ -894,14 +910,33 @@ export const useWorkspaceStore = create<State>((set, get) => ({
return true;
},
pushAi: (role, content, steps) =>
pushAi: (role, content, steps, extra) => {
const id = crypto.randomUUID();
set((s) => ({
aiMessages: [
...s.aiMessages,
{ id: crypto.randomUUID(), role, content, at: new Date().toLocaleTimeString(), steps },
{
id,
role,
content,
at: new Date().toLocaleTimeString(),
steps,
...extra,
},
],
}));
return id;
},
patchAiMessage: (id, patch) =>
set((s) => ({
aiMessages: s.aiMessages.map((m) => {
if (m.id !== id) return m;
return typeof patch === "function" ? patch(m) : { ...m, ...patch };
}),
})),
setSession: (connectionId, sessionId) => {
const sessions = { ...get().sessions };
if (sessionId) sessions[connectionId] = sessionId;
@@ -942,6 +977,18 @@ export const useWorkspaceStore = create<State>((set, get) => ({
resultTab: "data",
});
},
openAiResultSet: (label, sql, result) => {
const id = crypto.randomUUID();
const item: ResultSetItem = { id, label: label || "AI 结果", sql: sql || "", result };
const prev = get().resultSets.filter((x) => x.id !== id);
set({
resultSets: [item, ...prev].slice(0, 30),
activeResultSetId: id,
result,
resultTab: "data",
});
},
selectResultSet: (id) => {
const item = get().resultSets.find((x) => x.id === id);