Spaces:
Running
Running
Update index.html
Browse files- index.html +107 -21
index.html
CHANGED
|
@@ -340,36 +340,130 @@ class RobotFace {
|
|
| 340 |
this.blinkProgress = 1; this.isBlinking = false; this.lastBlinkTime = Date.now();
|
| 341 |
this.isSleeping = true; this.lastInteractionTime = Date.now(); this.sleepTimeout = 10000;
|
| 342 |
this.isWatchingInput = false; this.isThinking = false;
|
| 343 |
-
this.readingOscillation = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
this.initInteractions(); requestAnimationFrame(this.render.bind(this));
|
| 345 |
}
|
|
|
|
| 346 |
render() {
|
| 347 |
this.lookX += (this.targetLookX - this.lookX) * 0.1;
|
| 348 |
this.lookY += (this.targetLookY - this.lookY) * 0.1;
|
| 349 |
this.ctx.clearRect(0, 0, this.W, this.H); this.drawFaceplate();
|
| 350 |
const now = Date.now();
|
| 351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
if (this.isWatchingInput) {
|
| 353 |
this.targetLookY = 25; this.readingOscillation += 0.1;
|
| 354 |
this.targetLookX = Math.sin(this.readingOscillation) * 10;
|
| 355 |
}
|
| 356 |
-
|
| 357 |
-
if (
|
|
|
|
|
|
|
|
|
|
| 358 |
this.drawExpression();
|
| 359 |
}
|
| 360 |
requestAnimationFrame(this.render.bind(this));
|
| 361 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
drawFaceplate() {
|
| 363 |
const c = this.ctx, r = 60;
|
| 364 |
c.fillStyle = '#050505'; c.strokeStyle = '#333333'; c.lineWidth = 4;
|
| 365 |
c.beginPath(); c.roundRect(0, 0, this.W, this.H, r); c.fill(); c.stroke();
|
| 366 |
}
|
| 367 |
-
drawExpression() {
|
| 368 |
-
const eW = 100, eH = 100, eR = 30, eY = this.H / 2 + this.lookY;
|
| 369 |
-
const lCX = this.W * 0.3 + this.lookX, rCX = this.W * 0.7 + this.lookX;
|
| 370 |
-
const cH = eH * this.blinkProgress;
|
| 371 |
-
this.drawEye(lCX, eY, eW, cH, eR); this.drawEye(rCX, eY, eW, cH, eR);
|
| 372 |
-
}
|
| 373 |
drawEye(cx, cy, w, h, r) {
|
| 374 |
const c = this.ctx;
|
| 375 |
const eyeColor = getComputedStyle(document.documentElement).getPropertyValue('--eye-color').trim();
|
|
@@ -420,11 +514,6 @@ class RobotFace {
|
|
| 420 |
};
|
| 421 |
requestAnimationFrame(a);
|
| 422 |
}
|
| 423 |
-
startThinking() {
|
| 424 |
-
this.isThinking = true; this.targetLookX = 0; this.targetLookY = 0;
|
| 425 |
-
this.thinkingInterval = setInterval(() => this.blink(), 800);
|
| 426 |
-
}
|
| 427 |
-
stopThinking() { this.isThinking = false; clearInterval(this.thinkingInterval); }
|
| 428 |
}
|
| 429 |
|
| 430 |
class ChatInterface {
|
|
@@ -435,7 +524,7 @@ class ChatInterface {
|
|
| 435 |
this.inputArea = document.getElementById('input-area');
|
| 436 |
this.sendButtonContainer = document.getElementById('send-button-container');
|
| 437 |
this.sendButton = document.getElementById('send-button');
|
| 438 |
-
this.isDragging = false; this.startY = 0; this.currentY = 0; this.dragThreshold = 30;
|
| 439 |
this.API_URL = 'https://text.pollinations.ai/openai';
|
| 440 |
|
| 441 |
const SYSTEM_PROMPT = `
|
|
@@ -572,20 +661,17 @@ class ChatInterface {
|
|
| 572 |
return '';
|
| 573 |
});
|
| 574 |
|
| 575 |
-
// First, escape all HTML, then process markdown.
|
| 576 |
let contentHtml = escapeHTML(processedText)
|
| 577 |
.replace(markdownLinkRegex, '<a href="$2" target="_blank">$1</a>');
|
| 578 |
|
| 579 |
-
// Since code blocks are special, we handle them separately to avoid double-escaping.
|
| 580 |
contentHtml = contentHtml.replace(codeBlockRegex, (match, lang, code) => {
|
| 581 |
const uniqueId = `code-${Date.now()}-${Math.random()}`;
|
| 582 |
-
|
| 583 |
-
return `<div class="code-block-wrapper">
|
| 584 |
<div class="code-block-header">
|
| 585 |
<button class="copy-code-btn" data-target="${uniqueId}">کپی</button>
|
| 586 |
</div>
|
| 587 |
<pre><code id="${uniqueId}">${code.trim()}</code></pre>
|
| 588 |
-
</div>`;
|
| 589 |
});
|
| 590 |
|
| 591 |
bubble.innerHTML = `
|
|
|
|
| 340 |
this.blinkProgress = 1; this.isBlinking = false; this.lastBlinkTime = Date.now();
|
| 341 |
this.isSleeping = true; this.lastInteractionTime = Date.now(); this.sleepTimeout = 10000;
|
| 342 |
this.isWatchingInput = false; this.isThinking = false;
|
| 343 |
+
this.readingOscillation = 0;
|
| 344 |
+
|
| 345 |
+
// --- State variables for new animations ---
|
| 346 |
+
this.thinkingStartTime = 0;
|
| 347 |
+
this.leftEyeScale = 1;
|
| 348 |
+
this.rightEyeScale = 1;
|
| 349 |
+
|
| 350 |
+
this.isFocusing = false;
|
| 351 |
+
this.focusProgress = 0;
|
| 352 |
+
this.focusEye = 'left';
|
| 353 |
+
this.lastFocusTime = Date.now();
|
| 354 |
+
this.nextFocusDelay = 4000 + Math.random() * 3000;
|
| 355 |
+
|
| 356 |
this.initInteractions(); requestAnimationFrame(this.render.bind(this));
|
| 357 |
}
|
| 358 |
+
|
| 359 |
render() {
|
| 360 |
this.lookX += (this.targetLookX - this.lookX) * 0.1;
|
| 361 |
this.lookY += (this.targetLookY - this.lookY) * 0.1;
|
| 362 |
this.ctx.clearRect(0, 0, this.W, this.H); this.drawFaceplate();
|
| 363 |
const now = Date.now();
|
| 364 |
+
|
| 365 |
+
if (this.isSleeping) {
|
| 366 |
+
this.drawSleepEyes();
|
| 367 |
+
} else {
|
| 368 |
+
// --- Animation Logic ---
|
| 369 |
+
const isIdle = !this.isThinking && !this.isWatchingInput && !this.isBlinking && !this.isFocusing;
|
| 370 |
+
|
| 371 |
+
if (this.isThinking) {
|
| 372 |
+
const elapsed = now - this.thinkingStartTime;
|
| 373 |
+
const oscillation = Math.sin(elapsed / 300); // Speed of thinking animation
|
| 374 |
+
const scaleAmount = 0.3; // How much the eye scales
|
| 375 |
+
this.leftEyeScale = 1 - (scaleAmount * (oscillation + 1) / 2);
|
| 376 |
+
this.rightEyeScale = 1 - (scaleAmount * (-oscillation + 1) / 2);
|
| 377 |
+
} else {
|
| 378 |
+
// Reset scale if not thinking
|
| 379 |
+
this.leftEyeScale = 1;
|
| 380 |
+
this.rightEyeScale = 1;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
if (this.isWatchingInput) {
|
| 384 |
this.targetLookY = 25; this.readingOscillation += 0.1;
|
| 385 |
this.targetLookX = Math.sin(this.readingOscillation) * 10;
|
| 386 |
}
|
| 387 |
+
|
| 388 |
+
if (isIdle && now - this.lastInteractionTime > this.sleepTimeout) { this.isSleeping = true; }
|
| 389 |
+
if (isIdle && now - this.lastBlinkTime > 3000 + Math.random() * 2000) { this.blink(); }
|
| 390 |
+
if (isIdle && now - this.lastFocusTime > this.nextFocusDelay) { this.startFocus(); }
|
| 391 |
+
|
| 392 |
this.drawExpression();
|
| 393 |
}
|
| 394 |
requestAnimationFrame(this.render.bind(this));
|
| 395 |
}
|
| 396 |
+
|
| 397 |
+
drawExpression() {
|
| 398 |
+
const eW = 100, eH = 100, eR = 30, eY = this.H / 2 + this.lookY;
|
| 399 |
+
const lCX = this.W * 0.3 + this.lookX, rCX = this.W * 0.7 + this.lookX;
|
| 400 |
+
|
| 401 |
+
let finalLeftScale = this.leftEyeScale;
|
| 402 |
+
let finalRightScale = this.rightEyeScale;
|
| 403 |
+
|
| 404 |
+
if (this.isFocusing) {
|
| 405 |
+
const scale = 1 - (1 - 0.65) * this.focusProgress;
|
| 406 |
+
if (this.focusEye === 'left') {
|
| 407 |
+
finalLeftScale = scale;
|
| 408 |
+
} else {
|
| 409 |
+
finalRightScale = scale;
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
const lW = eW * finalLeftScale;
|
| 414 |
+
const lH = eH * this.blinkProgress * finalLeftScale;
|
| 415 |
+
const rW = eW * finalRightScale;
|
| 416 |
+
const rH = eH * this.blinkProgress * finalRightScale;
|
| 417 |
+
|
| 418 |
+
this.drawEye(lCX, eY, lW, lH, eR);
|
| 419 |
+
this.drawEye(rCX, eY, rW, rH, eR);
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
startFocus() {
|
| 423 |
+
if (this.isFocusing) return;
|
| 424 |
+
this.isFocusing = true;
|
| 425 |
+
this.focusEye = Math.random() < 0.5 ? 'left' : 'right';
|
| 426 |
+
this.lastFocusTime = Date.now();
|
| 427 |
+
this.nextFocusDelay = 4000 + Math.random() * 3000;
|
| 428 |
+
|
| 429 |
+
let startTime = null;
|
| 430 |
+
const duration = 300; // a quick focus animation
|
| 431 |
+
const animateFocus = (timestamp) => {
|
| 432 |
+
if (!startTime) startTime = timestamp;
|
| 433 |
+
const elapsed = timestamp - startTime;
|
| 434 |
+
|
| 435 |
+
if (elapsed < duration) { // Focusing in
|
| 436 |
+
this.focusProgress = elapsed / duration;
|
| 437 |
+
} else if (elapsed < duration * 2) { // Focusing out
|
| 438 |
+
this.focusProgress = 1 - ((elapsed - duration) / duration);
|
| 439 |
+
} else {
|
| 440 |
+
this.focusProgress = 0;
|
| 441 |
+
this.isFocusing = false;
|
| 442 |
+
return;
|
| 443 |
+
}
|
| 444 |
+
requestAnimationFrame(animateFocus);
|
| 445 |
+
};
|
| 446 |
+
requestAnimationFrame(animateFocus);
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
startThinking() {
|
| 450 |
+
this.isThinking = true;
|
| 451 |
+
this.thinkingStartTime = Date.now();
|
| 452 |
+
this.targetLookX = 0; this.targetLookY = 0;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
stopThinking() {
|
| 456 |
+
this.isThinking = false;
|
| 457 |
+
this.leftEyeScale = 1;
|
| 458 |
+
this.rightEyeScale = 1;
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
// --- Other methods (drawFaceplate, drawEye, drawSleepEyes, initInteractions, wakeUp, blink) remain unchanged ---
|
| 462 |
drawFaceplate() {
|
| 463 |
const c = this.ctx, r = 60;
|
| 464 |
c.fillStyle = '#050505'; c.strokeStyle = '#333333'; c.lineWidth = 4;
|
| 465 |
c.beginPath(); c.roundRect(0, 0, this.W, this.H, r); c.fill(); c.stroke();
|
| 466 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
drawEye(cx, cy, w, h, r) {
|
| 468 |
const c = this.ctx;
|
| 469 |
const eyeColor = getComputedStyle(document.documentElement).getPropertyValue('--eye-color').trim();
|
|
|
|
| 514 |
};
|
| 515 |
requestAnimationFrame(a);
|
| 516 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
}
|
| 518 |
|
| 519 |
class ChatInterface {
|
|
|
|
| 524 |
this.inputArea = document.getElementById('input-area');
|
| 525 |
this.sendButtonContainer = document.getElementById('send-button-container');
|
| 526 |
this.sendButton = document.getElementById('send-button');
|
| 527 |
+
this.isDragging = false; this.startY = 0; this.currentY = 0; this.dragThreshold = 30;
|
| 528 |
this.API_URL = 'https://text.pollinations.ai/openai';
|
| 529 |
|
| 530 |
const SYSTEM_PROMPT = `
|
|
|
|
| 661 |
return '';
|
| 662 |
});
|
| 663 |
|
|
|
|
| 664 |
let contentHtml = escapeHTML(processedText)
|
| 665 |
.replace(markdownLinkRegex, '<a href="$2" target="_blank">$1</a>');
|
| 666 |
|
|
|
|
| 667 |
contentHtml = contentHtml.replace(codeBlockRegex, (match, lang, code) => {
|
| 668 |
const uniqueId = `code-${Date.now()}-${Math.random()}`;
|
| 669 |
+
return `</div><div class="code-block-wrapper">
|
|
|
|
| 670 |
<div class="code-block-header">
|
| 671 |
<button class="copy-code-btn" data-target="${uniqueId}">کپی</button>
|
| 672 |
</div>
|
| 673 |
<pre><code id="${uniqueId}">${code.trim()}</code></pre>
|
| 674 |
+
</div><div class="message-content">`;
|
| 675 |
});
|
| 676 |
|
| 677 |
bubble.innerHTML = `
|