@@ -38,7 +38,7 @@ function submitSystemPrompt(event) {
38
38
localStorage . setItem ( "system_prompt" , document . getElementById ( "systemPrompt" ) . value ) ;
39
39
document . getElementById ( "systemPrompt" ) . blur ( ) ;
40
40
}
41
-
41
+
42
42
var image = "" ;
43
43
44
44
function submitPrompt ( event ) {
@@ -54,15 +54,15 @@ function submitPrompt(event) {
54
54
}
55
55
56
56
function readInputImage ( ) {
57
-
57
+
58
58
if ( ! this . files || ! this . files [ 0 ] ) return ;
59
-
59
+
60
60
const FR = new FileReader ( ) ;
61
-
61
+
62
62
FR . addEventListener ( "load" , function ( evt ) {
63
63
image = evt . target . result ;
64
- } ) ;
65
-
64
+ } ) ;
65
+
66
66
FR . readAsDataURL ( this . files [ 0 ] ) ;
67
67
}
68
68
@@ -155,50 +155,94 @@ function readInputImage() {
155
155
stream : true ,
156
156
} ) ,
157
157
} ) ;
158
-
158
+
159
159
if ( ! response . ok ) {
160
160
Alpine . store ( "chat" ) . add (
161
161
"assistant" ,
162
162
`<span class='error'>Error: POST /v1/chat/completions ${ response . status } </span>` ,
163
163
) ;
164
164
return ;
165
165
}
166
-
166
+
167
167
const reader = response . body
168
168
?. pipeThrough ( new TextDecoderStream ( ) )
169
169
. getReader ( ) ;
170
-
170
+
171
171
if ( ! reader ) {
172
172
Alpine . store ( "chat" ) . add (
173
173
"assistant" ,
174
174
`<span class='error'>Error: Failed to decode API response</span>` ,
175
175
) ;
176
176
return ;
177
177
}
178
-
179
- while ( true ) {
180
- const { value, done } = await reader . read ( ) ;
181
- if ( done ) break ;
182
- let dataDone = false ;
183
- const arr = value . split ( "\n" ) ;
184
- arr . forEach ( ( data ) => {
185
- if ( data . length === 0 ) return ;
186
- if ( data . startsWith ( ":" ) ) return ;
187
- if ( data === "data: [DONE]" ) {
188
- dataDone = true ;
189
- return ;
190
- }
191
- const token = JSON . parse ( data . substring ( 6 ) ) . choices [ 0 ] . delta . content ;
192
- if ( ! token ) {
193
- return ;
178
+
179
+ // Function to add content to the chat and handle DOM updates efficiently
180
+ const addToChat = ( token ) => {
181
+ const chatStore = Alpine . store ( "chat" ) ;
182
+ chatStore . add ( "assistant" , token ) ;
183
+ // Efficiently scroll into view without triggering multiple reflows
184
+ const messages = document . getElementById ( 'messages' ) ;
185
+ messages . scrollTop = messages . scrollHeight ;
186
+ } ;
187
+
188
+ let buffer = "" ;
189
+ let contentBuffer = [ ] ;
190
+
191
+ try {
192
+ while ( true ) {
193
+ const { value, done } = await reader . read ( ) ;
194
+ if ( done ) break ;
195
+
196
+ buffer += value ;
197
+
198
+ let lines = buffer . split ( "\n" ) ;
199
+ buffer = lines . pop ( ) ; // Retain any incomplete line in the buffer
200
+
201
+ lines . forEach ( ( line ) => {
202
+ if ( line . length === 0 || line . startsWith ( ":" ) ) return ;
203
+ if ( line === "data: [DONE]" ) {
204
+ return ;
205
+ }
206
+
207
+ if ( line . startsWith ( "data: " ) ) {
208
+ try {
209
+ const jsonData = JSON . parse ( line . substring ( 6 ) ) ;
210
+ const token = jsonData . choices [ 0 ] . delta . content ;
211
+
212
+ if ( token ) {
213
+ contentBuffer . push ( token ) ;
214
+ }
215
+ } catch ( error ) {
216
+ console . error ( "Failed to parse line:" , line , error ) ;
217
+ }
218
+ }
219
+ } ) ;
220
+
221
+ // Efficiently update the chat in batch
222
+ if ( contentBuffer . length > 0 ) {
223
+ addToChat ( contentBuffer . join ( "" ) ) ;
224
+ contentBuffer = [ ] ;
194
225
}
195
- hljs . highlightAll ( ) ;
196
- Alpine . store ( "chat" ) . add ( "assistant" , token ) ;
197
- document . getElementById ( 'messages' ) . scrollIntoView ( false )
198
- } ) ;
226
+ }
227
+
228
+ // Final content flush if any data remains
229
+ if ( contentBuffer . length > 0 ) {
230
+ addToChat ( contentBuffer . join ( "" ) ) ;
231
+ }
232
+
233
+ // Highlight all code blocks once at the end
199
234
hljs . highlightAll ( ) ;
200
- if ( dataDone ) break ;
235
+ } catch ( error ) {
236
+ console . error ( "An error occurred while reading the stream:" , error ) ;
237
+ Alpine . store ( "chat" ) . add (
238
+ "assistant" ,
239
+ `<span class='error'>Error: Failed to process stream</span>` ,
240
+ ) ;
241
+ } finally {
242
+ // Perform any cleanup if necessary
243
+ reader . releaseLock ( ) ;
201
244
}
245
+
202
246
// Remove class "loader" from the element with "loader" id
203
247
//document.getElementById("loader").classList.remove("loader");
204
248
document . getElementById ( "loader" ) . style . display = "none" ;
@@ -209,7 +253,7 @@ function readInputImage() {
209
253
// set focus to the input
210
254
document . getElementById ( "input" ) . focus ( ) ;
211
255
}
212
-
256
+
213
257
document . getElementById ( "key" ) . addEventListener ( "submit" , submitKey ) ;
214
258
document . getElementById ( "system_prompt" ) . addEventListener ( "submit" , submitSystemPrompt ) ;
215
259
@@ -230,7 +274,7 @@ function readInputImage() {
230
274
} else {
231
275
document . getElementById ( "systemPrompt" ) . value = null ;
232
276
}
233
-
277
+
234
278
marked . setOptions ( {
235
279
highlight : function ( code ) {
236
280
return hljs . highlightAuto ( code ) . value ;
0 commit comments