|
|
import React, { useState, useEffect, useCallback } from 'react' |
|
|
import { Message, ChatSession, MessageStatus } from '@/types/chat' |
|
|
import { chatStorage } from '@/lib/chat-storage' |
|
|
|
|
|
interface UseChatOptions { |
|
|
api_endpoint?: string |
|
|
defaultModel?: string |
|
|
defaultSystemPrompt?: string |
|
|
} |
|
|
|
|
|
interface ApiResponse { |
|
|
thinking_content: string |
|
|
content: string |
|
|
model_used: string |
|
|
supports_thinking: boolean |
|
|
} |
|
|
|
|
|
export function useChat(options: UseChatOptions = {}) { |
|
|
const { |
|
|
api_endpoint = `${window.location.origin}/generate`, |
|
|
defaultModel = 'Qwen/Qwen3-30B-A3B', |
|
|
defaultSystemPrompt = '' |
|
|
} = options |
|
|
|
|
|
|
|
|
const [sessions, setSessions] = useState<ChatSession[]>([]) |
|
|
const [currentSessionId, setCurrentSessionId] = useState<string | null>(null) |
|
|
const [input, setInput] = useState('') |
|
|
const [status, setStatus] = useState<MessageStatus>({ |
|
|
isLoading: false, |
|
|
error: null |
|
|
}) |
|
|
|
|
|
|
|
|
const [selectedModel, setSelectedModel] = useState(defaultModel) |
|
|
const [systemPrompt, setSystemPrompt] = useState(defaultSystemPrompt) |
|
|
const [temperature, setTemperature] = useState(0.7) |
|
|
const [maxTokens, setMaxTokens] = useState(1024) |
|
|
|
|
|
|
|
|
const currentSession = React.useMemo(() => { |
|
|
const session = sessions.find((s: any) => s.id === currentSessionId) || null |
|
|
console.log('useChat - currentSession updated:', { |
|
|
sessionId: currentSessionId, |
|
|
found: !!session, |
|
|
messageCount: session?.messages?.length || 0 |
|
|
}) |
|
|
return session |
|
|
}, [sessions, currentSessionId]) |
|
|
|
|
|
const messages = React.useMemo(() => { |
|
|
const msgs = currentSession?.messages || [] |
|
|
console.log('useChat - messages computed:', msgs.length, msgs.map(m => ({ id: m.id, role: m.role }))) |
|
|
return msgs |
|
|
}, [currentSession?.messages]) |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
const loadedSessions = chatStorage.getAllSessions() |
|
|
setSessions(loadedSessions) |
|
|
|
|
|
const currentSession = chatStorage.getCurrentSession() |
|
|
if (currentSession) { |
|
|
setCurrentSessionId(currentSession.id) |
|
|
} else if (loadedSessions.length > 0) { |
|
|
setCurrentSessionId(loadedSessions[0].id) |
|
|
chatStorage.setCurrentSession(loadedSessions[0].id) |
|
|
} |
|
|
}, []) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const createNewSession = useCallback(() => { |
|
|
const newSession = chatStorage.createSession( |
|
|
undefined, |
|
|
selectedModel, |
|
|
systemPrompt |
|
|
) |
|
|
|
|
|
|
|
|
const updatedSessions = chatStorage.getAllSessions() |
|
|
setSessions([...updatedSessions]) |
|
|
setCurrentSessionId(newSession.id) |
|
|
chatStorage.setCurrentSession(newSession.id) |
|
|
|
|
|
return newSession.id |
|
|
}, [selectedModel, systemPrompt]) |
|
|
|
|
|
|
|
|
const selectSession = useCallback((sessionId: string) => { |
|
|
setCurrentSessionId(sessionId) |
|
|
chatStorage.setCurrentSession(sessionId) |
|
|
}, []) |
|
|
|
|
|
|
|
|
const deleteSession = useCallback((sessionId: string) => { |
|
|
chatStorage.deleteSession(sessionId) |
|
|
const updatedSessions = chatStorage.getAllSessions() |
|
|
setSessions([...updatedSessions]) |
|
|
|
|
|
if (currentSessionId === sessionId) { |
|
|
if (updatedSessions.length > 0) { |
|
|
setCurrentSessionId(updatedSessions[0].id) |
|
|
chatStorage.setCurrentSession(updatedSessions[0].id) |
|
|
} else { |
|
|
setCurrentSessionId(null) |
|
|
} |
|
|
} |
|
|
}, [currentSessionId]) |
|
|
|
|
|
|
|
|
const renameSession = useCallback((sessionId: string, newTitle: string) => { |
|
|
chatStorage.updateSession(sessionId, { title: newTitle }) |
|
|
const updatedSessions = chatStorage.getAllSessions() |
|
|
setSessions([...updatedSessions]) |
|
|
}, []) |
|
|
|
|
|
|
|
|
const addMessage = useCallback((message: Omit<Message, 'id' | 'timestamp'>) => { |
|
|
if (!currentSessionId) return |
|
|
|
|
|
chatStorage.addMessageToSession(currentSessionId, message) |
|
|
|
|
|
const updatedSessions = chatStorage.getAllSessions() |
|
|
setSessions([...updatedSessions]) |
|
|
}, [currentSessionId]) |
|
|
|
|
|
|
|
|
const sendMessage = useCallback(async () => { |
|
|
if (!input.trim() || status.isLoading) return |
|
|
|
|
|
let sessionId = currentSessionId |
|
|
|
|
|
|
|
|
if (!sessionId) { |
|
|
sessionId = createNewSession() |
|
|
} |
|
|
|
|
|
const userMessage = input.trim() |
|
|
setInput('') |
|
|
setStatus({ isLoading: true, error: null }) |
|
|
|
|
|
|
|
|
if (sessionId) { |
|
|
chatStorage.addMessageToSession(sessionId, { |
|
|
role: 'user', |
|
|
content: userMessage |
|
|
}) |
|
|
|
|
|
const updatedSessions = chatStorage.getAllSessions() |
|
|
setSessions([...updatedSessions]) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const actualSession = chatStorage.getSession(sessionId!) |
|
|
const hasMessages = actualSession?.messages && actualSession.messages.length > 1 |
|
|
|
|
|
if (systemPrompt && !hasMessages && sessionId) { |
|
|
chatStorage.addMessageToSession(sessionId, { |
|
|
role: 'system', |
|
|
content: systemPrompt |
|
|
}) |
|
|
|
|
|
const updatedSessions = chatStorage.getAllSessions() |
|
|
setSessions([...updatedSessions]) |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
const actualSession = chatStorage.getSession(sessionId!) |
|
|
const conversationHistory = actualSession?.messages |
|
|
?.filter((msg: any) => msg.role !== 'system') |
|
|
?.map((msg: any) => ({ |
|
|
role: msg.role, |
|
|
content: msg.content |
|
|
})) || [] |
|
|
|
|
|
const response = await fetch(api_endpoint, { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
prompt: userMessage, |
|
|
messages: conversationHistory, |
|
|
system_prompt: systemPrompt || null, |
|
|
model_name: selectedModel, |
|
|
temperature, |
|
|
max_new_tokens: maxTokens |
|
|
}), |
|
|
}) |
|
|
|
|
|
if (!response.ok) { |
|
|
const errorData = await response.json() |
|
|
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`) |
|
|
} |
|
|
|
|
|
const data: ApiResponse = await response.json() |
|
|
|
|
|
|
|
|
if (sessionId) { |
|
|
chatStorage.addMessageToSession(sessionId, { |
|
|
role: 'assistant', |
|
|
content: data.content, |
|
|
thinking_content: data.thinking_content, |
|
|
model_used: data.model_used, |
|
|
supports_thinking: data.supports_thinking |
|
|
}) |
|
|
|
|
|
const updatedSessions = chatStorage.getAllSessions() |
|
|
setSessions([...updatedSessions]) |
|
|
} |
|
|
|
|
|
setStatus({ isLoading: false, error: null }) |
|
|
} catch (error) { |
|
|
const errorMessage = error instanceof Error ? error.message : 'An error occurred' |
|
|
setStatus({ isLoading: false, error: errorMessage }) |
|
|
|
|
|
|
|
|
if (sessionId) { |
|
|
chatStorage.addMessageToSession(sessionId, { |
|
|
role: 'assistant', |
|
|
content: `Sorry, I encountered an error: ${errorMessage}` |
|
|
}) |
|
|
|
|
|
const updatedSessions = chatStorage.getAllSessions() |
|
|
setSessions([...updatedSessions]) |
|
|
} |
|
|
} |
|
|
}, [ |
|
|
input, |
|
|
status.isLoading, |
|
|
currentSessionId, |
|
|
createNewSession, |
|
|
addMessage, |
|
|
systemPrompt, |
|
|
messages.length, |
|
|
api_endpoint, |
|
|
selectedModel, |
|
|
temperature, |
|
|
maxTokens |
|
|
]) |
|
|
|
|
|
|
|
|
const stopGeneration = useCallback(() => { |
|
|
setStatus({ isLoading: false, error: null }) |
|
|
}, []) |
|
|
|
|
|
|
|
|
const clearAllSessions = useCallback(() => { |
|
|
chatStorage.clear() |
|
|
setSessions([]) |
|
|
setCurrentSessionId(null) |
|
|
}, []) |
|
|
|
|
|
return { |
|
|
|
|
|
sessions, |
|
|
currentSession, |
|
|
currentSessionId, |
|
|
createNewSession, |
|
|
selectSession, |
|
|
deleteSession, |
|
|
renameSession, |
|
|
clearAllSessions, |
|
|
|
|
|
|
|
|
messages, |
|
|
input, |
|
|
setInput, |
|
|
|
|
|
|
|
|
sendMessage, |
|
|
stopGeneration, |
|
|
|
|
|
|
|
|
isLoading: status.isLoading, |
|
|
error: status.error, |
|
|
|
|
|
|
|
|
selectedModel, |
|
|
setSelectedModel, |
|
|
systemPrompt, |
|
|
setSystemPrompt, |
|
|
temperature, |
|
|
setTemperature, |
|
|
maxTokens, |
|
|
setMaxTokens |
|
|
} |
|
|
} |
|
|
|