chizk commited on
Commit
e39cf8d
·
verified ·
1 Parent(s): 477333d

Upload 2 files

Browse files
Files changed (2) hide show
  1. static/script.js +146 -0
  2. static/style.css +25 -0
static/script.js ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* static/script.js */
2
+ function chatApp() {
3
+ return {
4
+ // ------------------------------
5
+ // State
6
+ // ------------------------------
7
+ messages: [], // {id, role, content}
8
+ userInput: '',
9
+ loading: false,
10
+ dark: false, // dark‑mode flag
11
+
12
+ // ------------------------------
13
+ // Init – runs once when component mounts
14
+ // ------------------------------
15
+ init() {
16
+ // Detect system dark‑mode preference
17
+ this.dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
18
+ this.applyDarkMode();
19
+
20
+ // Listen for changes (e.g., user switches OS theme)
21
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
22
+ this.dark = e.matches;
23
+ this.applyDarkMode();
24
+ });
25
+ },
26
+
27
+ // ------------------------------
28
+ // Dark‑mode helper
29
+ // ------------------------------
30
+ toggleDark() {
31
+ this.dark = !this.dark;
32
+ this.applyDarkMode();
33
+ },
34
+ applyDarkMode() {
35
+ if (this.dark) {
36
+ document.documentElement.classList.add('dark');
37
+ } else {
38
+ document.documentElement.classList.remove('dark');
39
+ }
40
+ },
41
+
42
+ // ------------------------------
43
+ // Auto‑grow textarea
44
+ // ------------------------------
45
+ autoResize(event) {
46
+ const el = event.target;
47
+ el.style.height = 'auto';
48
+ el.style.height = `${el.scrollHeight}px`;
49
+ },
50
+
51
+ // ------------------------------
52
+ // Send a message to the back‑end
53
+ // ------------------------------
54
+ async sendMessage() {
55
+ const content = this.userInput.trim();
56
+ if (!content) return;
57
+
58
+ // 1️⃣ Add user bubble
59
+ const userMsg = {
60
+ id: Date.now(),
61
+ role: 'user',
62
+ content: this.escapeHtml(content).replace(/\n/g, '<br>')
63
+ };
64
+ this.messages.push(userMsg);
65
+ this.userInput = '';
66
+ this.scrollToBottom();
67
+
68
+ // 2️⃣ Show loading indicator
69
+ this.loading = true;
70
+
71
+ // 3️⃣ Call the OpenAI‑compatible endpoint
72
+ try {
73
+ const response = await fetch('/v1/chat/completions', {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify({
77
+ model: 'qwen2.5-1.5b-instruct',
78
+ messages: [
79
+ // Keep the whole conversation – the model can handle a few turns
80
+ ...this.messages.map(m => ({
81
+ role: m.role,
82
+ content: this.stripHtml(m.content) // plain text for the model
83
+ }))
84
+ ],
85
+ temperature: 0.7,
86
+ max_tokens: 512,
87
+ top_p: 0.9,
88
+ stream: false // set true later if you implement SSE streaming
89
+ })
90
+ });
91
+
92
+ if (!response.ok) {
93
+ const err = await response.json();
94
+ throw new Error(err.detail || 'API error');
95
+ }
96
+
97
+ const data = await response.json();
98
+ const assistant = data.choices[0].message.content;
99
+
100
+ // 4️⃣ Add assistant bubble
101
+ const assistantMsg = {
102
+ id: Date.now() + 1,
103
+ role: 'assistant',
104
+ content: this.escapeHtml(assistant).replace(/\n/g, '<br>')
105
+ };
106
+ this.messages.push(assistantMsg);
107
+ this.scrollToBottom();
108
+ } catch (e) {
109
+ console.error(e);
110
+ const errMsg = {
111
+ id: Date.now() + 1,
112
+ role: 'assistant',
113
+ content: `<span class="text-red-500">⚠️ ${this.escapeHtml(e.message)}</span>`
114
+ };
115
+ this.messages.push(errMsg);
116
+ this.scrollToBottom();
117
+ } finally {
118
+ this.loading = false;
119
+ }
120
+ },
121
+
122
+ // ------------------------------
123
+ // Helpers
124
+ // ------------------------------
125
+ scrollToBottom() {
126
+ this.$nextTick(() => {
127
+ const chat = document.getElementById('chat-window');
128
+ chat.scrollTop = chat.scrollHeight;
129
+ });
130
+ },
131
+
132
+ // Escape HTML to avoid XSS when we later render raw strings
133
+ escapeHtml(str) {
134
+ const div = document.createElement('div');
135
+ div.appendChild(document.createTextNode(str));
136
+ return div.innerHTML;
137
+ },
138
+
139
+ // Strip HTML tags – used when sending the prompt to the model
140
+ stripHtml(html) {
141
+ const div = document.createElement('div');
142
+ div.innerHTML = html;
143
+ return div.textContent || div.innerText || '';
144
+ }
145
+ };
146
+ }
static/style.css ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* static/style.css */
2
+
3
+ /* Smooth scrolling for the chat window */
4
+ #chat-window {
5
+ scroll-behavior: smooth;
6
+ }
7
+
8
+ /* Hide the default scrollbar on macOS while keeping it usable */
9
+ #chat-window::-webkit-scrollbar {
10
+ width: 6px;
11
+ }
12
+ #chat-window::-webkit-scrollbar-thumb {
13
+ background-color: rgba(100, 100, 100, 0.4);
14
+ border-radius: 3px;
15
+ }
16
+
17
+ /* Auto‑resize textarea – hide the native resize handle */
18
+ textarea {
19
+ overflow-y: hidden;
20
+ }
21
+
22
+ /* Dark‑mode transition (nice fade) */
23
+ html {
24
+ transition: background-color 0.2s, color 0.2s;
25
+ }