| |
| function chatApp() { |
| return { |
| |
| |
| |
| messages: [], |
| userInput: '', |
| loading: false, |
| dark: false, |
|
|
| |
| |
| |
| init() { |
| |
| this.dark = window.matchMedia('(prefers-color-scheme: dark)').matches; |
| this.applyDarkMode(); |
|
|
| |
| window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { |
| this.dark = e.matches; |
| this.applyDarkMode(); |
| }); |
| }, |
|
|
| |
| |
| |
| toggleDark() { |
| this.dark = !this.dark; |
| this.applyDarkMode(); |
| }, |
| applyDarkMode() { |
| if (this.dark) { |
| document.documentElement.classList.add('dark'); |
| } else { |
| document.documentElement.classList.remove('dark'); |
| } |
| }, |
|
|
| |
| |
| |
| autoResize(event) { |
| const el = event.target; |
| el.style.height = 'auto'; |
| el.style.height = `${el.scrollHeight}px`; |
| }, |
|
|
| |
| |
| |
| async sendMessage() { |
| const content = this.userInput.trim(); |
| if (!content) return; |
|
|
| |
| const userMsg = { |
| id: Date.now(), |
| role: 'user', |
| content: this.escapeHtml(content).replace(/\n/g, '<br>') |
| }; |
| this.messages.push(userMsg); |
| this.userInput = ''; |
| this.scrollToBottom(); |
|
|
| |
| this.loading = true; |
|
|
| |
| try { |
| const response = await fetch('/v1/chat/completions', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| model: 'qwen2.5-1.5b-instruct', |
| messages: [ |
| |
| ...this.messages.map(m => ({ |
| role: m.role, |
| content: this.stripHtml(m.content) |
| })) |
| ], |
| temperature: 0.7, |
| max_tokens: 512, |
| top_p: 0.9, |
| stream: false |
| }) |
| }); |
|
|
| if (!response.ok) { |
| const err = await response.json(); |
| throw new Error(err.detail || 'API error'); |
| } |
|
|
| const data = await response.json(); |
| const assistant = data.choices[0].message.content; |
|
|
| |
| const assistantMsg = { |
| id: Date.now() + 1, |
| role: 'assistant', |
| content: this.escapeHtml(assistant).replace(/\n/g, '<br>') |
| }; |
| this.messages.push(assistantMsg); |
| this.scrollToBottom(); |
| } catch (e) { |
| console.error(e); |
| const errMsg = { |
| id: Date.now() + 1, |
| role: 'assistant', |
| content: `<span class="text-red-500">⚠️ ${this.escapeHtml(e.message)}</span>` |
| }; |
| this.messages.push(errMsg); |
| this.scrollToBottom(); |
| } finally { |
| this.loading = false; |
| } |
| }, |
|
|
| |
| |
| |
| scrollToBottom() { |
| this.$nextTick(() => { |
| const chat = document.getElementById('chat-window'); |
| chat.scrollTop = chat.scrollHeight; |
| }); |
| }, |
|
|
| |
| escapeHtml(str) { |
| const div = document.createElement('div'); |
| div.appendChild(document.createTextNode(str)); |
| return div.innerHTML; |
| }, |
|
|
| |
| stripHtml(html) { |
| const div = document.createElement('div'); |
| div.innerHTML = html; |
| return div.textContent || div.innerText || ''; |
| } |
| }; |
| } |
|
|