Download src/store/agentStore.js from shutajb/agent: direct link, hf CLI and curl.
- Browser
- Download file 5.34 kB
-
https://huggingface.co/spaces/shutajb/agent/resolve/main/src/store/agentStore.js
- Command line
-
hf download hf://spaces/shutajb/agent/src/store/agentStore.js
-
curl -L -o agentStore.js https://huggingface.co/spaces/shutajb/agent/resolve/main/src/store/agentStore.js
5.34 kB
| import { create } from 'zustand' | |
| import { toOpenAITools } from '../utils/tools' | |
| import { safeCalc } from '../utils/calc' | |
| const STEP_LIMIT = 6 | |
| const TIMEOUT_MS = 120000 | |
| const DEFAULT_MODEL = 'nvidia/nemotron-3-ultra-550b-a55b:free' | |
| /* ------------------------------------------------------------------ * | |
| * 工具執行 | |
| * ------------------------------------------------------------------ */ | |
| function executeTool(call) { | |
| const name = call?.function?.name | |
| let args | |
| try { | |
| args = JSON.parse(call?.function?.arguments || '{}') | |
| } catch { | |
| return '工具參數不是合法 JSON' | |
| } | |
| switch (name) { | |
| case 'search_web': | |
| return `【search_web】已查詢「${args.query}」。` + | |
| '(純前端環境沒有網路存取權限,此為模擬回應;要真的搜尋需改接後端服務)' | |
| case 'calculate': | |
| return safeCalc(args.expression) | |
| default: | |
| return `【${name || '未知工具'}】此環境未實作,收到參數 ${JSON.stringify(args)}` | |
| } | |
| } | |
| /* ------------------------------------------------------------------ * | |
| * 前置檢查與單一請求 | |
| * ------------------------------------------------------------------ */ | |
| function validateConfig(apiConfig) { | |
| if (!apiConfig) return '尚未設定 API 連線' | |
| if (!apiConfig.apiKey?.trim()) return '尚未設定 API Key(請先開「設定」填入)' | |
| if (!apiConfig.supplier?.trim()) return '尚未設定 API URL' | |
| if (!apiConfig.model?.trim()) return '尚未設定 Model 名稱' | |
| return '' | |
| } | |
| async function requestCompletion(messages, toolsList, apiConfig) { | |
| const base = String(apiConfig.supplier).replace(/\/+$/, '') | |
| const controller = new AbortController() | |
| const timer = setTimeout(() => controller.abort(), TIMEOUT_MS) | |
| try { | |
| const res = await fetch(`${base}/chat/completions`, { | |
| method: 'POST', | |
| signal: controller.signal, | |
| headers: { | |
| Authorization: `Bearer ${apiConfig.apiKey.trim()}`, | |
| 'Content-Type': 'application/json', | |
| 'HTTP-Referer': typeof window !== 'undefined' ? window.location.origin : 'https://agent-studio', | |
| 'X-Title': 'Agent Studio', | |
| }, | |
| body: JSON.stringify({ | |
| model: apiConfig.model, | |
| messages, | |
| tools: toOpenAITools(toolsList), | |
| stream: false, | |
| }), | |
| }) | |
| let data = null | |
| try { data = await res.json() } catch { /* 非 JSON 回應 */ } | |
| if (!res.ok) { | |
| const detail = data?.error?.message | |
| || data?.error?.data?.message | |
| || data?.message | |
| || (typeof data === 'string' ? data : '無錯誤訊息') | |
| return { ok: false, error: `API 錯誤 ${res.status}:${detail}` } | |
| } | |
| return { ok: true, data } | |
| } catch (e) { | |
| if (e.name === 'AbortError') { | |
| return { ok: false, error: `請求逾時(超過 ${TIMEOUT_MS / 1000} 秒未回應)` } | |
| } | |
| return { ok: false, error: `網路錯誤:${e.message}` } | |
| } finally { | |
| clearTimeout(timer) | |
| } | |
| } | |
| /* ------------------------------------------------------------------ * | |
| * Store | |
| * ------------------------------------------------------------------ */ | |
| export const useAgentStore = create((set, get) => ({ | |
| messages: [], | |
| running: false, | |
| step: 0, | |
| error: '', | |
| reset() { | |
| if (get().running) return | |
| set({ messages: [], step: 0, error: '' }) | |
| }, | |
| async runAgent(input, toolsList, apiConfig, agent) { | |
| const preflight = validateConfig(apiConfig) | |
| if (preflight) { | |
| set({ error: preflight }) | |
| return { ok: false, error: preflight } | |
| } | |
| if (get().running) { | |
| set({ error: '已有任務正在執行,請稍候或清空對話' }) | |
| return { ok: false, error: 'running' } | |
| } | |
| // system prompt 每輪重設,歷史訊息保留 | |
| const history = get().messages.filter((m) => m.role !== 'system') | |
| const seed = agent?.systemPrompt | |
| ? [{ role: 'system', content: agent.systemPrompt }, ...history] | |
| : history | |
| set({ running: true, error: '', step: 0 }) | |
| const userMsg = { role: 'user', content: input } | |
| set((s) => ({ messages: [...s.messages, userMsg] })) | |
| const messages = [...seed, userMsg] | |
| const add = (msg) => set((s) => ({ messages: [...s.messages, msg], error: '' })) | |
| for (let step = 1; step <= STEP_LIMIT; step++) { | |
| set({ step }) | |
| const result = await requestCompletion(messages, toolsList, apiConfig) | |
| if (!result.ok) { | |
| add({ role: 'assistant', content: `⚠️ ${result.error}` }) | |
| break | |
| } | |
| const message = result.data?.choices?.[0]?.message | |
| if (!message) { | |
| add({ role: 'assistant', content: '⚠️ API 回應格式異常(choices 為空)' }) | |
| break | |
| } | |
| const assistantMsg = { | |
| role: 'assistant', | |
| content: message.content || '', | |
| tool_calls: message.tool_calls || [], | |
| } | |
| messages.push(assistantMsg) | |
| add(assistantMsg) | |
| const calls = assistantMsg.tool_calls | |
| if (calls.length === 0) break | |
| for (const call of calls) { | |
| const toolMsg = { | |
| role: 'tool', | |
| tool_call_id: call.id, | |
| name: call.function?.name, | |
| arguments: call.function?.arguments, | |
| content: executeTool(call), | |
| } | |
| messages.push(toolMsg) | |
| add(toolMsg) | |
| } | |
| } | |
| set({ running: false, step: 0 }) | |
| return { ok: true } | |
| }, | |
| })) | |