diff --git a/frontend/src/main.js b/frontend/src/main.js index 2671fb0..b6b1677 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -4,6 +4,7 @@ import router from './router' import App from './App.vue' import './assets/main.css' import { useAuthStore } from './store/authStore.js' +import { useChatSessionStore } from './store/chatSessionStore.js' const app = createApp(App) const pinia = createPinia() @@ -11,5 +12,6 @@ app.use(pinia) app.use(router) useAuthStore().restoreSession() +useChatSessionStore().restoreSessions() app.mount('#app') diff --git a/frontend/src/store/chatSessionStore.js b/frontend/src/store/chatSessionStore.js new file mode 100644 index 0000000..a16e0d9 --- /dev/null +++ b/frontend/src/store/chatSessionStore.js @@ -0,0 +1,132 @@ +import { defineStore } from 'pinia' +import { sendChatMessage } from '../api/chat.js' + +const STORAGE_KEY = 'salmanhae.chatSessions' +const MAX_SESSIONS = 20 + +const generateId = () => `sess_${Date.now()}_${Math.random().toString(36).slice(2, 7)}` + +const makeSession = () => ({ + id: generateId(), + title: '새 대화', + messages: [], + createdAt: new Date().toISOString(), +}) + +export const useChatSessionStore = defineStore('chatSession', { + state: () => ({ + sessions: [], + currentSessionId: null, + isChatLoading: false, + chatError: '', + _chatRequestSeq: 0, + }), + + getters: { + currentSession: (state) => + state.sessions.find((s) => s.id === state.currentSessionId) ?? null, + chatMessages: (state) => + state.sessions.find((s) => s.id === state.currentSessionId)?.messages ?? [], + sessionList: (state) => + [...state.sessions].sort((a, b) => b.createdAt.localeCompare(a.createdAt)), + }, + + actions: { + restoreSessions() { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return + const parsed = JSON.parse(raw) + if (Array.isArray(parsed) && parsed.length) { + this.sessions = parsed.slice(0, MAX_SESSIONS) + this.currentSessionId = this.sessions[0].id + } + } catch { + // ignore + } + }, + + _persist() { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(this.sessions.slice(0, MAX_SESSIONS))) + } catch { + // ignore + } + }, + + createSession() { + const session = makeSession() + this.sessions.unshift(session) + this.currentSessionId = session.id + this._persist() + return session + }, + + switchSession(id) { + if (this.sessions.some((s) => s.id === id)) { + this.currentSessionId = id + } + }, + + _ensureSession() { + if (!this.currentSessionId || !this.sessions.some((s) => s.id === this.currentSessionId)) { + this.createSession() + } + }, + + _updateTitle(session, firstUserMessage) { + if (session.title === '새 대화') { + session.title = firstUserMessage.slice(0, 30) + (firstUserMessage.length > 30 ? '…' : '') + } + }, + + async sendChat(message, selectedPropertyId = null) { + const text = String(message || '').trim() + if (!text || this.isChatLoading) return + + this._ensureSession() + const session = this.sessions.find((s) => s.id === this.currentSessionId) + if (!session) return + + const seq = ++this._chatRequestSeq + session.messages.push({ role: 'user', text }) + this._updateTitle(session, text) + this._persist() + + this.isChatLoading = true + this.chatError = '' + + try { + const response = await sendChatMessage({ + message: text, + sessionId: session.id, + selectedPropertyId, + }) + if (seq !== this._chatRequestSeq) return + + session.messages.push({ + role: 'bot', + text: response.message, + intent: response.intent, + legalCards: response.legalCards, + analysisCards: response.analysisCards, + properties: response.properties, + }) + this._persist() + } catch (error) { + if (seq !== this._chatRequestSeq) return + this.chatError = error.response?.data?.message || 'AI 응답을 불러오지 못했습니다.' + session.messages.push({ + role: 'bot', + text: this.chatError, + isError: true, + legalCards: [], + analysisCards: [], + }) + this._persist() + } finally { + if (seq === this._chatRequestSeq) this.isChatLoading = false + } + }, + }, +}) diff --git a/frontend/src/store/mapStore.js b/frontend/src/store/mapStore.js index 8803d7a..8a4facf 100644 --- a/frontend/src/store/mapStore.js +++ b/frontend/src/store/mapStore.js @@ -1,5 +1,5 @@ +// 채팅 관련 state/action은 chatSessionStore로 이관됨 import { defineStore } from 'pinia' -import { sendChatMessage } from '../api/chat.js' import { fetchMapViewport, fetchPropertyDetail } from '../api/properties.js' import { isPropertyItem, VIEWPORT_MODES } from '../utils/mapViewport.js' @@ -60,16 +60,11 @@ export default defineStore('map', { }, isLoading: false, isDetailLoading: false, - isChatLoading: false, error: '', detailError: '', - chatError: '', - chatSessionId: null, - chatMessages: [], lastFetchedAt: null, requestSeq: 0, - detailRequestSeq: 0, - chatRequestSeq: 0 + detailRequestSeq: 0 }), getters: { regions: () => [ @@ -221,48 +216,5 @@ export default defineStore('map', { this.isDetailLoading = false this.selectedViewportItem = item }, - async sendChat(message) { - const text = String(message || '').trim() - if (!text || this.isChatLoading) return - - const seq = ++this.chatRequestSeq - this.chatMessages.push({ role: 'user', text }) - this.isChatLoading = true - this.chatError = '' - - try { - const response = await sendChatMessage({ - message: text, - sessionId: this.chatSessionId, - selectedPropertyId: this.selectedPropertyId - }) - - if (seq !== this.chatRequestSeq) return - - this.chatSessionId = response.sessionId || this.chatSessionId - this.chatMessages.push({ - role: 'bot', - text: response.message, - intent: response.intent, - legalCards: response.legalCards, - analysisCards: response.analysisCards, - properties: response.properties - }) - } catch (error) { - if (seq !== this.chatRequestSeq) return - this.chatError = error.response?.data?.message || 'AI 계약 상담 응답을 불러오지 못했습니다.' - this.chatMessages.push({ - role: 'bot', - text: this.chatError, - isError: true, - legalCards: [], - analysisCards: [] - }) - } finally { - if (seq === this.chatRequestSeq) { - this.isChatLoading = false - } - } - } } }) diff --git a/frontend/src/views/Chatbot.vue b/frontend/src/views/Chatbot.vue index 36bf767..c756aab 100644 --- a/frontend/src/views/Chatbot.vue +++ b/frontend/src/views/Chatbot.vue @@ -7,7 +7,7 @@
{{ msg.text }}
@@ -74,7 +74,7 @@