diff --git a/src/api/ai/agent/index.ts b/src/api/ai/agent/index.ts new file mode 100644 index 0000000..0366114 --- /dev/null +++ b/src/api/ai/agent/index.ts @@ -0,0 +1,148 @@ +import type { AxiosPromise } from '@/utils/api-types'; +import request from '@/utils/request'; +import { getToken } from '@/utils/auth'; +import type { AgentChatRequest, AgentItem, ConversationMessage, ConversationSummaryList, SnailOpenApiUser } from './types'; + +export const fetchMyAgents = (): AxiosPromise => { + return request({ + url: '/snail-ai/agents', + method: 'get' + }); +}; + +export const fetchAgentDetail = (id: number): AxiosPromise => { + return request({ + url: `/snail-ai/agent/${id}`, + method: 'get' + }); +}; + +export const fetchAgentConversations = ( + id: number, + params: { page?: number; size?: number; start?: string; end?: string } +): AxiosPromise => { + return request({ + url: `/snail-ai/agent/${id}/conversations`, + method: 'get', + params + }); +}; + +export const fetchConversationMessages = (agentId: number, conversationId: string): AxiosPromise => { + return request({ + url: `/snail-ai/agent/${agentId}/conversation/${conversationId}/messages`, + method: 'get' + }); +}; + +export const createConversation = (agentId: number, data: { title?: string }): AxiosPromise<{ conversationId: string; title?: string }> => { + return request({ + url: `/snail-ai/agent/${agentId}/conversation`, + method: 'post', + data + }); +}; + +export const registerCurrentSnailUser = (): AxiosPromise => { + return request({ + url: '/snail-ai/user/register', + method: 'post' + }); +}; + +export const fetchChatMode = (): AxiosPromise<{ mode?: 'stream' | 'sync' }> => { + return request({ + url: '/snail-ai/chat/mode', + method: 'get' + }); +}; + +export const fetchAgentChat = ( + agentId: number, + data: AgentChatRequest, + options: { + onMessage: (chunk: string) => void; + onThinking?: (chunk: string) => void; + onDone: () => void; + onError: (error: Error) => void; + signal?: AbortSignal; + } +) => { + const baseURL = import.meta.env.VITE_APP_BASE_API; + const token = getToken(); + const query = new URLSearchParams({ content: data.content }); + if (data.conversationId) { + query.set('conversationId', data.conversationId); + } + + fetch(`${baseURL}/snail-ai/agent/${agentId}/chat/stream?${query.toString()}`, { + method: 'GET', + headers: { + Authorization: token ? `Bearer ${token}` : '', + clientid: import.meta.env.VITE_APP_CLIENT_ID + }, + signal: options.signal + }) + .then(async response => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('ReadableStream not supported'); + } + + const decoder = new TextDecoder(); + let buffer = ''; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const eventBlocks = buffer.split('\n\n'); + buffer = eventBlocks.pop() || ''; + + for (const block of eventBlocks) { + if (!block.trim()) continue; + let eventName = 'message'; + let payload = ''; + for (const line of block.split('\n')) { + if (line.startsWith('event:')) { + eventName = line.slice(6).trim(); + } else if (line.startsWith('data:')) { + payload += line.slice(5).trim(); + } + } + if (!payload) continue; + if (eventName === 'thinking') { + options.onThinking?.(payload); + } else if (eventName === 'text') { + options.onMessage(payload); + } else if (eventName === 'done') { + options.onDone(); + return; + } else if (eventName === 'error') { + throw new Error(payload || 'SSE stream error'); + } else { + options.onMessage(payload); + } + } + } + + options.onDone(); + }) + .catch((error: Error) => { + if (error.name !== 'AbortError') { + options.onError(error); + } + }); +}; + +export const fetchAgentChatSync = (agentId: number, data: AgentChatRequest): AxiosPromise => { + return request({ + url: `/snail-ai/agent/${agentId}/chat/sync`, + method: 'post', + data + }); +}; diff --git a/src/api/ai/agent/types.ts b/src/api/ai/agent/types.ts new file mode 100644 index 0000000..8372591 --- /dev/null +++ b/src/api/ai/agent/types.ts @@ -0,0 +1,45 @@ +export interface AgentItem { + id: number; + name: string; + description?: string; + avatar?: string; + greeting?: string; + presetQuestions?: string[]; + webSearchEnabled?: boolean; +} + +export interface ConversationSummaryItem { + conversationId: string; + title: string; + lastMessageDt?: string; + createDt?: string; +} + +export interface ConversationSummaryList { + data: ConversationSummaryItem[]; + page?: number; + size?: number; + total?: number; +} + +export interface ConversationMessage { + role?: string; + content?: string; + thinking?: string; +} + +export interface AgentChatRequest { + conversationId?: string; + content: string; + disabledMcpServerIds?: number[]; + disabledSkillIds?: number[]; + deepPlanEnabled?: boolean; + webSearchEnabled?: boolean; +} + +export interface SnailOpenApiUser { + openId: string; + nickname?: string; + externalId?: string; + created?: boolean; +} diff --git a/src/api/ai/index.ts b/src/api/ai/index.ts new file mode 100644 index 0000000..81bac90 --- /dev/null +++ b/src/api/ai/index.ts @@ -0,0 +1,2 @@ +export * from './agent'; +export * from './agent/types'; diff --git a/src/views/ai/chat/index.vue b/src/views/ai/chat/index.vue new file mode 100644 index 0000000..72f0fb2 --- /dev/null +++ b/src/views/ai/chat/index.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/src/views/ai/chat/modules/chat-input.vue b/src/views/ai/chat/modules/chat-input.vue new file mode 100644 index 0000000..2413715 --- /dev/null +++ b/src/views/ai/chat/modules/chat-input.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/views/ai/chat/modules/chat-main.vue b/src/views/ai/chat/modules/chat-main.vue new file mode 100644 index 0000000..b8fbef1 --- /dev/null +++ b/src/views/ai/chat/modules/chat-main.vue @@ -0,0 +1,422 @@ + + + + + diff --git a/src/views/ai/chat/modules/chat-sidebar.vue b/src/views/ai/chat/modules/chat-sidebar.vue new file mode 100644 index 0000000..f7f0bd8 --- /dev/null +++ b/src/views/ai/chat/modules/chat-sidebar.vue @@ -0,0 +1,184 @@ + + + + +