@surf-kit/agent 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/chat/index.cjs +131 -41
  2. package/dist/chat/index.cjs.map +1 -1
  3. package/dist/chat/index.d.cts +3 -2
  4. package/dist/chat/index.d.ts +3 -2
  5. package/dist/chat/index.js +132 -42
  6. package/dist/chat/index.js.map +1 -1
  7. package/dist/{chat-CGamM7Mz.d.cts → chat-BRY3xGg_.d.cts} +1 -1
  8. package/dist/{chat-BIIDOGrD.d.ts → chat-CcKc6OAR.d.ts} +1 -1
  9. package/dist/{hooks-CTeEqnBQ.d.ts → hooks-BLeiVk-x.d.ts} +3 -2
  10. package/dist/{hooks-B1NYoLLs.d.cts → hooks-CSGGLd7j.d.cts} +3 -2
  11. package/dist/hooks.cjs +41 -11
  12. package/dist/hooks.cjs.map +1 -1
  13. package/dist/hooks.d.cts +3 -3
  14. package/dist/hooks.d.ts +3 -3
  15. package/dist/hooks.js +41 -11
  16. package/dist/hooks.js.map +1 -1
  17. package/dist/index.cjs +131 -41
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +3 -3
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.js +136 -46
  22. package/dist/index.js.map +1 -1
  23. package/dist/layouts/index.cjs +131 -41
  24. package/dist/layouts/index.cjs.map +1 -1
  25. package/dist/layouts/index.d.cts +1 -1
  26. package/dist/layouts/index.d.ts +1 -1
  27. package/dist/layouts/index.js +134 -44
  28. package/dist/layouts/index.js.map +1 -1
  29. package/dist/response/index.cjs +34 -3
  30. package/dist/response/index.cjs.map +1 -1
  31. package/dist/response/index.d.cts +1 -1
  32. package/dist/response/index.d.ts +1 -1
  33. package/dist/response/index.js +35 -4
  34. package/dist/response/index.js.map +1 -1
  35. package/dist/streaming/index.cjs +14 -3
  36. package/dist/streaming/index.cjs.map +1 -1
  37. package/dist/streaming/index.d.cts +2 -2
  38. package/dist/streaming/index.d.ts +2 -2
  39. package/dist/streaming/index.js +14 -3
  40. package/dist/streaming/index.js.map +1 -1
  41. package/dist/{streaming-x7umFHoP.d.cts → streaming-BHPXnwwo.d.cts} +3 -1
  42. package/dist/{streaming-Bx-ff2tt.d.ts → streaming-C6mbU7My.d.ts} +3 -1
  43. package/package.json +5 -4
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useAgentChat.ts","../src/hooks/useStreaming.ts","../src/hooks/useConversation.ts","../src/hooks/useFeedback.ts","../src/hooks/useAgentTheme.ts","../src/hooks/useCharacterDrain.ts"],"sourcesContent":["'use client'\n\nimport { useReducer, useCallback, useRef } from 'react'\nimport type { ChatMessage, ChatError, Attachment } from '../types/chat'\nimport type { AgentResponse } from '../types/agent'\nimport type { StreamState } from '../types/streaming'\nimport type { AgentChatConfig } from '../types/config'\n\n// ── State ──────────────────────────────────────────────────────────────\n\nexport interface AgentChatState {\n messages: ChatMessage[]\n conversationId: string | null\n isLoading: boolean\n error: ChatError | null\n inputValue: string\n streamPhase: StreamState['phase']\n streamingContent: string\n streamingAgent: string | null\n}\n\nconst initialState: AgentChatState = {\n messages: [],\n conversationId: null,\n isLoading: false,\n error: null,\n inputValue: '',\n streamPhase: 'idle',\n streamingContent: '',\n streamingAgent: null,\n}\n\n// ── Actions ────────────────────────────────────────────────────────────\n\ntype Action =\n | { type: 'SET_INPUT'; value: string }\n | { type: 'SEND_START'; message: ChatMessage }\n | { type: 'STREAM_PHASE'; phase: StreamState['phase'] }\n | { type: 'STREAM_CONTENT'; content: string }\n | { type: 'STREAM_AGENT'; agent: string }\n | { type: 'SEND_SUCCESS'; message: ChatMessage; streamingContent: string; conversationId: string | null }\n | { type: 'SEND_ERROR'; error: ChatError }\n | { type: 'LOAD_CONVERSATION'; conversationId: string; messages: ChatMessage[] }\n | { type: 'RESET' }\n | { type: 'CLEAR_ERROR' }\n\nfunction reducer(state: AgentChatState, action: Action): AgentChatState {\n switch (action.type) {\n case 'SET_INPUT':\n return { ...state, inputValue: action.value }\n\n case 'SEND_START':\n return {\n ...state,\n messages: [...state.messages, action.message],\n isLoading: true,\n error: null,\n inputValue: '',\n streamPhase: 'thinking',\n streamingContent: '',\n streamingAgent: null,\n }\n\n case 'STREAM_PHASE':\n return { ...state, streamPhase: action.phase }\n\n case 'STREAM_CONTENT':\n return { ...state, streamingContent: state.streamingContent + action.content }\n\n case 'STREAM_AGENT':\n return { ...state, streamingAgent: action.agent }\n\n case 'SEND_SUCCESS':\n return {\n ...state,\n messages: [...state.messages, action.message],\n conversationId: action.conversationId ?? state.conversationId,\n isLoading: false,\n streamPhase: 'idle',\n streamingContent: '',\n }\n\n case 'SEND_ERROR':\n return {\n ...state,\n isLoading: false,\n error: action.error,\n streamPhase: 'idle',\n streamingContent: '',\n streamingAgent: null,\n }\n\n case 'LOAD_CONVERSATION':\n return {\n ...state,\n conversationId: action.conversationId,\n messages: action.messages,\n error: null,\n }\n\n case 'RESET':\n return { ...initialState }\n\n case 'CLEAR_ERROR':\n return { ...state, error: null }\n\n default:\n return state\n }\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────\n\nlet msgIdCounter = 0\nfunction generateMessageId(): string {\n return `msg-${Date.now()}-${++msgIdCounter}`\n}\n\nexport interface AgentChatActions {\n sendMessage: (content: string, attachments?: Attachment[]) => Promise<void>\n setInputValue: (value: string) => void\n loadConversation: (conversationId: string, messages: ChatMessage[]) => void\n submitFeedback: (messageId: string, rating: 'positive' | 'negative', comment?: string) => Promise<void>\n retry: () => Promise<void>\n reset: () => void\n}\n\nexport function useAgentChat(config: AgentChatConfig) {\n const [state, dispatch] = useReducer(reducer, initialState)\n const configRef = useRef(config)\n configRef.current = config\n const lastUserMessageRef = useRef<string | null>(null)\n const lastUserAttachmentsRef = useRef<Attachment[] | undefined>(undefined)\n\n const sendMessage = useCallback(\n async (content: string, attachments?: Attachment[]) => {\n const { apiUrl, streamPath = '/chat/stream', headers: headersOrFn, timeout = 30000, bodyExtra } = configRef.current\n const headers = typeof headersOrFn === 'function' ? await headersOrFn() : (headersOrFn ?? {})\n\n lastUserMessageRef.current = content\n lastUserAttachmentsRef.current = attachments\n\n const userMessage: ChatMessage = {\n id: generateMessageId(),\n role: 'user',\n content,\n attachments,\n timestamp: new Date(),\n }\n\n dispatch({ type: 'SEND_START', message: userMessage })\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n const url = `${apiUrl}${streamPath}`\n const mergedHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n ...headers,\n }\n\n // Build request body — include attachments if present\n const requestBody: Record<string, unknown> = {\n message: content,\n conversation_id: state.conversationId,\n ...bodyExtra,\n }\n if (attachments && attachments.length > 0) {\n requestBody.attachments = attachments.map(a => ({\n filename: a.filename,\n content_type: a.content_type,\n data: a.data,\n }))\n }\n const body = JSON.stringify(requestBody)\n\n // These variables are mutated inside handleEvent (called from async stream processing).\n // TypeScript can't track mutations through closures, so we use a mutable context object.\n const ctx = {\n accumulatedContent: '',\n agentResponse: null as AgentResponse | null,\n capturedAgent: null as string | null,\n capturedConversationId: null as string | null,\n hadStreamError: false,\n }\n\n // Shared handler for parsed SSE events (used by both adapter and default paths)\n const handleEvent = (event: { type: string; [key: string]: unknown }) => {\n switch (event.type) {\n case 'agent':\n ctx.capturedAgent = event.agent as string\n dispatch({ type: 'STREAM_AGENT', agent: ctx.capturedAgent })\n break\n case 'phase':\n dispatch({ type: 'STREAM_PHASE', phase: event.phase as StreamState['phase'] })\n break\n case 'delta':\n ctx.accumulatedContent += event.content\n dispatch({ type: 'STREAM_CONTENT', content: event.content as string })\n break\n case 'done':\n ctx.agentResponse = event.response as AgentResponse\n ctx.capturedConversationId = (event.conversation_id as string) ?? null\n break\n case 'error':\n ctx.hadStreamError = true\n dispatch({ type: 'SEND_ERROR', error: event.error as ChatError })\n break\n }\n }\n\n const { streamAdapter } = configRef.current\n\n if (streamAdapter) {\n // Use the custom stream adapter (e.g. React Native XHR-based SSE)\n await streamAdapter(\n url,\n { method: 'POST', headers: mergedHeaders, body, signal: controller.signal },\n handleEvent,\n )\n clearTimeout(timeoutId)\n } else {\n // Default path: fetch + ReadableStream getReader()\n const response = await fetch(url, {\n method: 'POST',\n headers: mergedHeaders,\n body,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n dispatch({\n type: 'SEND_ERROR',\n error: {\n code: 'API_ERROR',\n message: `HTTP ${response.status}: ${response.statusText}`,\n retryable: response.status >= 500,\n },\n })\n return\n }\n\n const reader = response.body?.getReader()\n if (!reader) {\n dispatch({\n type: 'SEND_ERROR',\n error: { code: 'STREAM_ERROR', message: 'No response body', retryable: true },\n })\n return\n }\n\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n const data = line.slice(6).trim()\n if (data === '[DONE]') continue\n\n try {\n const event = JSON.parse(data)\n handleEvent(event)\n } catch {\n // Skip malformed events\n }\n }\n }\n }\n\n // If an error event was dispatched during streaming, don't dispatch success\n if (ctx.hadStreamError) return\n\n const assistantMessage: ChatMessage = {\n id: generateMessageId(),\n role: 'assistant',\n content: ctx.agentResponse?.message ?? ctx.accumulatedContent,\n response: ctx.agentResponse ?? undefined,\n agent: ctx.capturedAgent ?? undefined,\n timestamp: new Date(),\n }\n\n dispatch({\n type: 'SEND_SUCCESS',\n message: assistantMessage,\n streamingContent: ctx.accumulatedContent,\n conversationId: ctx.capturedConversationId,\n })\n } catch (err: unknown) {\n clearTimeout(timeoutId)\n if ((err as Error).name === 'AbortError') {\n dispatch({\n type: 'SEND_ERROR',\n error: { code: 'TIMEOUT', message: 'Request timed out', retryable: true },\n })\n } else {\n dispatch({\n type: 'SEND_ERROR',\n error: {\n code: 'NETWORK_ERROR',\n message: (err as Error).message ?? 'Network error',\n retryable: true,\n },\n })\n }\n }\n },\n [state.conversationId],\n )\n\n const setInputValue = useCallback((value: string) => {\n dispatch({ type: 'SET_INPUT', value })\n }, [])\n\n const loadConversation = useCallback((conversationId: string, messages: ChatMessage[]) => {\n dispatch({ type: 'LOAD_CONVERSATION', conversationId, messages })\n }, [])\n\n const submitFeedback = useCallback(\n async (messageId: string, rating: 'positive' | 'negative', comment?: string) => {\n const { apiUrl, feedbackPath = '/feedback', headers: headersOrFn } = configRef.current\n const headers = typeof headersOrFn === 'function' ? await headersOrFn() : (headersOrFn ?? {})\n await fetch(`${apiUrl}${feedbackPath}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify({ messageId, rating, comment }),\n })\n },\n [],\n )\n\n const retry = useCallback(async () => {\n if (lastUserMessageRef.current) {\n await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current)\n }\n }, [sendMessage])\n\n const reset = useCallback(() => {\n dispatch({ type: 'RESET' })\n lastUserMessageRef.current = null\n lastUserAttachmentsRef.current = undefined\n }, [])\n\n const actions: AgentChatActions = {\n sendMessage,\n setInputValue,\n loadConversation,\n submitFeedback,\n retry,\n reset,\n }\n\n return { state, actions }\n}\n","'use client'\n\nimport { useState, useCallback, useRef, useEffect } from 'react'\nimport type { StreamEvent, StreamState } from '../types/streaming'\nimport type { Source } from '../types/agent'\n\nexport interface UseStreamingOptions {\n /** SSE endpoint URL */\n url: string\n /** Additional headers for fetch-based SSE (not used with native EventSource) */\n headers?: Record<string, string>\n /** Called when a complete response is received */\n onDone?: (event: Extract<StreamEvent, { type: 'done' }>) => void\n /** Called on error */\n onError?: (event: Extract<StreamEvent, { type: 'error' }>) => void\n}\n\nconst initialState: StreamState = {\n active: false,\n phase: 'idle',\n content: '',\n sources: [],\n agent: null,\n agentLabel: null,\n}\n\nexport function useStreaming(options: UseStreamingOptions) {\n const { url, headers, onDone, onError } = options\n const [state, setState] = useState<StreamState>(initialState)\n const abortRef = useRef<AbortController | null>(null)\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const stop = useCallback(() => {\n if (abortRef.current) {\n abortRef.current.abort()\n abortRef.current = null\n }\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n }, [])\n\n const start = useCallback(\n async (body: Record<string, unknown>) => {\n // Reset state\n setState({ ...initialState, active: true, phase: 'thinking' })\n\n const controller = new AbortController()\n abortRef.current = controller\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n ...headers,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorEvent: StreamEvent = {\n type: 'error',\n error: {\n code: 'API_ERROR',\n message: `HTTP ${response.status}: ${response.statusText}`,\n retryable: response.status >= 500,\n },\n }\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n optionsRef.current.onError?.(errorEvent as Extract<StreamEvent, { type: 'error' }>)\n return\n }\n\n const reader = response.body?.getReader()\n if (!reader) {\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n return\n }\n\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n const data = line.slice(6).trim()\n if (data === '[DONE]') continue\n\n try {\n const event = JSON.parse(data) as StreamEvent\n processEvent(event, setState, optionsRef)\n } catch {\n // Skip malformed events\n }\n }\n }\n } catch (err: unknown) {\n if ((err as Error).name === 'AbortError') return\n const errorEvent: StreamEvent = {\n type: 'error',\n error: {\n code: 'NETWORK_ERROR',\n message: (err as Error).message ?? 'Network error',\n retryable: true,\n },\n }\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n optionsRef.current.onError?.(errorEvent as Extract<StreamEvent, { type: 'error' }>)\n }\n },\n [url, headers],\n )\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (abortRef.current) {\n abortRef.current.abort()\n }\n }\n }, [])\n\n return { state, start, stop }\n}\n\nfunction processEvent(\n event: StreamEvent,\n setState: React.Dispatch<React.SetStateAction<StreamState>>,\n optionsRef: React.MutableRefObject<UseStreamingOptions>,\n) {\n switch (event.type) {\n case 'phase':\n setState((prev) => ({ ...prev, phase: event.phase }))\n break\n case 'delta':\n setState((prev) => ({ ...prev, content: prev.content + event.content }))\n break\n case 'source':\n setState((prev) => ({ ...prev, sources: [...prev.sources, event.source] }))\n break\n case 'agent':\n setState((prev) => ({ ...prev, agent: event.agent }))\n break\n case 'done':\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n optionsRef.current.onDone?.(event)\n break\n case 'error':\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n optionsRef.current.onError?.(event)\n break\n }\n}\n","'use client'\n\nimport { useState, useCallback } from 'react'\nimport type { ChatMessage } from '../types/chat'\nimport type { ConversationSummary } from '../types/chat'\n\nexport interface Conversation {\n id: string\n title: string\n messages: ChatMessage[]\n createdAt: Date\n updatedAt: Date\n}\n\nexport interface UseConversationOptions {\n /** Enable localStorage persistence */\n persist?: boolean\n /** localStorage key prefix */\n storageKey?: string\n}\n\nconst STORAGE_PREFIX = 'surf-kit-conversations'\n\nfunction getStorageKey(prefix: string) {\n return `${prefix}:list`\n}\n\nfunction loadFromStorage(storageKey: string): Conversation[] {\n if (typeof window === 'undefined') return []\n try {\n const raw = window.localStorage.getItem(getStorageKey(storageKey))\n if (!raw) return []\n const parsed = JSON.parse(raw) as Array<Conversation & { createdAt: string; updatedAt: string }>\n return parsed.map((c) => ({\n ...c,\n createdAt: new Date(c.createdAt),\n updatedAt: new Date(c.updatedAt),\n messages: c.messages.map((m) => ({ ...m, timestamp: new Date(m.timestamp as unknown as string) })),\n }))\n } catch {\n return []\n }\n}\n\nfunction saveToStorage(storageKey: string, conversations: Conversation[]) {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.setItem(getStorageKey(storageKey), JSON.stringify(conversations))\n } catch {\n // Storage full or unavailable\n }\n}\n\nlet idCounter = 0\nfunction generateId(): string {\n return `conv-${Date.now()}-${++idCounter}`\n}\n\nexport function useConversation(options: UseConversationOptions = {}) {\n const { persist = false, storageKey = STORAGE_PREFIX } = options\n\n const [conversations, setConversations] = useState<Conversation[]>(() =>\n persist ? loadFromStorage(storageKey) : [],\n )\n const [current, setCurrent] = useState<Conversation | null>(null)\n\n const persistIfNeeded = useCallback(\n (convs: Conversation[]) => {\n if (persist) saveToStorage(storageKey, convs)\n },\n [persist, storageKey],\n )\n\n const create = useCallback(\n (title?: string): Conversation => {\n const now = new Date()\n const conv: Conversation = {\n id: generateId(),\n title: title ?? 'New Conversation',\n messages: [],\n createdAt: now,\n updatedAt: now,\n }\n setConversations((prev) => {\n const next = [conv, ...prev]\n persistIfNeeded(next)\n return next\n })\n setCurrent(conv)\n return conv\n },\n [persistIfNeeded],\n )\n\n const list = useCallback((): ConversationSummary[] => {\n return conversations.map((c) => ({\n id: c.id,\n title: c.title,\n lastMessage: c.messages.length > 0 ? c.messages[c.messages.length - 1].content : '',\n updatedAt: c.updatedAt,\n messageCount: c.messages.length,\n }))\n }, [conversations])\n\n const load = useCallback(\n (id: string): Conversation | null => {\n const conv = conversations.find((c) => c.id === id) ?? null\n setCurrent(conv)\n return conv\n },\n [conversations],\n )\n\n const remove = useCallback(\n (id: string) => {\n setConversations((prev) => {\n const next = prev.filter((c) => c.id !== id)\n persistIfNeeded(next)\n return next\n })\n setCurrent((prev) => (prev?.id === id ? null : prev))\n },\n [persistIfNeeded],\n )\n\n const rename = useCallback(\n (id: string, title: string) => {\n setConversations((prev) => {\n const next = prev.map((c) => (c.id === id ? { ...c, title, updatedAt: new Date() } : c))\n persistIfNeeded(next)\n return next\n })\n setCurrent((prev) => (prev?.id === id ? { ...prev, title, updatedAt: new Date() } : prev))\n },\n [persistIfNeeded],\n )\n\n const addMessage = useCallback(\n (conversationId: string, message: ChatMessage) => {\n setConversations((prev) => {\n const next = prev.map((c) =>\n c.id === conversationId\n ? { ...c, messages: [...c.messages, message], updatedAt: new Date() }\n : c,\n )\n persistIfNeeded(next)\n return next\n })\n setCurrent((prev) =>\n prev?.id === conversationId\n ? { ...prev, messages: [...prev.messages, message], updatedAt: new Date() }\n : prev,\n )\n },\n [persistIfNeeded],\n )\n\n return {\n conversations,\n current,\n create,\n list,\n load,\n delete: remove,\n rename,\n addMessage,\n }\n}\n","'use client'\n\nimport { useState, useCallback, useRef } from 'react'\n\nexport type FeedbackState = 'idle' | 'submitting' | 'submitted' | 'error'\n\nexport interface UseFeedbackOptions {\n /** API endpoint URL for feedback submission */\n url: string\n /** Additional request headers */\n headers?: Record<string, string>\n}\n\nexport interface FeedbackPayload {\n messageId: string\n rating: 'positive' | 'negative'\n comment?: string\n}\n\nexport function useFeedback(options: UseFeedbackOptions) {\n const { url, headers } = options\n const [state, setState] = useState<FeedbackState>('idle')\n const [error, setError] = useState<string | null>(null)\n const abortRef = useRef<AbortController | null>(null)\n\n const submit = useCallback(\n async (messageId: string, rating: 'positive' | 'negative', comment?: string) => {\n setState('submitting')\n setError(null)\n\n const controller = new AbortController()\n abortRef.current = controller\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify({ messageId, rating, comment }),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n setState('submitted')\n } catch (err: unknown) {\n if ((err as Error).name === 'AbortError') return\n setError((err as Error).message ?? 'Failed to submit feedback')\n setState('error')\n }\n },\n [url, headers],\n )\n\n const reset = useCallback(() => {\n setState('idle')\n setError(null)\n }, [])\n\n return { state, error, submit, reset }\n}\n","'use client'\n\nimport { useMemo } from 'react'\nimport type { AgentInfo } from '../types/agent'\n\nexport interface AgentThemeResult {\n accent: string\n icon: AgentInfo['icon'] | null\n label: string\n}\n\nconst DEFAULT_ACCENT = '#6366f1'\nconst DEFAULT_LABEL = 'Agent'\n\nexport function useAgentTheme(\n agentId: string | null | undefined,\n agentThemes?: Record<string, AgentInfo>,\n): AgentThemeResult {\n return useMemo(() => {\n if (!agentId) {\n return { accent: DEFAULT_ACCENT, icon: null, label: DEFAULT_LABEL }\n }\n\n const theme = agentThemes?.[agentId]\n if (!theme) {\n return { accent: DEFAULT_ACCENT, icon: null, label: agentId }\n }\n\n return {\n accent: theme.accent ?? DEFAULT_ACCENT,\n icon: theme.icon ?? null,\n label: theme.label,\n }\n }, [agentId, agentThemes])\n}\n","'use client'\n\nimport { useState, useRef, useEffect } from 'react'\n\nexport interface CharacterDrainResult {\n displayed: string\n isDraining: boolean\n}\n\n/**\n * Smoothly drains a growing `target` string character-by-character using\n * `requestAnimationFrame`, decoupling visual rendering from network packet\n * timing so text appears to type out instead of arriving in chunks.\n *\n * When `target` resets to empty (e.g. stream finished), the hook continues\n * draining the previous content to completion before resetting, so the\n * typing animation isn't cut short.\n *\n * Design: the RAF loop is long-lived — it does NOT restart on every delta.\n * The tick function is stored in a ref (updated each render) so the loop\n * always reads the latest drainTarget without being cancelled/restarted.\n * A separate kick-start effect re-fires the loop when it was idle and new\n * content arrives.\n */\nexport function useCharacterDrain(target: string, msPerChar = 15): CharacterDrainResult {\n const [displayed, setDisplayed] = useState('')\n const indexRef = useRef(0)\n const lastTimeRef = useRef(0)\n const rafRef = useRef<number | null>(null)\n // Holds the last non-empty target so we can finish draining after source resets\n const drainTargetRef = useRef('')\n const msPerCharRef = useRef(msPerChar)\n\n msPerCharRef.current = msPerChar\n\n // Update drain target when new content arrives; preserve old value on reset\n if (target !== '') {\n drainTargetRef.current = target\n }\n\n const drainTarget = drainTargetRef.current\n const isDraining = displayed.length < drainTarget.length\n\n // Tick function stored in ref so the long-lived RAF loop always reads the\n // latest drainTarget and msPerChar without being cancelled/recreated.\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n const tickRef = useRef<(now: number) => void>(() => {})\n tickRef.current = (now: number) => {\n const currentTarget = drainTargetRef.current\n if (currentTarget === '') {\n rafRef.current = null\n return\n }\n\n if (lastTimeRef.current === 0) lastTimeRef.current = now\n const elapsed = now - lastTimeRef.current\n const charsToAdvance = Math.floor(elapsed / msPerCharRef.current)\n\n if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {\n let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length)\n // When the slice would end with whitespace, advance past it to include\n // the next visible character. This prevents trimEnd() in the streaming\n // UI from stripping trailing newlines and causing ReactMarkdown to\n // \"jump\" between block structures in a single frame (e.g. a heading\n // suddenly gaining a following paragraph with no transition).\n while (\n nextIndex < currentTarget.length &&\n currentTarget[nextIndex - 1].trim() === ''\n ) {\n nextIndex++\n }\n indexRef.current = nextIndex\n lastTimeRef.current = now\n setDisplayed(currentTarget.slice(0, nextIndex))\n }\n\n if (indexRef.current < currentTarget.length) {\n rafRef.current = requestAnimationFrame((t) => tickRef.current(t))\n } else {\n rafRef.current = null\n }\n }\n\n // Kick-start the RAF loop when new content arrives and the loop is idle.\n // No cleanup here — we intentionally do NOT cancel the running loop when\n // drainTarget grows; the long-lived tick will pick up new chars automatically.\n useEffect(() => {\n if (\n drainTargetRef.current !== '' &&\n indexRef.current < drainTargetRef.current.length &&\n rafRef.current === null\n ) {\n rafRef.current = requestAnimationFrame((t) => tickRef.current(t))\n }\n }, [drainTarget]) // drainTarget change = new content; check if loop needs kicking\n\n // Once drain completes and source stream is already done, reset all state\n useEffect(() => {\n if (target === '' && !isDraining && displayed !== '') {\n indexRef.current = 0\n lastTimeRef.current = 0\n drainTargetRef.current = ''\n setDisplayed('')\n }\n }, [target, isDraining, displayed])\n\n // Cancel any pending RAF on unmount\n useEffect(() => {\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current)\n rafRef.current = null\n }\n }\n }, [])\n\n return { displayed, isDraining }\n}\n"],"mappings":"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","names":["useCallback","useRef","initialState","useState","useCallback","useState","useCallback","useRef","useState","useRef","useEffect"]}
1
+ {"version":3,"sources":["../src/hooks/useAgentChat.ts","../src/hooks/useStreaming.ts","../src/hooks/useConversation.ts","../src/hooks/useFeedback.ts","../src/hooks/useAgentTheme.ts","../src/hooks/useCharacterDrain.ts"],"sourcesContent":["'use client'\n\nimport { useReducer, useCallback, useRef } from 'react'\nimport type { ChatMessage, ChatError, Attachment } from '../types/chat'\nimport type { AgentResponse } from '../types/agent'\nimport type { StreamState } from '../types/streaming'\nimport type { AgentChatConfig } from '../types/config'\n\n// ── State ──────────────────────────────────────────────────────────────\n\nexport interface AgentChatState {\n messages: ChatMessage[]\n conversationId: string | null\n isLoading: boolean\n error: ChatError | null\n inputValue: string\n streamPhase: StreamState['phase']\n streamingContent: string\n streamingAgent: string | null\n}\n\nconst initialState: AgentChatState = {\n messages: [],\n conversationId: null,\n isLoading: false,\n error: null,\n inputValue: '',\n streamPhase: 'idle',\n streamingContent: '',\n streamingAgent: null,\n}\n\n// ── Actions ────────────────────────────────────────────────────────────\n\ntype Action =\n | { type: 'SET_INPUT'; value: string }\n | { type: 'SEND_START'; message: ChatMessage }\n | { type: 'STREAM_PHASE'; phase: StreamState['phase'] }\n | { type: 'STREAM_CONTENT'; content: string }\n | { type: 'STREAM_CONTENT_RESET' }\n | { type: 'STREAM_AGENT'; agent: string }\n | { type: 'SEND_SUCCESS'; message: ChatMessage; streamingContent: string; conversationId: string | null }\n | { type: 'SEND_ERROR'; error: ChatError }\n | { type: 'LOAD_CONVERSATION'; conversationId: string; messages: ChatMessage[] }\n | { type: 'RESET' }\n | { type: 'CLEAR_ERROR' }\n\nfunction reducer(state: AgentChatState, action: Action): AgentChatState {\n switch (action.type) {\n case 'SET_INPUT':\n return { ...state, inputValue: action.value }\n\n case 'SEND_START':\n return {\n ...state,\n messages: [...state.messages, action.message],\n isLoading: true,\n error: null,\n inputValue: '',\n streamPhase: 'thinking',\n streamingContent: '',\n streamingAgent: null,\n }\n\n case 'STREAM_PHASE':\n return { ...state, streamPhase: action.phase }\n\n case 'STREAM_CONTENT':\n return { ...state, streamingContent: state.streamingContent + action.content }\n\n case 'STREAM_CONTENT_RESET':\n return { ...state, streamingContent: '' }\n\n case 'STREAM_AGENT':\n return { ...state, streamingAgent: action.agent }\n\n case 'SEND_SUCCESS':\n return {\n ...state,\n messages: [...state.messages, action.message],\n conversationId: action.conversationId ?? state.conversationId,\n isLoading: false,\n streamPhase: 'idle',\n streamingContent: '',\n }\n\n case 'SEND_ERROR':\n return {\n ...state,\n isLoading: false,\n error: action.error,\n streamPhase: 'idle',\n streamingContent: '',\n streamingAgent: null,\n }\n\n case 'LOAD_CONVERSATION':\n return {\n ...state,\n conversationId: action.conversationId,\n messages: action.messages,\n error: null,\n }\n\n case 'RESET':\n return { ...initialState }\n\n case 'CLEAR_ERROR':\n return { ...state, error: null }\n\n default:\n return state\n }\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────\n\nlet msgIdCounter = 0\nfunction generateMessageId(): string {\n return `msg-${Date.now()}-${++msgIdCounter}`\n}\n\nexport interface AgentChatActions {\n sendMessage: (content: string, attachments?: Attachment[]) => Promise<void>\n setInputValue: (value: string) => void\n loadConversation: (conversationId: string, messages: ChatMessage[]) => void\n submitFeedback: (messageId: string, rating: 'positive' | 'negative', comment?: string) => Promise<void>\n retry: () => Promise<void>\n stop: () => void\n reset: () => void\n}\n\nexport function useAgentChat(config: AgentChatConfig) {\n const [state, dispatch] = useReducer(reducer, initialState)\n const configRef = useRef(config)\n configRef.current = config\n const lastUserMessageRef = useRef<string | null>(null)\n const lastUserAttachmentsRef = useRef<Attachment[] | undefined>(undefined)\n const abortControllerRef = useRef<AbortController | null>(null)\n\n const sendMessage = useCallback(\n async (content: string, attachments?: Attachment[]) => {\n const { apiUrl, streamPath = '/chat/stream', headers: headersOrFn, timeout = 30000, bodyExtra } = configRef.current\n const headers = typeof headersOrFn === 'function' ? await headersOrFn() : (headersOrFn ?? {})\n\n lastUserMessageRef.current = content\n lastUserAttachmentsRef.current = attachments\n\n const userMessage: ChatMessage = {\n id: generateMessageId(),\n role: 'user',\n content,\n attachments,\n timestamp: new Date(),\n }\n\n dispatch({ type: 'SEND_START', message: userMessage })\n\n const controller = new AbortController()\n abortControllerRef.current = controller\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // These variables are mutated inside handleEvent (called from async stream processing).\n // TypeScript can't track mutations through closures, so we use a mutable context object.\n // Defined outside try so the catch block can access accumulated content for user-initiated stop.\n const ctx = {\n accumulatedContent: '',\n agentResponse: null as AgentResponse | null,\n capturedAgent: null as string | null,\n capturedConversationId: null as string | null,\n hadStreamError: false,\n }\n\n try {\n const url = `${apiUrl}${streamPath}`\n const mergedHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n ...headers,\n }\n\n // Build request body — include attachments if present\n const requestBody: Record<string, unknown> = {\n message: content,\n conversation_id: state.conversationId,\n ...bodyExtra,\n }\n if (attachments && attachments.length > 0) {\n requestBody.attachments = attachments.map(a => ({\n filename: a.filename,\n content_type: a.content_type,\n data: a.data,\n }))\n }\n const body = JSON.stringify(requestBody)\n\n // Shared handler for parsed SSE events (used by both adapter and default paths)\n const handleEvent = (event: { type: string; [key: string]: unknown }) => {\n switch (event.type) {\n case 'agent':\n ctx.capturedAgent = event.agent as string\n dispatch({ type: 'STREAM_AGENT', agent: ctx.capturedAgent })\n break\n case 'phase':\n dispatch({ type: 'STREAM_PHASE', phase: event.phase as StreamState['phase'] })\n break\n case 'delta':\n ctx.accumulatedContent += event.content\n dispatch({ type: 'STREAM_CONTENT', content: event.content as string })\n break\n case 'delta_reset':\n ctx.accumulatedContent = ''\n dispatch({ type: 'STREAM_CONTENT_RESET' })\n break\n case 'done':\n ctx.agentResponse = event.response as AgentResponse\n ctx.capturedConversationId = (event.conversation_id as string) ?? null\n break\n case 'error':\n ctx.hadStreamError = true\n dispatch({ type: 'SEND_ERROR', error: event.error as ChatError })\n break\n }\n }\n\n const { streamAdapter } = configRef.current\n\n if (streamAdapter) {\n // Use the custom stream adapter (e.g. React Native XHR-based SSE)\n await streamAdapter(\n url,\n { method: 'POST', headers: mergedHeaders, body, signal: controller.signal },\n handleEvent,\n )\n clearTimeout(timeoutId)\n } else {\n // Default path: fetch + ReadableStream getReader()\n const response = await fetch(url, {\n method: 'POST',\n headers: mergedHeaders,\n body,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n dispatch({\n type: 'SEND_ERROR',\n error: {\n code: 'API_ERROR',\n message: `HTTP ${response.status}: ${response.statusText}`,\n retryable: response.status >= 500,\n },\n })\n return\n }\n\n const reader = response.body?.getReader()\n if (!reader) {\n dispatch({\n type: 'SEND_ERROR',\n error: { code: 'STREAM_ERROR', message: 'No response body', retryable: true },\n })\n return\n }\n\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n const data = line.slice(6).trim()\n if (data === '[DONE]') continue\n\n try {\n const event = JSON.parse(data)\n handleEvent(event)\n } catch {\n // Skip malformed events\n }\n }\n }\n }\n\n // If an error event was dispatched during streaming, don't dispatch success\n if (ctx.hadStreamError) return\n\n const assistantMessage: ChatMessage = {\n id: generateMessageId(),\n role: 'assistant',\n content: ctx.agentResponse?.message ?? ctx.accumulatedContent,\n response: ctx.agentResponse ?? undefined,\n agent: ctx.capturedAgent ?? undefined,\n timestamp: new Date(),\n }\n\n dispatch({\n type: 'SEND_SUCCESS',\n message: assistantMessage,\n streamingContent: ctx.accumulatedContent,\n conversationId: ctx.capturedConversationId,\n })\n } catch (err: unknown) {\n clearTimeout(timeoutId)\n if ((err as Error).name === 'AbortError') {\n // User-initiated stop: commit whatever content we have so far\n if (ctx.accumulatedContent) {\n const partialMessage: ChatMessage = {\n id: generateMessageId(),\n role: 'assistant',\n content: ctx.accumulatedContent,\n agent: ctx.capturedAgent ?? undefined,\n timestamp: new Date(),\n }\n dispatch({\n type: 'SEND_SUCCESS',\n message: partialMessage,\n streamingContent: ctx.accumulatedContent,\n conversationId: ctx.capturedConversationId,\n })\n } else {\n dispatch({\n type: 'SEND_ERROR',\n error: { code: 'ABORTED', message: 'Request stopped', retryable: true },\n })\n }\n } else {\n dispatch({\n type: 'SEND_ERROR',\n error: {\n code: 'NETWORK_ERROR',\n message: (err as Error).message ?? 'Network error',\n retryable: true,\n },\n })\n }\n } finally {\n abortControllerRef.current = null\n }\n },\n [state.conversationId],\n )\n\n const setInputValue = useCallback((value: string) => {\n dispatch({ type: 'SET_INPUT', value })\n }, [])\n\n const loadConversation = useCallback((conversationId: string, messages: ChatMessage[]) => {\n dispatch({ type: 'LOAD_CONVERSATION', conversationId, messages })\n }, [])\n\n const submitFeedback = useCallback(\n async (messageId: string, rating: 'positive' | 'negative', comment?: string) => {\n const { apiUrl, feedbackPath = '/feedback', headers: headersOrFn } = configRef.current\n const headers = typeof headersOrFn === 'function' ? await headersOrFn() : (headersOrFn ?? {})\n await fetch(`${apiUrl}${feedbackPath}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify({ messageId, rating, comment }),\n })\n },\n [],\n )\n\n const retry = useCallback(async () => {\n if (lastUserMessageRef.current) {\n await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current)\n }\n }, [sendMessage])\n\n const stop = useCallback(() => {\n abortControllerRef.current?.abort()\n }, [])\n\n const reset = useCallback(() => {\n dispatch({ type: 'RESET' })\n lastUserMessageRef.current = null\n lastUserAttachmentsRef.current = undefined\n }, [])\n\n const actions: AgentChatActions = {\n sendMessage,\n setInputValue,\n loadConversation,\n submitFeedback,\n retry,\n stop,\n reset,\n }\n\n return { state, actions }\n}\n","'use client'\n\nimport { useState, useCallback, useRef, useEffect } from 'react'\nimport type { StreamEvent, StreamState } from '../types/streaming'\nimport type { Source } from '../types/agent'\n\nexport interface UseStreamingOptions {\n /** SSE endpoint URL */\n url: string\n /** Additional headers for fetch-based SSE (not used with native EventSource) */\n headers?: Record<string, string>\n /** Called when a complete response is received */\n onDone?: (event: Extract<StreamEvent, { type: 'done' }>) => void\n /** Called on error */\n onError?: (event: Extract<StreamEvent, { type: 'error' }>) => void\n}\n\nconst initialState: StreamState = {\n active: false,\n phase: 'idle',\n content: '',\n sources: [],\n agent: null,\n agentLabel: null,\n}\n\nexport function useStreaming(options: UseStreamingOptions) {\n const { url, headers, onDone, onError } = options\n const [state, setState] = useState<StreamState>(initialState)\n const abortRef = useRef<AbortController | null>(null)\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const stop = useCallback(() => {\n if (abortRef.current) {\n abortRef.current.abort()\n abortRef.current = null\n }\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n }, [])\n\n const start = useCallback(\n async (body: Record<string, unknown>) => {\n // Reset state\n setState({ ...initialState, active: true, phase: 'thinking' })\n\n const controller = new AbortController()\n abortRef.current = controller\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n ...headers,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorEvent: StreamEvent = {\n type: 'error',\n error: {\n code: 'API_ERROR',\n message: `HTTP ${response.status}: ${response.statusText}`,\n retryable: response.status >= 500,\n },\n }\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n optionsRef.current.onError?.(errorEvent as Extract<StreamEvent, { type: 'error' }>)\n return\n }\n\n const reader = response.body?.getReader()\n if (!reader) {\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n return\n }\n\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n const data = line.slice(6).trim()\n if (data === '[DONE]') continue\n\n try {\n const event = JSON.parse(data) as StreamEvent\n processEvent(event, setState, optionsRef)\n } catch {\n // Skip malformed events\n }\n }\n }\n } catch (err: unknown) {\n if ((err as Error).name === 'AbortError') return\n const errorEvent: StreamEvent = {\n type: 'error',\n error: {\n code: 'NETWORK_ERROR',\n message: (err as Error).message ?? 'Network error',\n retryable: true,\n },\n }\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n optionsRef.current.onError?.(errorEvent as Extract<StreamEvent, { type: 'error' }>)\n }\n },\n [url, headers],\n )\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (abortRef.current) {\n abortRef.current.abort()\n }\n }\n }, [])\n\n return { state, start, stop }\n}\n\nfunction processEvent(\n event: StreamEvent,\n setState: React.Dispatch<React.SetStateAction<StreamState>>,\n optionsRef: React.MutableRefObject<UseStreamingOptions>,\n) {\n switch (event.type) {\n case 'phase':\n setState((prev) => ({ ...prev, phase: event.phase }))\n break\n case 'delta':\n setState((prev) => ({ ...prev, content: prev.content + event.content }))\n break\n case 'source':\n setState((prev) => ({ ...prev, sources: [...prev.sources, event.source] }))\n break\n case 'agent':\n setState((prev) => ({ ...prev, agent: event.agent }))\n break\n case 'done':\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n optionsRef.current.onDone?.(event)\n break\n case 'error':\n setState((prev) => ({ ...prev, active: false, phase: 'idle' }))\n optionsRef.current.onError?.(event)\n break\n }\n}\n","'use client'\n\nimport { useState, useCallback } from 'react'\nimport type { ChatMessage } from '../types/chat'\nimport type { ConversationSummary } from '../types/chat'\n\nexport interface Conversation {\n id: string\n title: string\n messages: ChatMessage[]\n createdAt: Date\n updatedAt: Date\n}\n\nexport interface UseConversationOptions {\n /** Enable localStorage persistence */\n persist?: boolean\n /** localStorage key prefix */\n storageKey?: string\n}\n\nconst STORAGE_PREFIX = 'surf-kit-conversations'\n\nfunction getStorageKey(prefix: string) {\n return `${prefix}:list`\n}\n\nfunction loadFromStorage(storageKey: string): Conversation[] {\n if (typeof window === 'undefined') return []\n try {\n const raw = window.localStorage.getItem(getStorageKey(storageKey))\n if (!raw) return []\n const parsed = JSON.parse(raw) as Array<Conversation & { createdAt: string; updatedAt: string }>\n return parsed.map((c) => ({\n ...c,\n createdAt: new Date(c.createdAt),\n updatedAt: new Date(c.updatedAt),\n messages: c.messages.map((m) => ({ ...m, timestamp: new Date(m.timestamp as unknown as string) })),\n }))\n } catch {\n return []\n }\n}\n\nfunction saveToStorage(storageKey: string, conversations: Conversation[]) {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.setItem(getStorageKey(storageKey), JSON.stringify(conversations))\n } catch {\n // Storage full or unavailable\n }\n}\n\nlet idCounter = 0\nfunction generateId(): string {\n return `conv-${Date.now()}-${++idCounter}`\n}\n\nexport function useConversation(options: UseConversationOptions = {}) {\n const { persist = false, storageKey = STORAGE_PREFIX } = options\n\n const [conversations, setConversations] = useState<Conversation[]>(() =>\n persist ? loadFromStorage(storageKey) : [],\n )\n const [current, setCurrent] = useState<Conversation | null>(null)\n\n const persistIfNeeded = useCallback(\n (convs: Conversation[]) => {\n if (persist) saveToStorage(storageKey, convs)\n },\n [persist, storageKey],\n )\n\n const create = useCallback(\n (title?: string): Conversation => {\n const now = new Date()\n const conv: Conversation = {\n id: generateId(),\n title: title ?? 'New Conversation',\n messages: [],\n createdAt: now,\n updatedAt: now,\n }\n setConversations((prev) => {\n const next = [conv, ...prev]\n persistIfNeeded(next)\n return next\n })\n setCurrent(conv)\n return conv\n },\n [persistIfNeeded],\n )\n\n const list = useCallback((): ConversationSummary[] => {\n return conversations.map((c) => ({\n id: c.id,\n title: c.title,\n lastMessage: c.messages.length > 0 ? c.messages[c.messages.length - 1].content : '',\n updatedAt: c.updatedAt,\n messageCount: c.messages.length,\n }))\n }, [conversations])\n\n const load = useCallback(\n (id: string): Conversation | null => {\n const conv = conversations.find((c) => c.id === id) ?? null\n setCurrent(conv)\n return conv\n },\n [conversations],\n )\n\n const remove = useCallback(\n (id: string) => {\n setConversations((prev) => {\n const next = prev.filter((c) => c.id !== id)\n persistIfNeeded(next)\n return next\n })\n setCurrent((prev) => (prev?.id === id ? null : prev))\n },\n [persistIfNeeded],\n )\n\n const rename = useCallback(\n (id: string, title: string) => {\n setConversations((prev) => {\n const next = prev.map((c) => (c.id === id ? { ...c, title, updatedAt: new Date() } : c))\n persistIfNeeded(next)\n return next\n })\n setCurrent((prev) => (prev?.id === id ? { ...prev, title, updatedAt: new Date() } : prev))\n },\n [persistIfNeeded],\n )\n\n const addMessage = useCallback(\n (conversationId: string, message: ChatMessage) => {\n setConversations((prev) => {\n const next = prev.map((c) =>\n c.id === conversationId\n ? { ...c, messages: [...c.messages, message], updatedAt: new Date() }\n : c,\n )\n persistIfNeeded(next)\n return next\n })\n setCurrent((prev) =>\n prev?.id === conversationId\n ? { ...prev, messages: [...prev.messages, message], updatedAt: new Date() }\n : prev,\n )\n },\n [persistIfNeeded],\n )\n\n return {\n conversations,\n current,\n create,\n list,\n load,\n delete: remove,\n rename,\n addMessage,\n }\n}\n","'use client'\n\nimport { useState, useCallback, useRef } from 'react'\n\nexport type FeedbackState = 'idle' | 'submitting' | 'submitted' | 'error'\n\nexport interface UseFeedbackOptions {\n /** API endpoint URL for feedback submission */\n url: string\n /** Additional request headers */\n headers?: Record<string, string>\n}\n\nexport interface FeedbackPayload {\n messageId: string\n rating: 'positive' | 'negative'\n comment?: string\n}\n\nexport function useFeedback(options: UseFeedbackOptions) {\n const { url, headers } = options\n const [state, setState] = useState<FeedbackState>('idle')\n const [error, setError] = useState<string | null>(null)\n const abortRef = useRef<AbortController | null>(null)\n\n const submit = useCallback(\n async (messageId: string, rating: 'positive' | 'negative', comment?: string) => {\n setState('submitting')\n setError(null)\n\n const controller = new AbortController()\n abortRef.current = controller\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify({ messageId, rating, comment }),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n }\n\n setState('submitted')\n } catch (err: unknown) {\n if ((err as Error).name === 'AbortError') return\n setError((err as Error).message ?? 'Failed to submit feedback')\n setState('error')\n }\n },\n [url, headers],\n )\n\n const reset = useCallback(() => {\n setState('idle')\n setError(null)\n }, [])\n\n return { state, error, submit, reset }\n}\n","'use client'\n\nimport { useMemo } from 'react'\nimport type { AgentInfo } from '../types/agent'\n\nexport interface AgentThemeResult {\n accent: string\n icon: AgentInfo['icon'] | null\n label: string\n}\n\nconst DEFAULT_ACCENT = '#6366f1'\nconst DEFAULT_LABEL = 'Agent'\n\nexport function useAgentTheme(\n agentId: string | null | undefined,\n agentThemes?: Record<string, AgentInfo>,\n): AgentThemeResult {\n return useMemo(() => {\n if (!agentId) {\n return { accent: DEFAULT_ACCENT, icon: null, label: DEFAULT_LABEL }\n }\n\n const theme = agentThemes?.[agentId]\n if (!theme) {\n return { accent: DEFAULT_ACCENT, icon: null, label: agentId }\n }\n\n return {\n accent: theme.accent ?? DEFAULT_ACCENT,\n icon: theme.icon ?? null,\n label: theme.label,\n }\n }, [agentId, agentThemes])\n}\n","'use client'\n\nimport { useState, useRef, useEffect } from 'react'\n\nexport interface CharacterDrainResult {\n displayed: string\n isDraining: boolean\n}\n\n/**\n * Smoothly drains a growing `target` string character-by-character using\n * `requestAnimationFrame`, decoupling visual rendering from network packet\n * timing so text appears to type out instead of arriving in chunks.\n *\n * When `target` resets to empty (e.g. stream finished), the hook continues\n * draining the previous content to completion before resetting, so the\n * typing animation isn't cut short.\n *\n * Design: the RAF loop is long-lived — it does NOT restart on every delta.\n * The tick function is stored in a ref (updated each render) so the loop\n * always reads the latest drainTarget without being cancelled/restarted.\n * A separate kick-start effect re-fires the loop when it was idle and new\n * content arrives.\n */\nexport function useCharacterDrain(target: string, msPerChar = 15): CharacterDrainResult {\n const [displayed, setDisplayed] = useState('')\n const indexRef = useRef(0)\n const lastTimeRef = useRef(0)\n const rafRef = useRef<number | null>(null)\n // Holds the last non-empty target so we can finish draining after source resets\n const drainTargetRef = useRef('')\n const msPerCharRef = useRef(msPerChar)\n\n msPerCharRef.current = msPerChar\n\n // Update drain target when new content arrives; preserve old value on reset\n if (target !== '') {\n drainTargetRef.current = target\n }\n\n const drainTarget = drainTargetRef.current\n const isDraining = displayed.length < drainTarget.length\n\n // Tick function stored in ref so the long-lived RAF loop always reads the\n // latest drainTarget and msPerChar without being cancelled/recreated.\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n const tickRef = useRef<(now: number) => void>(() => {})\n tickRef.current = (now: number) => {\n const currentTarget = drainTargetRef.current\n if (currentTarget === '') {\n rafRef.current = null\n return\n }\n\n if (lastTimeRef.current === 0) lastTimeRef.current = now\n const elapsed = now - lastTimeRef.current\n const charsToAdvance = Math.floor(elapsed / msPerCharRef.current)\n\n if (charsToAdvance > 0 && indexRef.current < currentTarget.length) {\n let nextIndex = Math.min(indexRef.current + charsToAdvance, currentTarget.length)\n // When the slice would end with whitespace, advance past it to include\n // the next visible character. This prevents trimEnd() in the streaming\n // UI from stripping trailing newlines and causing ReactMarkdown to\n // \"jump\" between block structures in a single frame (e.g. a heading\n // suddenly gaining a following paragraph with no transition).\n while (\n nextIndex < currentTarget.length &&\n currentTarget[nextIndex - 1].trim() === ''\n ) {\n nextIndex++\n }\n indexRef.current = nextIndex\n lastTimeRef.current = now\n setDisplayed(currentTarget.slice(0, nextIndex))\n }\n\n if (indexRef.current < currentTarget.length) {\n rafRef.current = requestAnimationFrame((t) => tickRef.current(t))\n } else {\n rafRef.current = null\n }\n }\n\n // Kick-start the RAF loop when new content arrives and the loop is idle.\n // No cleanup here — we intentionally do NOT cancel the running loop when\n // drainTarget grows; the long-lived tick will pick up new chars automatically.\n useEffect(() => {\n if (\n drainTargetRef.current !== '' &&\n indexRef.current < drainTargetRef.current.length &&\n rafRef.current === null\n ) {\n rafRef.current = requestAnimationFrame((t) => tickRef.current(t))\n }\n }, [drainTarget]) // drainTarget change = new content; check if loop needs kicking\n\n // Once drain completes and source stream is already done, reset all state\n useEffect(() => {\n if (target === '' && !isDraining && displayed !== '') {\n indexRef.current = 0\n lastTimeRef.current = 0\n drainTargetRef.current = ''\n setDisplayed('')\n }\n }, [target, isDraining, displayed])\n\n // Cancel any pending RAF on unmount\n useEffect(() => {\n return () => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current)\n rafRef.current = null\n }\n }\n }, [])\n\n return { displayed, isDraining }\n}\n"],"mappings":"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","names":["useCallback","useRef","initialState","useState","useCallback","useState","useCallback","useRef","useState","useRef","useEffect"]}
package/dist/index.cjs CHANGED
@@ -114,6 +114,8 @@ function reducer(state, action) {
114
114
  return { ...state, streamPhase: action.phase };
115
115
  case "STREAM_CONTENT":
116
116
  return { ...state, streamingContent: state.streamingContent + action.content };
117
+ case "STREAM_CONTENT_RESET":
118
+ return { ...state, streamingContent: "" };
117
119
  case "STREAM_AGENT":
118
120
  return { ...state, streamingAgent: action.agent };
119
121
  case "SEND_SUCCESS":
@@ -159,6 +161,7 @@ function useAgentChat(config2) {
159
161
  configRef.current = config2;
160
162
  const lastUserMessageRef = (0, import_react.useRef)(null);
161
163
  const lastUserAttachmentsRef = (0, import_react.useRef)(void 0);
164
+ const abortControllerRef = (0, import_react.useRef)(null);
162
165
  const sendMessage = (0, import_react.useCallback)(
163
166
  async (content, attachments) => {
164
167
  const { apiUrl, streamPath = "/chat/stream", headers: headersOrFn, timeout = 3e4, bodyExtra } = configRef.current;
@@ -174,7 +177,15 @@ function useAgentChat(config2) {
174
177
  };
175
178
  dispatch({ type: "SEND_START", message: userMessage });
176
179
  const controller = new AbortController();
180
+ abortControllerRef.current = controller;
177
181
  const timeoutId = setTimeout(() => controller.abort(), timeout);
182
+ const ctx = {
183
+ accumulatedContent: "",
184
+ agentResponse: null,
185
+ capturedAgent: null,
186
+ capturedConversationId: null,
187
+ hadStreamError: false
188
+ };
178
189
  try {
179
190
  const url = `${apiUrl}${streamPath}`;
180
191
  const mergedHeaders = {
@@ -195,13 +206,6 @@ function useAgentChat(config2) {
195
206
  }));
196
207
  }
197
208
  const body = JSON.stringify(requestBody);
198
- const ctx = {
199
- accumulatedContent: "",
200
- agentResponse: null,
201
- capturedAgent: null,
202
- capturedConversationId: null,
203
- hadStreamError: false
204
- };
205
209
  const handleEvent = (event) => {
206
210
  switch (event.type) {
207
211
  case "agent":
@@ -215,6 +219,10 @@ function useAgentChat(config2) {
215
219
  ctx.accumulatedContent += event.content;
216
220
  dispatch({ type: "STREAM_CONTENT", content: event.content });
217
221
  break;
222
+ case "delta_reset":
223
+ ctx.accumulatedContent = "";
224
+ dispatch({ type: "STREAM_CONTENT_RESET" });
225
+ break;
218
226
  case "done":
219
227
  ctx.agentResponse = event.response;
220
228
  ctx.capturedConversationId = event.conversation_id ?? null;
@@ -298,10 +306,26 @@ function useAgentChat(config2) {
298
306
  } catch (err) {
299
307
  clearTimeout(timeoutId);
300
308
  if (err.name === "AbortError") {
301
- dispatch({
302
- type: "SEND_ERROR",
303
- error: { code: "TIMEOUT", message: "Request timed out", retryable: true }
304
- });
309
+ if (ctx.accumulatedContent) {
310
+ const partialMessage = {
311
+ id: generateMessageId(),
312
+ role: "assistant",
313
+ content: ctx.accumulatedContent,
314
+ agent: ctx.capturedAgent ?? void 0,
315
+ timestamp: /* @__PURE__ */ new Date()
316
+ };
317
+ dispatch({
318
+ type: "SEND_SUCCESS",
319
+ message: partialMessage,
320
+ streamingContent: ctx.accumulatedContent,
321
+ conversationId: ctx.capturedConversationId
322
+ });
323
+ } else {
324
+ dispatch({
325
+ type: "SEND_ERROR",
326
+ error: { code: "ABORTED", message: "Request stopped", retryable: true }
327
+ });
328
+ }
305
329
  } else {
306
330
  dispatch({
307
331
  type: "SEND_ERROR",
@@ -312,6 +336,8 @@ function useAgentChat(config2) {
312
336
  }
313
337
  });
314
338
  }
339
+ } finally {
340
+ abortControllerRef.current = null;
315
341
  }
316
342
  },
317
343
  [state.conversationId]
@@ -339,6 +365,9 @@ function useAgentChat(config2) {
339
365
  await sendMessage(lastUserMessageRef.current, lastUserAttachmentsRef.current);
340
366
  }
341
367
  }, [sendMessage]);
368
+ const stop = (0, import_react.useCallback)(() => {
369
+ abortControllerRef.current?.abort();
370
+ }, []);
342
371
  const reset = (0, import_react.useCallback)(() => {
343
372
  dispatch({ type: "RESET" });
344
373
  lastUserMessageRef.current = null;
@@ -350,6 +379,7 @@ function useAgentChat(config2) {
350
379
  loadConversation,
351
380
  submitFeedback,
352
381
  retry,
382
+ stop,
353
383
  reset
354
384
  };
355
385
  return { state, actions };
@@ -369,6 +399,7 @@ var import_core2 = require("@surf-kit/core");
369
399
  var import_react2 = __toESM(require("react"), 1);
370
400
  var import_react_markdown = __toESM(require("react-markdown"), 1);
371
401
  var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
402
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
372
403
  var import_tailwind_merge = require("tailwind-merge");
373
404
  var import_jsx_runtime = require("react/jsx-runtime");
374
405
  function normalizeMarkdownLists(content) {
@@ -393,6 +424,10 @@ function ResponseMessage({ content, className }) {
393
424
  "[&_pre]:bg-surface-raised [&_pre]:border [&_pre]:border-border [&_pre]:rounded-xl [&_pre]:p-4 [&_pre]:overflow-x-auto",
394
425
  "[&_hr]:my-3 [&_hr]:border-border",
395
426
  "[&_blockquote]:border-l-2 [&_blockquote]:border-border-strong [&_blockquote]:pl-4 [&_blockquote]:text-text-secondary",
427
+ "[&_table]:w-full [&_table]:text-sm [&_table]:border-collapse [&_table]:my-2",
428
+ "[&_thead]:border-b [&_thead]:border-border",
429
+ "[&_th]:text-left [&_th]:px-2 [&_th]:py-1.5 [&_th]:font-semibold",
430
+ "[&_td]:px-2 [&_td]:py-1.5 [&_td]:border-t [&_td]:border-border/50",
396
431
  "[&_a]:text-accent [&_a]:underline-offset-2 [&_a]:hover:text-accent/80",
397
432
  className
398
433
  ),
@@ -400,6 +435,7 @@ function ResponseMessage({ content, className }) {
400
435
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
401
436
  import_react_markdown.default,
402
437
  {
438
+ remarkPlugins: [import_remark_gfm.default],
403
439
  rehypePlugins: [import_rehype_sanitize.default],
404
440
  components: {
405
441
  script: () => null,
@@ -425,7 +461,11 @@ function ResponseMessage({ content, className }) {
425
461
  h2: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-sm font-bold mt-3 mb-1", children }),
426
462
  h3: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-sm font-semibold mt-2 mb-1", children }),
427
463
  hr: () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { className: "my-3 border-border" }),
428
- code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children })
464
+ code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children }),
465
+ table: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "overflow-x-auto my-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { className: "w-full text-sm border-collapse", children }) }),
466
+ thead: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { className: "border-b border-border", children }),
467
+ th: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { className: "text-left px-2 py-1.5 font-semibold", children }),
468
+ td: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "px-2 py-1.5 border-t border-border/50", children })
429
469
  },
