SolarumAsteridion commited on
Commit
0fd39b4
·
verified ·
1 Parent(s): 9d16f84

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +111 -49
index.html CHANGED
@@ -266,6 +266,16 @@ th{font-weight:600}
266
  font-style:italic;
267
  }
268
 
 
 
 
 
 
 
 
 
 
 
269
  /* responsive & print */
270
  @media(max-width:768px){
271
  .container{margin:20px 16px;padding:28px}
@@ -366,10 +376,15 @@ async function imageToBase64(file) {
366
  return new Promise((resolve, reject) => {
367
  const reader = new FileReader();
368
  reader.onload = () => {
369
- const base64 = reader.result.split(',')[1];
370
- resolve(base64);
 
 
 
 
 
371
  };
372
- reader.onerror = reject;
373
  reader.readAsDataURL(file);
374
  });
375
  }
@@ -403,9 +418,9 @@ async function ocrImage(base64Image) {
403
  role: 'user',
404
  content: [
405
  { type: 'text', text: 'Image:' },
406
- {
407
- type: 'image_url',
408
- image_url: { url: `data:image/png;base64,${base64Image}` }
409
  }
410
  ]
411
  }
@@ -414,7 +429,8 @@ async function ocrImage(base64Image) {
414
  });
415
 
416
  if (!response.ok) {
417
- throw new Error(`OCR API error: ${response.status}`);
 
418
  }
419
 
420
  const data = await response.json();
@@ -426,7 +442,31 @@ async function ocrImage(base64Image) {
426
  }
427
  }
428
 
