From 3b5a493cdae58abf13c92dd080b314670fa15c5f Mon Sep 17 00:00:00 2001 From: Huarch Date: Wed, 29 Apr 2026 15:33:08 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=82=E9=85=8D=E6=96=B0=E7=9A=84=20opencode?= =?UTF-8?q?=20Agent=20=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 +- .gitea/workflows/package.yml | 2 +- Dockerfile | 2 +- docker-compose.yml | 2 +- src/components/chat/GlobalChatbox.tsx | 26 ++++++++++---------- src/components/chat/GlobalChatbox.types.ts | 2 +- src/components/chat/GlobalChatbox.utils.ts | 12 +++++----- src/config/config.ts | 2 +- src/lib/chatStream.test.ts | 28 +++++++++++----------- src/lib/chatStream.ts | 28 +++++++++++----------- 10 files changed, 53 insertions(+), 53 deletions(-) diff --git a/.env b/.env index 04d8be8..14d3546 100644 --- a/.env +++ b/.env @@ -6,7 +6,7 @@ NEXTAUTH_URL="https://demo.waternetwork.cn/" # 为前端暴露的变量添加 NEXT_PUBLIC_ 前缀 NEXT_PUBLIC_BACKEND_URL="https://server.waternetwork.cn" -NEXT_PUBLIC_COPILOT_URL="https://agent.waternetwork.cn" +NEXT_PUBLIC_AGENT_URL="https://agent.waternetwork.cn" NEXT_PUBLIC_AUDIO_SERVICE_URL="https://tts.waternetwork.cn" NEXT_PUBLIC_MAP_URL="https://geoserver.waternetwork.cn/geoserver" NEXT_PUBLIC_MAP_WORKSPACE="tjwater" diff --git a/.gitea/workflows/package.yml b/.gitea/workflows/package.yml index 639e848..26fd48d 100644 --- a/.gitea/workflows/package.yml +++ b/.gitea/workflows/package.yml @@ -102,7 +102,7 @@ jobs: -t "${IMAGE_NAME}:${IMAGE_TAG}" \ -t "${IMAGE_NAME}:latest" \ --build-arg NEXT_PUBLIC_BACKEND_URL="${{ vars.NEXT_PUBLIC_BACKEND_URL }}" \ - --build-arg NEXT_PUBLIC_COPILOT_URL="${{ vars.NEXT_PUBLIC_COPILOT_URL }}" \ + --build-arg NEXT_PUBLIC_AGENT_URL="${{ vars.NEXT_PUBLIC_AGENT_URL }}" \ --build-arg NEXT_PUBLIC_AUDIO_SERVICE_URL="${{ vars.NEXT_PUBLIC_AUDIO_SERVICE_URL }}" \ --build-arg NEXT_PUBLIC_MAP_URL="${{ vars.NEXT_PUBLIC_MAP_URL }}" \ --build-arg NEXT_PUBLIC_MAP_WORKSPACE="${{ vars.NEXT_PUBLIC_MAP_WORKSPACE }}" \ diff --git a/Dockerfile b/Dockerfile index 282bfa8..7da72fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ FROM base AS builder # 只定义 ARG 接收来自构建命令或 docker-compose.yaml 的参数 # Next.js 在 build 时会自动读取同名的 ARG 作为环境变量 ARG NEXT_PUBLIC_BACKEND_URL -ARG NEXT_PUBLIC_COPILOT_URL +ARG NEXT_PUBLIC_AGENT_URL ARG NEXT_PUBLIC_AUDIO_SERVICE_URL ARG NEXT_PUBLIC_MAP_URL ARG NEXT_PUBLIC_MAP_WORKSPACE diff --git a/docker-compose.yml b/docker-compose.yml index 52a00e8..b8d860b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: dockerfile: Dockerfile args: NEXT_PUBLIC_BACKEND_URL: ${NEXT_PUBLIC_BACKEND_URL} - NEXT_PUBLIC_COPILOT_URL: ${NEXT_PUBLIC_COPILOT_URL} + NEXT_PUBLIC_AGENT_URL: ${NEXT_PUBLIC_AGENT_URL} NEXT_PUBLIC_AUDIO_SERVICE_URL: ${NEXT_PUBLIC_AUDIO_SERVICE_URL} NEXT_PUBLIC_MAP_URL: ${NEXT_PUBLIC_MAP_URL} NEXT_PUBLIC_MAP_WORKSPACE: ${NEXT_PUBLIC_MAP_WORKSPACE} diff --git a/src/components/chat/GlobalChatbox.tsx b/src/components/chat/GlobalChatbox.tsx index 90854e8..d01aa9b 100644 --- a/src/components/chat/GlobalChatbox.tsx +++ b/src/components/chat/GlobalChatbox.tsx @@ -32,7 +32,7 @@ import KeyboardArrowDownRounded from "@mui/icons-material/KeyboardArrowDownRound import KeyboardArrowUpRounded from "@mui/icons-material/KeyboardArrowUpRounded"; // Logic -import { streamCopilotChat } from "@/lib/chatStream"; +import { streamAgentChat } from "@/lib/chatStream"; import type { StreamEvent } from "@/lib/chatStream"; import { useChatToolStore, @@ -60,8 +60,8 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { const [isStreaming, setIsStreaming] = useState(false); const [width, setWidth] = useState(480); const [isResizing, setIsResizing] = useState(false); - const [conversationId, setConversationId] = useState( - initialChatStateRef.current.conversationId + const [sessionId, setSessionId] = useState( + initialChatStateRef.current.sessionId ); const [headerMenuAnchorEl, setHeaderMenuAnchorEl] = useState(null); const [isPresetPanelOpen, setIsPresetPanelOpen] = useState(false); @@ -117,13 +117,13 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { }, [open]); useEffect(() => { - const state: PersistedChatState = { messages, conversationId }; + const state: PersistedChatState = { messages, sessionId }; try { window.localStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(state)); } catch (error) { console.error("[GlobalChatbox] Failed to persist chat state:", error); } - }, [messages, conversationId]); + }, [messages, sessionId]); const sendPrompt = useCallback( async (rawPrompt: string) => { @@ -291,13 +291,13 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { }; try { - await streamCopilotChat({ + await streamAgentChat({ message: prompt, - conversationId, + sessionId, signal: controller.signal, onEvent: (event) => { if (event.type === "token") { - if (!conversationId && event.conversationId) setConversationId(event.conversationId); + if (!sessionId && event.sessionId) setSessionId(event.sessionId); const normalizedToken = normalizeThoughtTagToken(event.content); setMessages((prev) => prev.map((m) => @@ -307,13 +307,13 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { ) ); } else if (event.type === "done") { - if (!conversationId && event.conversationId) setConversationId(event.conversationId); + if (!sessionId && event.sessionId) setSessionId(event.sessionId); setMessages((prev) => prev.map((m) => m.id === assistantId && m.content.trim().length === 0 ? { ...m, - content: "⚠️ **错误:** Copilot 未返回内容,请稍后重试。", + content: "⚠️ **错误:** Agent 未返回内容,请稍后重试。", isError: true, } : m @@ -358,7 +358,7 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { setIsStreaming(false); } }, - [conversationId, isStreaming, stopListening, dispatchToolAction], + [sessionId, isStreaming, stopListening, dispatchToolAction], ); const handleSend = async () => { @@ -573,7 +573,7 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { - Copilot + Agent 你的 AI 助手 @@ -834,7 +834,7 @@ export const GlobalChatbox: React.FC = ({ open, onClose }) => { void handleSend(); } }} - placeholder="输入消息给 Copilot..." + placeholder="输入消息给 Agent..." fullWidth multiline maxRows={3} diff --git a/src/components/chat/GlobalChatbox.types.ts b/src/components/chat/GlobalChatbox.types.ts index d7546ce..b7155da 100644 --- a/src/components/chat/GlobalChatbox.types.ts +++ b/src/components/chat/GlobalChatbox.types.ts @@ -14,5 +14,5 @@ export type SpeechState = "idle" | "playing" | "paused"; export type PersistedChatState = { messages: Message[]; - conversationId?: string; + sessionId?: string; }; diff --git a/src/components/chat/GlobalChatbox.utils.ts b/src/components/chat/GlobalChatbox.utils.ts index ff0b069..94846a2 100644 --- a/src/components/chat/GlobalChatbox.utils.ts +++ b/src/components/chat/GlobalChatbox.utils.ts @@ -2,7 +2,7 @@ import type { PersistedChatState } from "./GlobalChatbox.types"; export const createId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; -export const CHAT_STORAGE_KEY = "tjwater_copilot_chat_state_v1"; +export const CHAT_STORAGE_KEY = "tjwater_agent_chat_state_v1"; const THINK_TAG_ALIAS_PATTERN = /<\s*(\/?)\s*(thinking|reasoning|thought)\b[^>]*>/gi; export const PRESET_PROMPTS = [ @@ -36,24 +36,24 @@ export const stripMarkdown = (md: string): string => export const getInitialChatState = (): PersistedChatState => { if (typeof window === "undefined") { - return { messages: [], conversationId: undefined }; + return { messages: [], sessionId: undefined }; } try { const storedRaw = window.localStorage.getItem(CHAT_STORAGE_KEY); - if (!storedRaw) return { messages: [], conversationId: undefined }; + if (!storedRaw) return { messages: [], sessionId: undefined }; const parsed = JSON.parse(storedRaw) as PersistedChatState; if (!Array.isArray(parsed.messages)) { console.error("[GlobalChatbox] Invalid persisted messages format."); window.localStorage.removeItem(CHAT_STORAGE_KEY); - return { messages: [], conversationId: undefined }; + return { messages: [], sessionId: undefined }; } - return { messages: parsed.messages, conversationId: parsed.conversationId }; + return { messages: parsed.messages, sessionId: parsed.sessionId }; } catch (error) { console.error( "[GlobalChatbox] Failed to read persisted chat state:", error, ); window.localStorage.removeItem(CHAT_STORAGE_KEY); - return { messages: [], conversationId: undefined }; + return { messages: [], sessionId: undefined }; } }; diff --git a/src/config/config.ts b/src/config/config.ts index a5bf841..b2126e3 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,6 +1,6 @@ export const config = { BACKEND_URL: process.env.NEXT_PUBLIC_BACKEND_URL || "http://127.0.0.1:8000", - COPILOT_URL: process.env.NEXT_PUBLIC_COPILOT_URL || "http://127.0.0.1:8787", + AGENT_URL: process.env.NEXT_PUBLIC_AGENT_URL || "http://127.0.0.1:8788", AUDIO_SERVICE_URL: process.env.NEXT_PUBLIC_AUDIO_SERVICE_URL || "http://127.0.0.1:18083", MAP_URL: process.env.NEXT_PUBLIC_MAP_URL || "http://127.0.0.1:8080/geoserver", diff --git a/src/lib/chatStream.test.ts b/src/lib/chatStream.test.ts index c138d8a..4528f51 100644 --- a/src/lib/chatStream.test.ts +++ b/src/lib/chatStream.test.ts @@ -1,4 +1,4 @@ -import { streamCopilotChat } from "./chatStream"; +import { streamAgentChat } from "./chatStream"; import { ReadableStream } from "stream/web"; import { TextEncoder, TextDecoder } from "util"; @@ -32,7 +32,7 @@ const makeStream = (chunks: string[]) => }, }); -describe("streamCopilotChat", () => { +describe("streamAgentChat", () => { beforeEach(() => { apiFetch.mockReset(); }); @@ -41,21 +41,21 @@ describe("streamCopilotChat", () => { apiFetch.mockResolvedValue({ ok: true, body: makeStream([ - 'event: token\ndata: {"conversationId":"c1","content":"he"}\n\n', - 'event: token\ndata: {"conversationId":"c1","content":"llo"}\n\n', - 'event: done\ndata: {"conversationId":"c1"}\n\n', + 'event: token\ndata: {"session_id":"s1","content":"he"}\n\n', + 'event: token\ndata: {"session_id":"s1","content":"llo"}\n\n', + 'event: done\ndata: {"session_id":"s1"}\n\n', ]), }); - const events: Array<{ type: string; content?: string; conversationId?: string }> = []; + const events: Array<{ type: string; content?: string; sessionId?: string }> = []; - await streamCopilotChat({ + await streamAgentChat({ message: "hi", onEvent: (event) => events.push(event), }); expect(apiFetch).toHaveBeenCalledWith( - expect.stringContaining("/api/v1/copilot/chat/stream"), + expect.stringContaining("/api/v1/agent/chat/stream"), expect.objectContaining({ method: "POST", projectHeaderMode: "include", @@ -64,9 +64,9 @@ describe("streamCopilotChat", () => { ); expect(events).toEqual([ - { type: "token", conversationId: "c1", content: "he" }, - { type: "token", conversationId: "c1", content: "llo" }, - { type: "done", conversationId: "c1" }, + { type: "token", sessionId: "s1", content: "he" }, + { type: "token", sessionId: "s1", content: "llo" }, + { type: "done", sessionId: "s1" }, ]); }); @@ -78,7 +78,7 @@ describe("streamCopilotChat", () => { }); const events: Array<{ type: string; message?: string; detail?: string }> = []; - await streamCopilotChat({ + await streamAgentChat({ message: "hi", onEvent: (event) => events.push(event), }); @@ -97,7 +97,7 @@ describe("streamCopilotChat", () => { }); const events: Array<{ type: string; message?: string; detail?: string }> = []; - await streamCopilotChat({ + await streamAgentChat({ message: "hi", onEvent: (event) => events.push(event), }); @@ -111,7 +111,7 @@ describe("streamCopilotChat", () => { apiFetch.mockRejectedValue(new TypeError("Failed to fetch")); const events: Array<{ type: string; message?: string; detail?: string }> = []; - await streamCopilotChat({ + await streamAgentChat({ message: "hi", onEvent: (event) => events.push(event), }); diff --git a/src/lib/chatStream.ts b/src/lib/chatStream.ts index 3745809..772658c 100644 --- a/src/lib/chatStream.ts +++ b/src/lib/chatStream.ts @@ -2,24 +2,24 @@ import { apiFetch } from "@/lib/apiFetch"; import { config } from "@config/config"; export type StreamEvent = - | { type: "token"; conversationId: string; content: string } - | { type: "done"; conversationId: string } + | { type: "token"; sessionId: string; content: string } + | { type: "done"; sessionId: string } | { type: "error"; - conversationId?: string; + sessionId?: string; message: string; detail?: string; } | { type: "tool_call"; - conversationId: string; + sessionId: string; tool: string; params: Record; }; type StreamOptions = { message: string; - conversationId?: string; + sessionId?: string; signal?: AbortSignal; onEvent: (event: StreamEvent) => void; }; @@ -43,16 +43,16 @@ const parseEventBlock = (block: string): { event?: string; data?: string } => { }; }; -export const streamCopilotChat = async ({ +export const streamAgentChat = async ({ message, - conversationId, + sessionId, signal, onEvent, }: StreamOptions) => { let response: Response; try { response = await apiFetch( - `${config.COPILOT_URL}/api/v1/copilot/chat/stream`, + `${config.AGENT_URL}/api/v1/agent/chat/stream`, { method: "POST", signal, @@ -62,7 +62,7 @@ export const streamCopilotChat = async ({ }, body: JSON.stringify({ message, - conversation_id: conversationId, + session_id: sessionId, }), projectHeaderMode: "include", skipAuthRedirect: true, @@ -115,7 +115,7 @@ export const streamCopilotChat = async ({ try { const parsed = JSON.parse(data) as { - conversationId?: string; + session_id?: string; content?: string; message?: string; detail?: string; @@ -125,25 +125,25 @@ export const streamCopilotChat = async ({ if (event === "token") { onEvent({ type: "token", - conversationId: parsed.conversationId ?? "", + sessionId: parsed.session_id ?? "", content: parsed.content ?? "", }); } else if (event === "done") { onEvent({ type: "done", - conversationId: parsed.conversationId ?? "", + sessionId: parsed.session_id ?? "", }); } else if (event === "error") { onEvent({ type: "error", - conversationId: parsed.conversationId, + sessionId: parsed.session_id, message: parsed.message ?? "unknown error", detail: parsed.detail, }); } else if (event === "tool_call") { onEvent({ type: "tool_call", - conversationId: parsed.conversationId ?? "", + sessionId: parsed.session_id ?? "", tool: parsed.tool ?? "", params: parsed.params ?? {}, });