430
470
  children: normalizeMarkdownLists(content)
431
471
  }
@@ -435,6 +475,8 @@ function ResponseMessage({ content, className }) {
435
475
  }
436
476
 
437
477
  // src/response/StructuredResponse/StructuredResponse.tsx
478
+ var import_react_markdown2 = __toESM(require("react-markdown"), 1);
479
+ var import_rehype_sanitize2 = __toESM(require("rehype-sanitize"), 1);
438
480
  var import_jsx_runtime2 = require("react/jsx-runtime");
439
481
  function tryParse(value) {
440
482
  if (value === void 0 || value === null) return null;
@@ -447,6 +489,25 @@ function tryParse(value) {
447
489
  }
448
490
  return value;
449
491
  }
492
+ function InlineMarkdown({ text }) {
493
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
494
+ import_react_markdown2.default,
495
+ {
496
+ rehypePlugins: [import_rehype_sanitize2.default],
497
+ components: {
498
+ // Unwrap block-level <p> so content stays inline within its parent
499
+ p: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children }),
500
+ strong: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { className: "font-semibold", children }),
501
+ em: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("em", { className: "italic", children }),
502
+ code: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("code", { className: "bg-surface-sunken rounded px-1 py-0.5 text-xs font-mono", children }),
503
+ // Prevent block elements that would break layout
504
+ script: () => null,
505
+ iframe: () => null
506
+ },
507
+ children: text
508
+ }
509
+ );
510
+ }
450
511
  function renderSteps(data) {
451
512
  const steps = tryParse(data.steps);
452
513
  if (!steps || !Array.isArray(steps)) return null;
@@ -459,7 +520,7 @@ function renderSteps(data) {
459
520
  children: i + 1
460
521
  }
461
522
  ),