429
- /* ======= Solve with Cerebras API (Streaming) ======= */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  async function solveQuestion(question) {
431
  const cerebrasKey = localStorage.getItem('cerebras-api-key');
432
  if (!cerebrasKey) {
@@ -435,74 +475,97 @@ async function solveQuestion(question) {
435
  }
436
 
437
  showProcessing('Solving the question...');
 
438
 
439
  try {
440
  const response = await fetch('https://api.cerebras.ai/v1/chat/completions', {
441
  method: 'POST',
442
  headers: {
443
  'Content-Type': 'application/json',
 
444
  'Authorization': `Bearer ${cerebrasKey}`
445
  },
446
  body: JSON.stringify({
447
  model: 'gpt-oss-120b',
448
  stream: true,
449
  max_tokens: 65536,
450
- temperature: 0.1,
451
- reasoning_effort: 'medium',
 
452
  messages: [
453
- {
454
- role: 'system',
455
- content: 'Solve this Question. Provide a clear, step-by-step solution with LaTeX.'
456
- },
457
- {
458
- role: 'user',
459
- content: question
460
- }
461
  ]
462
  })
463
  });
464
 
465
  if (!response.ok) {
466
- throw new Error(`Cerebras API error: ${response.status}`);
 
467
  }
468
 
469
- // Process streaming response
470
- const reader = response.body.getReader();
471
  const decoder = new TextDecoder();
472
  let fullAnswer = '';
 
 
 
 
 
 
 
 
 
473
 
474
  while (true) {
475
  const { done, value } = await reader.read();
476
  if (done) break;
477
 
478
- const chunk = decoder.decode(value);
479
- const lines = chunk.split('\n').filter(line => line.trim() !== '');
480
-
481
- for (const line of lines) {
482
- if (line.startsWith('data: ')) {
483
- const data = line.slice(6);
484
- if (data === '[DONE]') continue;
485
-
486
- try {
487
- const parsed = JSON.parse(data);
488
- const content = parsed.choices[0]?.delta?.content;
489
- if (content) {
490
- fullAnswer += content;
491
- // Update display in real-time
492
- const formatted = `**Question**: ${question}\n\n**Answer**: ${fullAnswer}`;
493
- processContent(formatted);
 
 
 
 
 
 
 
 
 
 
494
  }
495
- } catch (e) {
496
- console.error('Error parsing stream chunk:', e);
497
  }
 
 
 
498
  }
499
  }
500
  }
501
 
 
 
 
 
 
502
  return fullAnswer;
503
  } catch (error) {
504
  console.error('Solving Error:', error);
505
  alert('Error during solving: ' + error.message);
 
506
  return null;
507
  }
508
  }
@@ -522,14 +585,9 @@ async function processImage(file) {
522
 
523
  // Solve the question
524
  const answer = await solveQuestion(ocrText);
525
- if (!answer) {
526
- hideProcessing();
527
- return;
528
- }
529
 
530
- // Display the result
531
- const formatted = `**Question**: ${ocrText}\n\n**Answer**: ${answer}`;
532
- processContent(formatted);
533
  } catch (error) {
534
  console.error('Image processing error:', error);
535
  alert('Error processing image: ' + error.message);
@@ -544,7 +602,7 @@ document.addEventListener('paste', async (e) => {
544
  const isInputField = activeElement && (
545
  activeElement.tagName === 'INPUT' ||
546
  activeElement.tagName === 'TEXTAREA' ||
547
- activeElement.contentEditable === 'true'
548
  );
549
 
550
  // If pasting into an input field, let the browser handle it normally
@@ -555,14 +613,18 @@ document.addEventListener('paste', async (e) => {
555
  // Otherwise, handle custom paste logic
556
  e.preventDefault();
557
 
558
- // Check for image files
559
  const items = Array.from(e.clipboardData.items);
560
  const imageItem = items.find(item => item.type.startsWith('image/'));
561
 
562
  if (imageItem) {
563
  // Handle image paste
564
  const file = imageItem.getAsFile();
565
- await processImage(file);
 
 
 
 
566
  } else {
567
  // Handle text paste (existing functionality)
568
  const txt = e.clipboardData.getData('text/plain');
 
266
  font-style:italic;
267
  }
268
 
269
+ /* Styles for the lightweight streaming area */
270
+ .mono-stream{
271
+ font-family:'PT Mono',monospace;
272
+ white-space:pre-wrap;
273
+ background:var(--code-bg);
274
+ border:1px solid var(--code-border);
275
+ padding:12px;border-radius:6px;
276
+ margin-top: 8px; /* Add some spacing */
277
+ }
278
+
279
  /* responsive & print */
280
  @media(max-width:768px){
281
  .container{margin:20px 16px;padding:28px}
 
376
  return new Promise((resolve, reject) => {
377
  const reader = new FileReader();
378
  reader.onload = () => {
379
+ // Ensure it's a valid data URL and extract base64 part
380
+ if (reader.result && reader.result.includes(',')) {
381
+ const base64 = reader.result.split(',')[1];
382
+ resolve(base64);
383
+ } else {
384
+ reject(new Error("Failed to read file as Data URL."));
385
+ }
386
  };
387
+ reader.onerror = () => reject(reader.error);
388
  reader.readAsDataURL(file);
389
  });
390
  }
 
418
  role: 'user',
419
  content: [
420
  { type: 'text', text: 'Image:' },
421
+ {
422
+ type: 'image_url',
423
+ image_url: { url: `data:image/png;base64,${base64Image}` } // Assuming PNG, adjust if needed
424
  }
425
  ]
426
  }
 
429
  });
430
 
431
  if (!response.ok) {
432
+ const errorText = await response.text();
433
+ throw new Error(`OCR API error: ${response.status} - ${errorText}`);
434
  }
435
 
436
  const data = await response.json();
 
442
  }
443
  }
444
 
445
+ /* ======= UI Helpers for Streaming ======= */
446
+ function beginStreamingUI(question){
447
+ // Show a lightweight, non-MathJax view while the model streams
448
+ content.innerHTML = `
449
+ <div>
450
+ <p><strong>Question</strong>:</p>
451
+ <div class="mono-stream" id="qStream"></div>
452
+ <hr style="opacity:.35; margin: 20px 0;">
453
+ <p><strong>Answer</strong>:</p>
454
+ <div class="mono-stream" id="aStream">(generating...)</div>
455
+ </div>`;
456
+ const qEl = document.getElementById('qStream');
457
+ const aEl = document.getElementById('aStream');
458
+ qEl.textContent = question; // plain text now; pretty render later
459
+ aEl.textContent = ''; // clear "(generating...)"
460
+ return { qEl, aEl };
461
+ }
462
+
463
+ function finalizeStreaming(question, fullAnswer){
464
+ // One single heavy render (Markdown + MathJax) at the end
465
+ const formatted = `**Question**: ${question}\n\n**Answer**: ${fullAnswer}`;
466
+ processContent(formatted); // processContent calls hideProcessing after MathJax
467
+ }
468
+
469
+ /* ======= Solve with Cerebras API (Streaming Optimization) ======= */
470
  async function solveQuestion(question) {
471
  const cerebrasKey = localStorage.getItem('cerebras-api-key');
472
  if (!cerebrasKey) {
 
475
  }
476
 
477
  showProcessing('Solving the question...');
478
+ const ui = beginStreamingUI(question); // Prepare the lightweight streaming UI
479
 
480
  try {
481
  const response = await fetch('https://api.cerebras.ai/v1/chat/completions', {
482
  method: 'POST',
483
  headers: {
484
  'Content-Type': 'application/json',
485
+ 'Accept': 'text/event-stream', // Specify that we expect a stream
486
  'Authorization': `Bearer ${cerebrasKey}`
487
  },
488
  body: JSON.stringify({
489
  model: 'gpt-oss-120b',
490
  stream: true,
491
  max_tokens: 65536,
492
+ temperature: 0.1, // Set temperature to 0.1
493
+ reasoning_effort: 'medium', // Set reasoning_effort to 'medium'
494
+ // top_p: 1, // Removed as per user's request
495
  messages: [
496
+ { role: 'system', content: 'Solve this Question. Provide a clear, step-by-step solution.' },
497
+ { role: 'user', content: question }
 
 
 
 
 
 
498
  ]
499
  })
500
  });
501
 
502
  if (!response.ok) {
503
+ const errorText = await response.text();
504
+ throw new Error(`Cerebras API error: ${response.status} - ${errorText}`);
505
  }
506
 
507
+ const reader = response.body.getReader();
 
508
  const decoder = new TextDecoder();
509
  let fullAnswer = '';
510
+ let buffer = ''; // buffer for partial SSE frames
511
+ let lastFlushTime = 0;
512
+ const flushThrottle = 120; // milliseconds to wait between DOM updates
513
+
514
+ const flushUI = () => {
515
+ // Update the lightweight streaming area without MathJax
516
+ ui.aEl.textContent = fullAnswer;
517
+ lastFlushTime = performance.now();
518
+ };
519
 
520
  while (true) {
521
  const { done, value } = await reader.read();
522
  if (done) break;
523
 
524
+ buffer += decoder.decode(value, { stream: true });
525
+ // SSE events are typically separated by '\n\n'
526
+ const events = buffer.split('\n\n');
527
+ buffer = events.pop() || ''; // Keep any incomplete event for the next chunk
528
+
529
+ for (const evt of events) {
530
+ // Find the 'data:' line, which contains the JSON payload
531
+ const dataLine = evt.split('\n').find(line => line.trim().startsWith('data: '));
532
+ if (!dataLine) continue;
533
+
534
+ const data = dataLine.slice(6).trim(); // Remove 'data: ' prefix
535
+ if (data === '[DONE]') continue; // End of stream marker
536
+
537
+ try {
538
+ const parsed = JSON.parse(data);
539
+ // Extract content, being flexible with potential API response structures
540
+ const deltaContent = parsed.choices?.[0]?.delta?.content
541
+ ?? parsed.choices?.[0]?.message?.content
542
+ ?? parsed.choices?.[0]?.text // Some APIs might use 'text'
543
+ ?? '';
544
+
545
+ if (deltaContent) {
546
+ fullAnswer += deltaContent;
547
+ // Throttle DOM updates to prevent excessive rendering and jank
548
+ if (performance.now() - lastFlushTime > flushThrottle) {
549
+ flushUI();
550
  }
 
 
551
  }
552
+ } catch (e) {
553
+ // Ignore errors parsing JSON chunks if it's just partial data
554
+ console.error('Error parsing stream chunk data:', e, 'Chunk:', data);
555
  }
556
  }
557
  }
558
 
559
+ // Final flush to ensure all streamed content is displayed in the lightweight view
560
+ flushUI();
561
+
562
+ // Once streaming is complete, perform the final, heavier render with Markdown and MathJax
563
+ finalizeStreaming(question, fullAnswer);
564
  return fullAnswer;
565
  } catch (error) {
566
  console.error('Solving Error:', error);
567
  alert('Error during solving: ' + error.message);
568
+ hideProcessing(); // Ensure the processing indicator is hidden on error
569
  return null;
570
  }
571
  }
 
585
 
586
  // Solve the question
587
  const answer = await solveQuestion(ocrText);
588
+ // The solveQuestion function now handles hiding the processing indicator
589
+ // unless an error occurred, in which case it was hidden earlier.
 
 
590
 
 
 
 
591
  } catch (error) {
592
  console.error('Image processing error:', error);
593
  alert('Error processing image: ' + error.message);
 
602
  const isInputField = activeElement && (
603
  activeElement.tagName === 'INPUT' ||
604
  activeElement.tagName === 'TEXTAREA' ||
605
+ activeElement.isContentEditable === true // Use isContentEditable for modern check
606
  );
607
 
608
  // If pasting into an input field, let the browser handle it normally
 
613
  // Otherwise, handle custom paste logic
614
  e.preventDefault();
615
 
616
+ // Check for image files first
617
  const items = Array.from(e.clipboardData.items);
618
  const imageItem = items.find(item => item.type.startsWith('image/'));
619
 
620
  if (imageItem) {
621
  // Handle image paste
622
  const file = imageItem.getAsFile();
623
+ if (file) {
624
+ await processImage(file);
625
+ } else {
626
+ alert("Could not get image file from clipboard.");
627
+ }
628
  } else {
629
  // Handle text paste (existing functionality)
630
  const txt = e.clipboardData.getData('text/plain');