-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseClaude.ts
More file actions
124 lines (107 loc) · 3.79 KB
/
Copy pathuseClaude.ts
File metadata and controls
124 lines (107 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { useState, useCallback, useRef } from 'react'
import type { Message } from '../types'
import { useApiKey, authHeaders } from '../contexts/ApiKeyContext'
export type { Message }
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001'
export function useClaude(systemPrompt: string, mode: string) {
const [messages, setMessages] = useState<Message[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const sessionId = useRef<string | null>(null)
const { apiKey } = useApiKey()
const sendMessage = useCallback(
async (userMessage: string) => {
const newMessages: Message[] = [
...messages,
{ role: 'user', content: userMessage },
]
setMessages(newMessages)
setIsLoading(true)
setError(null)
try {
// Create a session on the first message (only if none is attached yet)
if (!sessionId.current) {
const sessionRes = await fetch(`${API_URL}/api/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mode }),
})
const session = await sessionRes.json()
sessionId.current = session.id
}
// Save the user message to the database
await fetch(`${API_URL}/api/sessions/${sessionId.current}/messages`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ role: 'user', content: userMessage }),
})
// Start streaming from the server
const response = await fetch(`${API_URL}/api/chat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders(apiKey),
},
body: JSON.stringify({
messages: newMessages,
systemPrompt,
sessionId: sessionId.current,
}),
})
if (!response.ok) throw new Error('Failed to reach server')
if (!response.body) throw new Error('No response body')
// Add an empty assistant message to fill in as chunks arrive
setMessages(prev => [...prev, { role: 'assistant', content: '' }])
// Read the stream chunk by chunk
const reader = response.body.getReader()
const decoder = new TextDecoder()
let fullText = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
fullText += decoder.decode(value, { stream: true })
setMessages(prev => {
const updated = [...prev]
updated[updated.length - 1] = {
role: 'assistant',
content: fullText,
}
return updated
})
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Something went wrong')
} finally {
setIsLoading(false)
}
},
[messages, systemPrompt, mode, apiKey]
)
const reset = useCallback(() => {
setMessages([])
setError(null)
sessionId.current = null
}, [])
// Hydrate the hook from an existing session (e.g. resume after reload).
// Sets the sessionId so subsequent sendMessage calls continue the same DB row.
const loadSession = useCallback((id: string, history: Message[]) => {
sessionId.current = id
setMessages(history)
setError(null)
}, [])
// Attach a pre-created sessionId to this hook instance without any messages yet.
// Used when we create the DB session upfront (before the first user message).
const attachSession = useCallback((id: string) => {
sessionId.current = id
}, [])
return {
messages,
isLoading,
error,
sendMessage,
reset,
loadSession,
attachSession,
sessionId: sessionId.current,
}
}