Some checks failed
ci / ci (22, ubuntu-latest) (push) Failing after 25m52s
Made-with: Cursor
146 lines
4.3 KiB
TypeScript
146 lines
4.3 KiB
TypeScript
import type { AiConversation, AiMessage } from '~/types/ai'
|
|
|
|
export function useAiChat() {
|
|
const client = useSupabaseClient()
|
|
const user = useSupabaseUser()
|
|
|
|
const conversations = ref<AiConversation[]>([])
|
|
const currentConversation = ref<AiConversation | null>(null)
|
|
const messages = ref<AiMessage[]>([])
|
|
const streamingContent = ref('')
|
|
const isStreaming = ref(false)
|
|
const loading = ref(false)
|
|
|
|
async function fetchConversations() {
|
|
if (!user.value) return
|
|
const { data } = await client
|
|
.from('ai_conversations')
|
|
.select('*')
|
|
.order('updated_at', { ascending: false })
|
|
conversations.value = (data as AiConversation[]) ?? []
|
|
}
|
|
|
|
async function createConversation(title: string = '새 대화') {
|
|
if (!user.value) return null
|
|
const { data, error } = await client
|
|
.from('ai_conversations')
|
|
.insert({ user_id: user.value.id, title })
|
|
.select()
|
|
.single()
|
|
if (error) return null
|
|
const conv = data as AiConversation
|
|
conversations.value.unshift(conv)
|
|
return conv
|
|
}
|
|
|
|
async function selectConversation(conv: AiConversation) {
|
|
currentConversation.value = conv
|
|
await fetchMessages(conv.id)
|
|
}
|
|
|
|
async function fetchMessages(conversationId: string) {
|
|
const { data } = await client
|
|
.from('ai_messages')
|
|
.select('*')
|
|
.eq('conversation_id', conversationId)
|
|
.order('created_at', { ascending: true })
|
|
messages.value = (data as AiMessage[]) ?? []
|
|
}
|
|
|
|
async function deleteConversation(id: string) {
|
|
await client.from('ai_conversations').delete().eq('id', id)
|
|
conversations.value = conversations.value.filter(c => c.id !== id)
|
|
if (currentConversation.value?.id === id) {
|
|
currentConversation.value = null
|
|
messages.value = []
|
|
}
|
|
}
|
|
|
|
async function sendMessage(content: string) {
|
|
if (!currentConversation.value || isStreaming.value) return
|
|
|
|
// Save user message to DB
|
|
const { data: userMsg } = await client
|
|
.from('ai_messages')
|
|
.insert({
|
|
conversation_id: currentConversation.value.id,
|
|
role: 'user',
|
|
content
|
|
})
|
|
.select()
|
|
.single()
|
|
|
|
if (userMsg) messages.value.push(userMsg as AiMessage)
|
|
|
|
// Start streaming
|
|
isStreaming.value = true
|
|
streamingContent.value = ''
|
|
|
|
try {
|
|
const response = await fetch('/api/ai/chat', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
conversationId: currentConversation.value.id,
|
|
messages: messages.value.map(m => ({ role: m.role, content: m.content }))
|
|
})
|
|
})
|
|
|
|
if (!response.ok) throw new Error('API 오류')
|
|
|
|
const reader = response.body!.getReader()
|
|
const decoder = new TextDecoder()
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
streamingContent.value += decoder.decode(value, { stream: true })
|
|
}
|
|
|
|
// Save assistant message to DB
|
|
const finalContent = streamingContent.value
|
|
const { data: assistantMsg } = await client
|
|
.from('ai_messages')
|
|
.insert({
|
|
conversation_id: currentConversation.value.id,
|
|
role: 'assistant',
|
|
content: finalContent
|
|
})
|
|
.select()
|
|
.single()
|
|
|
|
if (assistantMsg) messages.value.push(assistantMsg as AiMessage)
|
|
|
|
// Update conversation title if it's the first message
|
|
if (messages.value.length === 2) {
|
|
const title = content.slice(0, 30) + (content.length > 30 ? '...' : '')
|
|
await client
|
|
.from('ai_conversations')
|
|
.update({ title })
|
|
.eq('id', currentConversation.value.id)
|
|
const conv = conversations.value.find(c => c.id === currentConversation.value!.id)
|
|
if (conv) conv.title = title
|
|
}
|
|
} catch (e) {
|
|
console.error('Streaming error:', e)
|
|
} finally {
|
|
isStreaming.value = false
|
|
streamingContent.value = ''
|
|
}
|
|
}
|
|
|
|
return {
|
|
conversations: readonly(conversations),
|
|
currentConversation: readonly(currentConversation),
|
|
messages: readonly(messages),
|
|
streamingContent: readonly(streamingContent),
|
|
isStreaming: readonly(isStreaming),
|
|
loading: readonly(loading),
|
|
fetchConversations,
|
|
createConversation,
|
|
selectConversation,
|
|
deleteConversation,
|
|
sendMessage
|
|
}
|
|
}
|