Frontend: Composables useConfirm/useChat/usePolling; Guide-Chat abbrechbar
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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())
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -250,7 +216,12 @@ async function send() {
|
||||
@input="autoGrow"
|
||||
@keydown.enter.exact.prevent="send"
|
||||
></textarea>
|
||||
<button :disabled="!input.trim() || loading" @click="send">➤</button>
|
||||
<button
|
||||
:disabled="!input.trim() && !loading"
|
||||
:class="{ cancel: loading }"
|
||||
:title="loading ? 'Abbrechen' : 'Senden'"
|
||||
@click="send"
|
||||
>{{ loading ? '✕' : '➤' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -698,4 +669,8 @@ async function send() {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.chat-input button.cancel {
|
||||
background: var(--danger);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user