462
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: step })
523
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineMarkdown, { text: step }) })
463
524
  ] }, i)) });
464
525
  }
465
526
  function renderTable(data) {
@@ -525,7 +586,7 @@ function renderList(data) {
525
586
  title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs font-semibold uppercase tracking-wider text-text-secondary mb-1", children: title }),
526
587
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "flex flex-col gap-1.5", children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "flex items-start gap-2.5", children: [
527
588
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-accent", "aria-hidden": "true" }),
528
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: item })
589
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm text-text-primary leading-relaxed", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineMarkdown, { text: item }) })
529
590
  ] }, i)) })
530
591
  ] });
531
592
  }
@@ -964,24 +1025,46 @@ function MessageBubble({
964
1025
  var import_jsx_runtime8 = require("react/jsx-runtime");
965
1026
  function MessageThread({ messages, streamingSlot, showAgent, showSources, showConfidence, showVerification, hideLastAssistant, userBubbleClassName, className }) {
966
1027
  const scrollRef = (0, import_react4.useRef)(null);
967
- const isNearBottom = (0, import_react4.useRef)(true);
968
- const isProgrammaticScroll = (0, import_react4.useRef)(false);
1028
+ const shouldAutoScroll = (0, import_react4.useRef)(true);
969
1029
  const hasStreaming = !!streamingSlot;
970
1030
  const scrollToBottom = (0, import_react4.useCallback)(() => {
971
1031
  const el = scrollRef.current;
972
- if (el && isNearBottom.current) {
973
- isProgrammaticScroll.current = true;
1032
+ if (el && shouldAutoScroll.current) {
974
1033
  el.scrollTop = el.scrollHeight;
975
1034
  }
976
1035
  }, []);
1036
+ (0, import_react4.useEffect)(() => {
1037
+ const el = scrollRef.current;
1038
+ if (!el) return;
1039
+ const onWheel = (e) => {
1040
+ if (e.deltaY < 0) {
1041
+ shouldAutoScroll.current = false;
1042
+ }
1043
+ };
1044
+ const onPointerDown = () => {
1045
+ el.dataset.userPointer = "1";
1046
+ };
1047
+ const onPointerUp = () => {
1048
+ delete el.dataset.userPointer;
1049
+ };
1050
+ el.addEventListener("wheel", onWheel, { passive: true });
1051
+ el.addEventListener("pointerdown", onPointerDown);
1052
+ window.addEventListener("pointerup", onPointerUp);
1053
+ return () => {
1054
+ el.removeEventListener("wheel", onWheel);
1055
+ el.removeEventListener("pointerdown", onPointerDown);
1056
+ window.removeEventListener("pointerup", onPointerUp);
1057
+ };
1058
+ }, []);
977
1059
  const handleScroll = (0, import_react4.useCallback)(() => {
978
- if (isProgrammaticScroll.current) {
979
- isProgrammaticScroll.current = false;
980
- return;
981
- }
982
1060
  const el = scrollRef.current;
983
1061
  if (!el) return;
984
- isNearBottom.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
1062
+ const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
1063
+ if (nearBottom) {
1064
+ shouldAutoScroll.current = true;
1065
+ } else if (el.dataset.userPointer) {
1066
+ shouldAutoScroll.current = false;
1067
+ }
985
1068
  }, []);
986
1069
  (0, import_react4.useEffect)(scrollToBottom, [messages.length, scrollToBottom]);
987
1070
  (0, import_react4.useEffect)(() => {
@@ -994,6 +1077,11 @@ function MessageThread({ messages, streamingSlot, showAgent, showSources, showCo
994
1077
  raf = requestAnimationFrame(tick);
995
1078
  return () => cancelAnimationFrame(raf);
996
1079
  }, [hasStreaming, scrollToBottom]);
1080
+ (0, import_react4.useEffect)(() => {
1081
+ if (!hasStreaming) {
1082
+ shouldAutoScroll.current = true;
1083
+ }
1084
+ }, [hasStreaming]);
997
1085
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
998
1086
  "div",
999
1087
  {
@@ -1125,6 +1213,7 @@ function AttachmentPreview({
1125
1213
  }
1126
1214
  function MessageComposer({
1127
1215
  onSend,
1216
+ onStop,
1128
1217
  isLoading = false,
1129
1218
  placeholder = "Type a message...",
1130
1219
  className
@@ -1334,16 +1423,16 @@ function MessageComposer({
1334
1423
  "button",
1335
1424
  {
1336
1425
  type: "button",
1337
- onClick: handleSend,
1338
- disabled: !canSend,
1339
- "aria-label": "Send message",
1426
+ onClick: isLoading && onStop ? onStop : handleSend,
1427
+ disabled: !canSend && !isLoading,
1428
+ "aria-label": isLoading ? "Stop generating" : "Send message",
1340
1429
  className: (0, import_tailwind_merge6.twMerge)(
1341
1430
  "absolute bottom-3 right-3",
1342
1431
  "inline-flex items-center justify-center",
1343
1432
  "w-9 h-9 rounded-full",
1344
1433
  "transition-all duration-200",
1345
1434
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent",
1346
- canSend ? "bg-accent text-white hover:bg-accent-hover active:scale-90 shadow-md shadow-accent/25" : isLoading ? "bg-text-muted/20 text-text-secondary hover:bg-text-muted/30" : "bg-transparent text-text-muted/40 cursor-default"
1435
+ canSend ? "bg-accent text-white hover:bg-accent-hover active:scale-90 shadow-md shadow-accent/25" : isLoading ? "bg-text-muted/20 text-text-secondary hover:bg-text-muted/30 cursor-pointer" : "bg-transparent text-text-muted/40 cursor-default"
1347
1436
  ),
1348
1437
  children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ArrowUpIcon, {})
1349
1438
  }
@@ -1499,9 +1588,10 @@ var phaseLabels = {
1499
1588
  verifying: "Verifying..."
1500
1589
  };
1501
1590
  var CURSOR_STYLES = `
1502
- .sk-streaming-cursor > :not(ul,ol,blockquote):last-child::after,
1591
+ .sk-streaming-cursor > :not(ul,ol,blockquote,div:has(table)):last-child::after,
1503
1592
  .sk-streaming-cursor > :is(ul,ol):last-child > li:last-child::after,
1504
- .sk-streaming-cursor > blockquote:last-child > p:last-child::after {
1593
+ .sk-streaming-cursor > blockquote:last-child > p:last-child::after,
1594
+ .sk-streaming-cursor > div:has(table):last-child table tbody tr:last-child td:last-child::after {
1505
1595
  content: "";
1506
1596
  display: inline-block;
1507
1597
  width: 2px;
@@ -1635,7 +1725,7 @@ function AgentChat({
1635
1725
  onQuestionSelect: handleQuestionSelect
1636
1726
  }
1637
1727
  ),
1638
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageComposer, { onSend: handleSend, isLoading: state.isLoading })
1728
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageComposer, { onSend: handleSend, onStop: actions.stop, isLoading: state.isLoading })
1639
1729
  ]
1640
1730
  }
1641
1731
  );
@@ -2451,14 +2541,14 @@ function ConversationList({
2451
2541
  "nav",
2452
2542
  {
2453
2543
  "aria-label": "Conversation list",
2454
- className: (0, import_tailwind_merge14.twMerge)("flex flex-col h-full bg-canvas", className),
2544
+ className: (0, import_tailwind_merge14.twMerge)("flex flex-col flex-1 min-h-0", className),
2455
2545
  children: [
2456
- onNew && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "p-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2546
+ onNew && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "px-5 pt-1 pb-3 border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2457
2547
  "button",
2458
2548
  {
2459
2549
  type: "button",
2460
2550
  onClick: onNew,
2461
- className: "w-full px-4 py-2.5 rounded-xl text-sm font-semibold bg-accent text-white hover:bg-accent-hover transition-all duration-200",
2551
+ className: "w-full px-4 py-2 rounded-lg text-sm font-medium border border-border text-text-secondary hover:text-text-primary hover:bg-surface hover:border-border-strong transition-colors duration-150",
2462
2552
  children: "New conversation"
2463
2553
  }
2464
2554
  ) }),
@@ -2469,9 +2559,9 @@ function ConversationList({
2469
2559
  "li",
2470
2560
  {
2471
2561
  className: (0, import_tailwind_merge14.twMerge)(
2472
- "flex items-start border-b border-border transition-colors duration-200",
2473
- "hover:bg-surface",
2474
- isActive && "bg-surface-raised border-l-2 border-l-accent"
2562
+ "flex items-start transition-colors duration-150",
2563
+ "hover:bg-surface-raised",
2564
+ isActive ? "bg-accent-subtlest border-l-[3px] border-l-accent" : "border-l-[3px] border-l-transparent"
2475
2565
  ),
2476
2566
  children: [
2477
2567
  /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
@@ -2480,10 +2570,10 @@ function ConversationList({
2480
2570
  type: "button",
2481
2571
  onClick: () => onSelect(conversation.id),
2482
2572
  "aria-current": isActive ? "true" : void 0,
2483
- className: "flex-1 min-w-0 text-left px-4 py-3",
2573
+ className: "flex-1 min-w-0 text-left px-5 py-2.5",
2484
2574
  children: [
2485
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "text-sm font-medium text-brand-cream truncate", children: conversation.title }),
2486
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "text-xs text-brand-cream/40 truncate mt-0.5 leading-relaxed", children: conversation.lastMessage })
2575
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "text-sm font-medium text-text-primary truncate", children: conversation.title }),
2576
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "text-xs text-text-muted truncate mt-0.5 leading-relaxed", children: conversation.lastMessage })
2487
2577
  ]
2488
2578
  }
2489
2579
  ),
@@ -2493,7 +2583,7 @@ function ConversationList({
2493
2583
  type: "button",
2494
2584
  onClick: () => onDelete(conversation.id),
2495
2585
  "aria-label": `Delete ${conversation.title}`,
2496
- className: "shrink-0 p-1.5 m-2 rounded-lg text-brand-cream/25 hover:text-brand-watermelon hover:bg-brand-watermelon/10 transition-colors duration-200",
2586
+ className: "shrink-0 p-1.5 m-2 rounded-lg text-text-muted hover:text-status-error hover:bg-status-error/10 transition-colors duration-150",
2497
2587
  children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
2498
2588
  "svg",
2499
2589
  {
@@ -2520,7 +2610,7 @@ function ConversationList({
2520
2610
  conversation.id
2521
2611
  );
2522
2612
  }),
2523
- conversations.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { className: "px-4 py-8 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-sm text-brand-cream/30 font-body", children: "No conversations yet" }) })
2613
+ conversations.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { className: "px-5 py-12 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "text-sm text-text-muted font-body", children: "No conversations yet" }) })
2524
2614
  ] })
2525
2615
  ]
2526
2616
  }