feat: nuxt-claude 프로젝트 초기 커밋
Some checks failed
ci / ci (22, ubuntu-latest) (push) Failing after 25m52s

Made-with: Cursor
This commit is contained in:
2026-03-08 16:36:13 +09:00
commit e66321386a
44 changed files with 13058 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
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
}
}