From e372ec552dd8f5b6e30257cec5b0d3c59b06c32c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 21:25:08 +0000 Subject: [PATCH 1/2] Initial plan From c3351f7f9bcff5a9faf5019ea2ea20bfdb22b3ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 21:33:31 +0000 Subject: [PATCH 2/2] Implement real AI API integration for AI Assistant - Create AIService class to handle real API calls to LLM providers (Ollama, OpenAI, Anthropic, custom endpoints) - Add streaming response support for real-time token display - Create useAIChat React hook for managing AI chat state and settings - Update AIAssistant component to use real AI API instead of mock responses - Add error handling, loading states, and abort functionality - Persist model settings to localStorage Co-authored-by: hhongli1979-coder <244808107+hhongli1979-coder@users.noreply.github.com> --- src/components/ai-assistant/AIAssistant.tsx | 249 ++++++++------ src/hooks/useAIChat.ts | 221 ++++++++++++ src/lib/ai-service.ts | 359 ++++++++++++++++++++ 3 files changed, 717 insertions(+), 112 deletions(-) create mode 100644 src/hooks/useAIChat.ts create mode 100644 src/lib/ai-service.ts diff --git a/src/components/ai-assistant/AIAssistant.tsx b/src/components/ai-assistant/AIAssistant.tsx index f8a8db0e..2ac22000 100644 --- a/src/components/ai-assistant/AIAssistant.tsx +++ b/src/components/ai-assistant/AIAssistant.tsx @@ -21,13 +21,19 @@ import { Memory, Gear, Database, + Stop, + Warning, + Trash, } from '@phosphor-icons/react'; import { - generateMockAIAssistantState, + generateMockAICapabilities, + generateMockAIMemories, formatTimeAgo, } from '@/lib/mock-data'; import type { AIMessage, AIMemoryItem, AICapability } from '@/lib/types'; import { AIModelSettingsPanel } from './AIModelSettings'; +import { useAIChat } from '@/hooks/useAIChat'; +import { toast } from 'sonner'; function getCapabilityIcon(iconName: string) { const icons: Record = { @@ -210,67 +216,63 @@ function CapabilityCard({ capability, onToggle }: CapabilityCardProps) { } export function AIAssistant() { - const [state, setState] = useState(generateMockAIAssistantState); + const { + messages, + isLoading, + isStreaming, + currentResponse, + error, + sendMessage, + clearMessages, + abortRequest, + settings, + activeModel, + updateSettings, + } = useAIChat({ + onError: (err) => { + toast.error(`AI错误: ${err.message}`); + }, + }); + const [inputValue, setInputValue] = useState(''); - const [isTyping, setIsTyping] = useState(false); + const [capabilities, setCapabilities] = useState(generateMockAICapabilities); + const [memories] = useState(generateMockAIMemories); const scrollRef = useRef(null); + // Auto-scroll to bottom when messages change useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } - }, [state.currentConversation]); - - const handleSendMessage = () => { - if (!inputValue.trim()) return; - - const userMessage: AIMessage = { - id: `msg-${Date.now()}`, - role: 'user', - content: inputValue, - timestamp: Date.now(), - }; - - setState((prev) => ({ - ...prev, - currentConversation: [...prev.currentConversation, userMessage], - lastActiveAt: Date.now(), - })); + }, [messages, currentResponse]); + const handleSendMessage = async () => { + if (!inputValue.trim() || isLoading) return; + const message = inputValue; setInputValue(''); - setIsTyping(true); - - // Simulate AI response - setTimeout(() => { - const aiResponse: AIMessage = { - id: `msg-${Date.now()}`, - role: 'assistant', - content: generateAIResponse(inputValue), - timestamp: Date.now(), - action: detectAction(inputValue), - }; + await sendMessage(message); + }; - setState((prev) => ({ - ...prev, - currentConversation: [...prev.currentConversation, aiResponse], - lastActiveAt: Date.now(), - })); - setIsTyping(false); - }, 1500); + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } }; const handleToggleCapability = (id: string) => { - setState((prev) => ({ - ...prev, - capabilities: prev.capabilities.map((cap) => + setCapabilities((prev) => + prev.map((cap) => cap.id === id ? { ...cap, enabled: !cap.enabled } : cap - ), - })); + ) + ); }; - const memoryCapabilities = state.capabilities.filter((c) => c.category === 'memory'); - const languageCapabilities = state.capabilities.filter((c) => c.category === 'language'); - const controlCapabilities = state.capabilities.filter((c) => c.category === 'control'); + const memoryCapabilities = capabilities.filter((c) => c.category === 'memory'); + const languageCapabilities = capabilities.filter((c) => c.category === 'language'); + const controlCapabilities = capabilities.filter((c) => c.category === 'control'); + + const isActive = activeModel !== null && activeModel.enabled; return (
@@ -282,13 +284,15 @@ export function AIAssistant() {

AI 智能助手

- 具备记忆、语言理解和全面控制能力的智能助手 + {activeModel + ? `当前模型: ${activeModel.name} (${activeModel.modelName})` + : '请在"模型"标签页中配置并启用一个AI模型'}

- + - {state.isActive ? '活跃中' : '休眠'} + {isActive ? '已连接' : '未连接'} @@ -316,20 +320,71 @@ export function AIAssistant() { - - - 智能对话 - - - 使用自然语言与 AI 助手交流,执行钱包操作 - +
+
+ + + 智能对话 + + + 使用自然语言与 AI 助手交流,执行钱包操作 + +
+ {messages.length > 0 && ( + + )} +
+ {/* Error display */} + {error && ( +
+ + {error} +
+ )} + + {/* No model warning */} + {!activeModel && ( +
+ + 请先在"模型"标签页中配置并启用一个AI模型 +
+ )} + - {state.currentConversation.map((message) => ( + {messages.length === 0 && !isStreaming && ( +
+ +

开始与 AI 助手对话

+

输入您的问题或选择下方的快捷指令

+
+ )} + {messages.map((message) => ( ))} - {isTyping && ( + {/* Streaming response */} + {isStreaming && currentResponse && ( +
+
+
+ + OmniCore AI +
+
{currentResponse}
+
+
+ )} + {/* Loading indicator */} + {isLoading && !isStreaming && (
@@ -342,16 +397,28 @@ export function AIAssistant() {
setInputValue(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()} + onKeyDown={handleKeyDown} className="flex-1" + disabled={!activeModel || isLoading} /> - + {isLoading ? ( + + ) : ( + + )}
{['查看钱包余额', '创建新交易', '分析风险', 'DeFi策略推荐'].map((suggestion) => ( @@ -383,7 +450,7 @@ export function AIAssistant() {
- {state.memories.map((memory) => ( + {memories.map((memory) => ( ))}
@@ -394,15 +461,15 @@ export function AIAssistant() {
-
{state.memories.length}
+
{memories.length}
已学习记忆
- {state.memories.length > 0 + {memories.length > 0 ? Math.round( - state.memories.reduce((acc, m) => acc + m.confidence, 0) / - state.memories.length * + memories.reduce((acc, m) => acc + m.confidence, 0) / + memories.length * 100 ) : 0}% @@ -411,7 +478,7 @@ export function AIAssistant() {
- {state.memories.reduce((acc, m) => acc + m.usageCount, 0)} + {memories.reduce((acc, m) => acc + m.usageCount, 0)}
总使用次数
@@ -495,45 +562,3 @@ export function AIAssistant() {
); } - -// Helper functions for AI responses -function generateAIResponse(input: string): string { - const lowerInput = input.toLowerCase(); - - if (lowerInput.includes('钱包') || lowerInput.includes('余额') || lowerInput.includes('wallet') || lowerInput.includes('balance')) { - return '我已经检查了您的钱包状态。您目前有:\n\n💰 **总资产**: $231,690.75\n\n主要钱包:\n- Treasury Vault: $125,432 (Ethereum)\n- Operating Account: $23,234 (Polygon)\n- DeFi Strategy: $8,024 (Arbitrum)\n\n需要我执行什么操作吗?'; - } - - if (lowerInput.includes('交易') || lowerInput.includes('转账') || lowerInput.includes('transaction') || lowerInput.includes('transfer')) { - return '我可以帮您创建新交易。请提供以下信息:\n\n1. 发送方钱包\n2. 接收地址\n3. 金额和代币\n4. 交易描述\n\n或者您可以说 "从Treasury Vault转账5000 USDC到供应商",我会自动解析。'; - } - - if (lowerInput.includes('风险') || lowerInput.includes('分析') || lowerInput.includes('risk') || lowerInput.includes('analysis')) { - return '🔍 **风险分析报告**\n\n当前待处理交易风险:\n\n⚠️ **高风险** - tx-3 (Operating Account)\n- 大额转账: 25,000 USDT\n- 首次收款地址\n- 建议: 验证收款方身份\n\n✅ **低风险** - tx-1 (Treasury Vault)\n- 已知收款方\n- 常规交易模式\n\n需要我提供更详细的分析吗?'; - } - - if (lowerInput.includes('defi') || lowerInput.includes('策略') || lowerInput.includes('收益')) { - return '📊 **DeFi 策略建议**\n\n基于您的风险偏好,推荐:\n\n1. **稳定币借贷** (Aave V3)\n - APY: 5.2%\n - 风险: 低\n\n2. **ETH 质押** (Lido)\n - APY: 3.8%\n - 风险: 低\n\n3. **流动性挖矿** (Uniswap V3)\n - APY: 12.5%\n - 风险: 中\n\n需要我帮您配置自动投资策略吗?'; - } - - return '感谢您的提问!我是 OmniCore 智能助手,可以帮助您:\n\n• 📊 查询和管理钱包\n• 💸 创建和签署交易\n• 🔍 分析交易风险\n• 📈 管理 DeFi 策略\n• ⚙️ 配置平台设置\n\n请告诉我您需要什么帮助?'; -} - -function detectAction(input: string): AIMessage['action'] | undefined { - const lowerInput = input.toLowerCase(); - - if (lowerInput.includes('钱包') || lowerInput.includes('余额')) { - return { type: 'wallet_query', status: 'completed' }; - } - if (lowerInput.includes('交易') || lowerInput.includes('转账')) { - return { type: 'transaction_create', status: 'pending' }; - } - if (lowerInput.includes('风险') || lowerInput.includes('分析')) { - return { type: 'risk_analyze', status: 'completed' }; - } - if (lowerInput.includes('defi') || lowerInput.includes('策略')) { - return { type: 'defi_manage', status: 'completed' }; - } - - return undefined; -} diff --git a/src/hooks/useAIChat.ts b/src/hooks/useAIChat.ts new file mode 100644 index 00000000..43bfc8ce --- /dev/null +++ b/src/hooks/useAIChat.ts @@ -0,0 +1,221 @@ +import { useState, useCallback, useRef, useEffect } from 'react'; +import type { AIMessage, AIModelConfig, AIModelSettings } from '@/lib/types'; +import { AIService, convertToChatMessages, testModelConnection } from '@/lib/ai-service'; +import { generateMockAIModelSettings } from '@/lib/mock-data'; + +// Local storage key for AI model settings +const AI_SETTINGS_STORAGE_KEY = 'omnicore-ai-settings'; + +/** + * Load AI model settings from localStorage + */ +function loadSettings(): AIModelSettings { + try { + const stored = localStorage.getItem(AI_SETTINGS_STORAGE_KEY); + if (stored) { + return JSON.parse(stored); + } + } catch (error) { + console.error('Failed to load AI settings:', error); + } + return generateMockAIModelSettings(); +} + +/** + * Save AI model settings to localStorage + */ +function saveSettings(settings: AIModelSettings): void { + try { + localStorage.setItem(AI_SETTINGS_STORAGE_KEY, JSON.stringify(settings)); + } catch (error) { + console.error('Failed to save AI settings:', error); + } +} + +export interface UseAIChatOptions { + onError?: (error: Error) => void; +} + +export interface UseAIChatReturn { + // Chat state + messages: AIMessage[]; + isLoading: boolean; + isStreaming: boolean; + currentResponse: string; + error: string | null; + + // Chat actions + sendMessage: (content: string) => Promise; + clearMessages: () => void; + abortRequest: () => void; + + // Model settings + settings: AIModelSettings; + activeModel: AIModelConfig | null; + updateSettings: (settings: AIModelSettings) => void; + testConnection: (modelId?: string) => Promise<{ success: boolean; message: string; latency?: number }>; +} + +/** + * React hook for AI chat functionality with real LLM integration + */ +export function useAIChat(options: UseAIChatOptions = {}): UseAIChatReturn { + const [messages, setMessages] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isStreaming, setIsStreaming] = useState(false); + const [currentResponse, setCurrentResponse] = useState(''); + const [error, setError] = useState(null); + const [settings, setSettings] = useState(loadSettings); + + const aiServiceRef = useRef(null); + const messageIdRef = useRef(0); + + // Get the active/default model + const activeModel = settings.models.find(m => m.id === settings.defaultModelId && m.enabled) + || settings.models.find(m => m.enabled) + || null; + + // Save settings whenever they change + useEffect(() => { + saveSettings(settings); + }, [settings]); + + // Generate unique message ID + const generateMessageId = useCallback(() => { + messageIdRef.current += 1; + return `msg-${Date.now()}-${messageIdRef.current}`; + }, []); + + // Abort current request + const abortRequest = useCallback(() => { + if (aiServiceRef.current) { + aiServiceRef.current.abort(); + aiServiceRef.current = null; + } + setIsLoading(false); + setIsStreaming(false); + }, []); + + // Send a message to the AI + const sendMessage = useCallback(async (content: string) => { + if (!content.trim()) return; + + // Check if we have an active model + if (!activeModel) { + setError('没有可用的AI模型。请在"模型"标签页中配置并启用一个模型。'); + return; + } + + // Add user message + const userMessage: AIMessage = { + id: generateMessageId(), + role: 'user', + content: content.trim(), + timestamp: Date.now(), + }; + + setMessages(prev => [...prev, userMessage]); + setIsLoading(true); + setIsStreaming(false); + setCurrentResponse(''); + setError(null); + + try { + // Create AI service + const service = new AIService(activeModel); + aiServiceRef.current = service; + + // Convert messages for API + const chatMessages = convertToChatMessages( + [...messages, userMessage], + activeModel.systemPrompt + ); + + // Start streaming response + setIsStreaming(true); + + const fullResponse = await service.chat(chatMessages, { + onToken: (token) => { + setCurrentResponse(prev => prev + token); + }, + onComplete: (response) => { + // Add assistant message when complete + const assistantMessage: AIMessage = { + id: generateMessageId(), + role: 'assistant', + content: response, + timestamp: Date.now(), + }; + + setMessages(prev => [...prev, assistantMessage]); + setCurrentResponse(''); + setIsStreaming(false); + setIsLoading(false); + }, + onError: (err) => { + setError(err.message); + setIsStreaming(false); + setIsLoading(false); + options.onError?.(err); + }, + }); + + // If no streaming (shouldn't happen normally) + if (!fullResponse) { + throw new Error('AI模型返回了空响应'); + } + + } catch (err) { + const errorMessage = err instanceof Error ? err.message : '发送消息时发生错误'; + setError(errorMessage); + setIsStreaming(false); + setIsLoading(false); + + if (err instanceof Error) { + options.onError?.(err); + } + } finally { + aiServiceRef.current = null; + } + }, [activeModel, messages, generateMessageId, options]); + + // Clear all messages + const clearMessages = useCallback(() => { + setMessages([]); + setCurrentResponse(''); + setError(null); + }, []); + + // Update settings + const updateSettings = useCallback((newSettings: AIModelSettings) => { + setSettings(newSettings); + }, []); + + // Test connection to a model + const testConnectionFn = useCallback(async (modelId?: string) => { + const modelToTest = modelId + ? settings.models.find(m => m.id === modelId) + : activeModel; + + if (!modelToTest) { + return { success: false, message: '未找到要测试的模型' }; + } + + return await testModelConnection(modelToTest); + }, [settings.models, activeModel]); + + return { + messages, + isLoading, + isStreaming, + currentResponse, + error, + sendMessage, + clearMessages, + abortRequest, + settings, + activeModel, + updateSettings, + testConnection: testConnectionFn, + }; +} diff --git a/src/lib/ai-service.ts b/src/lib/ai-service.ts new file mode 100644 index 00000000..2d788872 --- /dev/null +++ b/src/lib/ai-service.ts @@ -0,0 +1,359 @@ +import type { AIModelConfig, AIMessage } from './types'; + +export interface ChatMessage { + role: 'system' | 'user' | 'assistant'; + content: string; +} + +export interface StreamCallbacks { + onToken: (token: string) => void; + onComplete: (fullResponse: string) => void; + onError: (error: Error) => void; +} + +/** + * AI Service for calling various LLM providers + * Supports: Ollama, OpenAI-compatible APIs, Anthropic, and custom endpoints + */ +export class AIService { + private config: AIModelConfig; + private abortController: AbortController | null = null; + + constructor(config: AIModelConfig) { + this.config = config; + } + + /** + * Abort any ongoing request + */ + abort(): void { + if (this.abortController) { + this.abortController.abort(); + this.abortController = null; + } + } + + /** + * Send a chat message and get a response + * Supports streaming for real-time token display + */ + async chat( + messages: ChatMessage[], + callbacks?: StreamCallbacks + ): Promise { + this.abortController = new AbortController(); + + try { + switch (this.config.provider) { + case 'ollama': + case 'local': + return await this.callOllama(messages, callbacks); + case 'openai': + return await this.callOpenAI(messages, callbacks); + case 'anthropic': + return await this.callAnthropic(messages, callbacks); + case 'custom': + return await this.callCustom(messages, callbacks); + default: + throw new Error(`Unsupported provider: ${this.config.provider}`); + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + throw new Error('请求已取消'); + } + throw error; + } + } + + /** + * Call Ollama API (also works for local Ollama-compatible servers) + */ + private async callOllama( + messages: ChatMessage[], + callbacks?: StreamCallbacks + ): Promise { + const systemMessage = messages.find(m => m.role === 'system'); + const chatMessages = messages.filter(m => m.role !== 'system'); + + // Build the prompt from messages + let prompt = ''; + for (const msg of chatMessages) { + if (msg.role === 'user') { + prompt += `User: ${msg.content}\n`; + } else if (msg.role === 'assistant') { + prompt += `Assistant: ${msg.content}\n`; + } + } + prompt += 'Assistant: '; + + const response = await fetch(this.config.apiEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: this.config.modelName, + prompt: prompt, + system: systemMessage?.content || this.config.systemPrompt, + stream: true, + options: { + temperature: this.config.temperature, + num_predict: this.config.maxTokens, + }, + }), + signal: this.abortController?.signal, + }); + + if (!response.ok) { + throw new Error(`Ollama API error: ${response.status} ${response.statusText}`); + } + + return await this.processStreamResponse(response, callbacks, 'ollama'); + } + + /** + * Call OpenAI-compatible API + */ + private async callOpenAI( + messages: ChatMessage[], + callbacks?: StreamCallbacks + ): Promise { + const response = await fetch(this.config.apiEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }), + }, + body: JSON.stringify({ + model: this.config.modelName, + messages: messages.map(m => ({ role: m.role, content: m.content })), + max_tokens: this.config.maxTokens, + temperature: this.config.temperature, + stream: true, + }), + signal: this.abortController?.signal, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`OpenAI API error: ${response.status} - ${errorText}`); + } + + return await this.processStreamResponse(response, callbacks, 'openai'); + } + + /** + * Call Anthropic API + */ + private async callAnthropic( + messages: ChatMessage[], + callbacks?: StreamCallbacks + ): Promise { + const systemMessage = messages.find(m => m.role === 'system'); + const chatMessages = messages.filter(m => m.role !== 'system'); + + const response = await fetch(this.config.apiEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'anthropic-version': '2023-06-01', + ...(this.config.apiKey && { 'x-api-key': this.config.apiKey }), + }, + body: JSON.stringify({ + model: this.config.modelName, + max_tokens: this.config.maxTokens, + system: systemMessage?.content || this.config.systemPrompt, + messages: chatMessages.map(m => ({ role: m.role, content: m.content })), + stream: true, + }), + signal: this.abortController?.signal, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Anthropic API error: ${response.status} - ${errorText}`); + } + + return await this.processStreamResponse(response, callbacks, 'anthropic'); + } + + /** + * Call custom API endpoint + */ + private async callCustom( + messages: ChatMessage[], + callbacks?: StreamCallbacks + ): Promise { + // Try OpenAI-compatible format first + const response = await fetch(this.config.apiEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }), + }, + body: JSON.stringify({ + model: this.config.modelName, + messages: messages.map(m => ({ role: m.role, content: m.content })), + max_tokens: this.config.maxTokens, + temperature: this.config.temperature, + stream: true, + }), + signal: this.abortController?.signal, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Custom API error: ${response.status} - ${errorText}`); + } + + return await this.processStreamResponse(response, callbacks, 'openai'); + } + + /** + * Process streaming response from various providers + */ + private async processStreamResponse( + response: Response, + callbacks: StreamCallbacks | undefined, + format: 'ollama' | 'openai' | 'anthropic' + ): Promise { + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('No response body'); + } + + const decoder = new TextDecoder(); + let fullResponse = ''; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split('\n').filter(line => line.trim()); + + for (const line of lines) { + let token = ''; + + if (format === 'ollama') { + try { + const data = JSON.parse(line); + token = data.response || ''; + if (data.done) continue; + } catch { + continue; + } + } else if (format === 'openai') { + if (line.startsWith('data: ')) { + const data = line.slice(6); + if (data === '[DONE]') continue; + try { + const parsed = JSON.parse(data); + token = parsed.choices?.[0]?.delta?.content || ''; + } catch { + continue; + } + } + } else if (format === 'anthropic') { + if (line.startsWith('data: ')) { + const data = line.slice(6); + try { + const parsed = JSON.parse(data); + if (parsed.type === 'content_block_delta') { + token = parsed.delta?.text || ''; + } + } catch { + continue; + } + } + } + + if (token) { + fullResponse += token; + callbacks?.onToken(token); + } + } + } + + callbacks?.onComplete(fullResponse); + return fullResponse; + } catch (error) { + if (error instanceof Error) { + callbacks?.onError(error); + } + throw error; + } finally { + reader.releaseLock(); + } + } +} + +/** + * Create AI service instance from model configuration + */ +export function createAIService(config: AIModelConfig): AIService { + return new AIService(config); +} + +/** + * Convert AIMessage array to ChatMessage array for API calls + */ +export function convertToChatMessages( + messages: AIMessage[], + systemPrompt: string +): ChatMessage[] { + const chatMessages: ChatMessage[] = [ + { role: 'system', content: systemPrompt } + ]; + + for (const msg of messages) { + if (msg.role === 'user' || msg.role === 'assistant') { + chatMessages.push({ + role: msg.role, + content: msg.content, + }); + } + } + + return chatMessages; +} + +/** + * Test connection to an AI model endpoint + */ +export async function testModelConnection(config: AIModelConfig): Promise<{ + success: boolean; + message: string; + latency?: number; +}> { + const startTime = Date.now(); + + try { + const service = new AIService(config); + const response = await service.chat([ + { role: 'system', content: '你是一个测试助手。' }, + { role: 'user', content: '请回复"连接成功"' }, + ]); + + const latency = Date.now() - startTime; + + if (response && response.length > 0) { + return { + success: true, + message: `连接成功!响应: "${response.slice(0, 50)}${response.length > 50 ? '...' : ''}"`, + latency, + }; + } else { + return { + success: false, + message: '模型返回空响应', + }; + } + } catch (error) { + return { + success: false, + message: error instanceof Error ? error.message : '未知错误', + }; + } +}