diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 587d7dc..ba6ab1e 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,6 +1,7 @@
diff --git a/frontend/src/components/TopicDetail.vue b/frontend/src/components/TopicDetail.vue
index 416cba3..be4abfe 100644
--- a/frontend/src/components/TopicDetail.vue
+++ b/frontend/src/components/TopicDetail.vue
@@ -2,6 +2,7 @@
import { computed, ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { fetchGuideContent, chatGuide, fetchProgress, setProgress } from '../api.js'
import { renderMarkdown } from '../markdown.js'
+import { useChat } from '../composables/useChat.js'
const props = defineProps({
previewGuide: { type: Object, default: null },
@@ -72,13 +73,16 @@ async function toggleChapter(title) {
}
}
-// --- Chat ---
+// --- Chat (Mechanik in useChat; Kontext-Extraktion bleibt hier) ---
+const chat = useChat((msgs) => {
+ const { section, outline } = extractContext()
+ return chatGuide(props.previewGuide.id, {
+ section, outline, messages: msgs, provider: props.provider,
+ })
+})
+const { messages, input, loading, messagesEl, inputEl, send } = chat
+const autoGrow = () => chat.autoGrow()
const chatOpen = ref(false)
-const messages = ref([])
-const input = ref('')
-const loading = ref(false)
-const messagesEl = ref(null)
-const inputEl = ref(null)
const panelEl = ref(null)
function openChat() {
@@ -88,8 +92,7 @@ function openChat() {
function closeChat() {
chatOpen.value = false
- messages.value = []
- input.value = ''
+ chat.reset()
}
function onDocMouseDown(e) {
@@ -141,43 +144,6 @@ function extractContext() {
return { section, outline }
}
-async function scrollToBottom() {
- await nextTick()
- if (messagesEl.value) messagesEl.value.scrollTop = messagesEl.value.scrollHeight
-}
-
-function autoGrow() {
- const el = inputEl.value
- if (!el) return
- el.style.height = 'auto'
- el.style.height = Math.min(el.scrollHeight, 140) + 'px'
-}
-
-async function send() {
- const text = input.value.trim()
- if (!text || loading.value || !props.previewGuide) return
- messages.value.push({ role: 'user', content: text })
- input.value = ''
- nextTick(autoGrow)
- loading.value = true
- scrollToBottom()
- try {
- const { section, outline } = extractContext()
- const res = await chatGuide(props.previewGuide.id, {
- section,
- outline,
- messages: messages.value,
- provider: props.provider,
- })
- messages.value.push({ role: 'assistant', content: res.reply || '…' })
- } catch {
- messages.value.push({ role: 'assistant', content: 'Fehler bei der Anfrage.' })
- } finally {
- loading.value = false
- scrollToBottom()
- nextTick(() => inputEl.value?.focus())
- }
-}
@@ -250,7 +216,12 @@ async function send() {
@input="autoGrow"
@keydown.enter.exact.prevent="send"
>
-
+
@@ -698,4 +669,8 @@ async function send() {
opacity: 0.4;
cursor: not-allowed;
}
+
+.chat-input button.cancel {
+ background: var(--danger);
+}
diff --git a/frontend/src/components/TopicSidebar.vue b/frontend/src/components/TopicSidebar.vue
index 8c8d22f..0184a31 100644
--- a/frontend/src/components/TopicSidebar.vue
+++ b/frontend/src/components/TopicSidebar.vue
@@ -1,5 +1,6 @@