From 439e49adae5e877e892903c8eb308f12ba86dce4 Mon Sep 17 00:00:00 2001 From: crolvlee Date: Thu, 25 Jun 2026 14:03:13 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix(chat):=20=EC=B1=84=ED=8C=85=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20UI=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EB=B0=B1?= =?UTF-8?q?=EC=97=94=EB=93=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Chatbot.vue: 세션 사이드바·메시지 렌더링·분석 카드 UI 구현 - chatSessionStore: 세션 생성·전환·로컬스토리지 유지 로직 구현 - ChatResponse: Map::copyOf → HashMap::new로 NPE 수정 - application.properties: HikariCP prepareThreshold=0 설정으로 prepared statement 충돌 방지 - price_analysis/safety_analysis: requiresSelection=True 시 분석 카드 미생성 - analysis_answer_service: 매물 미선택 시 안내 텍스트 반환으로 개선 Co-Authored-By: Claude Sonnet 4.6 --- backend-ai/app/graph/nodes/price_analysis.py | 1 + backend-ai/app/graph/nodes/safety_analysis.py | 17 ++- .../model/dto/chat/ChatResponse.java | 3 +- .../src/main/resources/application.properties | 3 + frontend/src/store/chatSessionStore.js | 13 ++- frontend/src/views/Chatbot.vue | 109 +++++++++++++++++- scripts/execute.py | 6 +- 7 files changed, 140 insertions(+), 12 deletions(-) diff --git a/backend-ai/app/graph/nodes/price_analysis.py b/backend-ai/app/graph/nodes/price_analysis.py index e0a316f..077232c 100644 --- a/backend-ai/app/graph/nodes/price_analysis.py +++ b/backend-ai/app/graph/nodes/price_analysis.py @@ -16,6 +16,7 @@ def price_analysis(state: AgentState) -> AgentState: if result.get("requiresSelection"): result = _regional_price_analysis(state["message"]) + updated_tool_results = { **state.get("tool_results", {}), "priceAnalysis": result, diff --git a/backend-ai/app/graph/nodes/safety_analysis.py b/backend-ai/app/graph/nodes/safety_analysis.py index cea7b5d..1d3e4fe 100644 --- a/backend-ai/app/graph/nodes/safety_analysis.py +++ b/backend-ai/app/graph/nodes/safety_analysis.py @@ -8,6 +8,18 @@ def safety_analysis(state: AgentState) -> AgentState: message=state["message"], context=state.get("context", {}), ) + + updated_tool_results = { + **state.get("tool_results", {}), + "safetyAnalysis": result, + } + + if result.get("requiresSelection"): + return { + **state, + "tool_results": updated_tool_results, + } + metrics = { "selectedPropertyId": result.get("selectedPropertyId"), "stub": result.get("stub", False), @@ -26,8 +38,5 @@ def safety_analysis(state: AgentState) -> AgentState: return { **state, "analysis_cards": [*state.get("analysis_cards", []), analysis_card], - "tool_results": { - **state.get("tool_results", {}), - "safetyAnalysis": result, - }, + "tool_results": updated_tool_results, } diff --git a/backend/src/main/java/com/ssafy/salmanhae/model/dto/chat/ChatResponse.java b/backend/src/main/java/com/ssafy/salmanhae/model/dto/chat/ChatResponse.java index b4ab43d..f312bc6 100644 --- a/backend/src/main/java/com/ssafy/salmanhae/model/dto/chat/ChatResponse.java +++ b/backend/src/main/java/com/ssafy/salmanhae/model/dto/chat/ChatResponse.java @@ -1,5 +1,6 @@ package com.ssafy.salmanhae.model.dto.chat; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -13,7 +14,7 @@ public record ChatResponse( ) { public ChatResponse { properties = properties == null ? List.of() : properties.stream() - .map(Map::copyOf) + .>map(HashMap::new) .toList(); legalCards = legalCards == null ? List.of() : List.copyOf(legalCards); analysisCards = analysisCards == null ? List.of() : List.copyOf(analysisCards); diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index aa44f3d..64d635d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -7,6 +7,9 @@ spring.datasource.url=${SUPABASE_DB_URL} spring.datasource.username=${SUPABASE_DB_USERNAME} spring.datasource.password=${SUPABASE_DB_PASSWORD} +# HikariCP - disable server-side prepared statements to prevent "prepared statement already exists" errors on connection reuse +spring.datasource.hikari.data-source-properties.prepareThreshold=0 + # MyBatis mybatis.mapper-locations=classpath:mappers/**/*.xml mybatis.type-aliases-package=com.ssafy.salmanhae.model.dto diff --git a/frontend/src/store/chatSessionStore.js b/frontend/src/store/chatSessionStore.js index a16e0d9..906279c 100644 --- a/frontend/src/store/chatSessionStore.js +++ b/frontend/src/store/chatSessionStore.js @@ -28,7 +28,9 @@ export const useChatSessionStore = defineStore('chatSession', { chatMessages: (state) => state.sessions.find((s) => s.id === state.currentSessionId)?.messages ?? [], sessionList: (state) => - [...state.sessions].sort((a, b) => b.createdAt.localeCompare(a.createdAt)), + [...state.sessions] + .filter((s) => s.messages.length > 0) + .sort((a, b) => b.createdAt.localeCompare(a.createdAt)), }, actions: { @@ -54,11 +56,14 @@ export const useChatSessionStore = defineStore('chatSession', { } }, - createSession() { + newChat() { + this.currentSessionId = null + }, + + _createSession() { const session = makeSession() this.sessions.unshift(session) this.currentSessionId = session.id - this._persist() return session }, @@ -70,7 +75,7 @@ export const useChatSessionStore = defineStore('chatSession', { _ensureSession() { if (!this.currentSessionId || !this.sessions.some((s) => s.id === this.currentSessionId)) { - this.createSession() + this._createSession() } }, diff --git a/frontend/src/views/Chatbot.vue b/frontend/src/views/Chatbot.vue index c756aab..f03f7d7 100644 --- a/frontend/src/views/Chatbot.vue +++ b/frontend/src/views/Chatbot.vue @@ -1,6 +1,27 @@