Spaces:
Running
Running
Commit
·
7a07363
1
Parent(s):
7ad6991
gsap safety
Browse files- .claude/commands/nourish.md +0 -1
- src/lib/components/chat/ChatPanel.svelte +11 -7
- src/lib/components/chat/ExampleMessages.svelte +9 -1
- src/lib/components/chat/InProgressBlock.svelte +5 -13
- src/lib/components/chat/ReasoningBlock.svelte +15 -3
- src/lib/components/chat/context.md +2 -1
- src/main.ts +5 -0
.claude/commands/nourish.md
CHANGED
|
@@ -7,7 +7,6 @@ Complete conversation by updating context and applying cleanup.
|
|
| 7 |
@CLAUDE.md
|
| 8 |
@layers/structure.md
|
| 9 |
@layers/context-template.md
|
| 10 |
-
@llms.txt
|
| 11 |
|
| 12 |
User arguments: "$ARGUMENTS"
|
| 13 |
|
|
|
|
| 7 |
@CLAUDE.md
|
| 8 |
@layers/structure.md
|
| 9 |
@layers/context-template.md
|
|
|
|
| 10 |
|
| 11 |
User arguments: "$ARGUMENTS"
|
| 12 |
|
src/lib/components/chat/ChatPanel.svelte
CHANGED
|
@@ -40,6 +40,17 @@
|
|
| 40 |
|
| 41 |
onDestroy(() => {
|
| 42 |
agentService.disconnect();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
});
|
| 44 |
|
| 45 |
$: if ($authStore.isAuthenticated && !$authStore.loading && !hasConnected) {
|
|
@@ -286,7 +297,6 @@
|
|
| 286 |
<div class="messages" bind:this={messagesContainer} on:scroll={handleScroll}>
|
| 287 |
{#if !$authStore.isAuthenticated && !$authStore.loading}
|
| 288 |
<div class="auth-prompt">
|
| 289 |
-
<p>Sign in to chat.</p>
|
| 290 |
<button bind:this={authPromptBtn} on:click={() => authStore.login()} class="auth-prompt-btn">
|
| 291 |
Sign in with 🤗 Hugging Face
|
| 292 |
</button>
|
|
@@ -639,12 +649,6 @@
|
|
| 639 |
text-align: center;
|
| 640 |
}
|
| 641 |
|
| 642 |
-
.auth-prompt p {
|
| 643 |
-
color: rgba(255, 255, 255, 0.6);
|
| 644 |
-
margin-bottom: 1.5rem;
|
| 645 |
-
font-size: 0.875rem;
|
| 646 |
-
}
|
| 647 |
-
|
| 648 |
.auth-prompt-btn {
|
| 649 |
padding: 0.6rem 1.8rem;
|
| 650 |
background: rgba(255, 210, 30, 0.08);
|
|
|
|
| 40 |
|
| 41 |
onDestroy(() => {
|
| 42 |
agentService.disconnect();
|
| 43 |
+
|
| 44 |
+
if (scrollAnimation) {
|
| 45 |
+
scrollAnimation.kill();
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
if (sendButton) gsap.killTweensOf(sendButton);
|
| 49 |
+
if (stopButton) gsap.killTweensOf(stopButton);
|
| 50 |
+
if (inputTextarea) gsap.killTweensOf(inputTextarea);
|
| 51 |
+
if (authPromptBtn) gsap.killTweensOf(authPromptBtn);
|
| 52 |
+
if (clearButton) gsap.killTweensOf(clearButton);
|
| 53 |
+
if (messagesContainer) gsap.killTweensOf(messagesContainer);
|
| 54 |
});
|
| 55 |
|
| 56 |
$: if ($authStore.isAuthenticated && !$authStore.loading && !hasConnected) {
|
|
|
|
| 297 |
<div class="messages" bind:this={messagesContainer} on:scroll={handleScroll}>
|
| 298 |
{#if !$authStore.isAuthenticated && !$authStore.loading}
|
| 299 |
<div class="auth-prompt">
|
|
|
|
| 300 |
<button bind:this={authPromptBtn} on:click={() => authStore.login()} class="auth-prompt-btn">
|
| 301 |
Sign in with 🤗 Hugging Face
|
| 302 |
</button>
|
|
|
|
| 649 |
text-align: center;
|
| 650 |
}
|
| 651 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
.auth-prompt-btn {
|
| 653 |
padding: 0.6rem 1.8rem;
|
| 654 |
background: rgba(255, 210, 30, 0.08);
|
src/lib/components/chat/ExampleMessages.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import { onMount } from "svelte";
|
| 3 |
import { fade } from "svelte/transition";
|
| 4 |
import gsap from "gsap";
|
| 5 |
|
|
@@ -33,6 +33,14 @@
|
|
| 33 |
});
|
| 34 |
});
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
function handleClick(text: string) {
|
| 37 |
onSendMessage(text);
|
| 38 |
}
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import { onMount, onDestroy } from "svelte";
|
| 3 |
import { fade } from "svelte/transition";
|
| 4 |
import gsap from "gsap";
|
| 5 |
|
|
|
|
| 33 |
});
|
| 34 |
});
|
| 35 |
|
| 36 |
+
onDestroy(() => {
|
| 37 |
+
exampleCards.forEach((card) => {
|
| 38 |
+
if (card) {
|
| 39 |
+
gsap.killTweensOf(card);
|
| 40 |
+
}
|
| 41 |
+
});
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
function handleClick(text: string) {
|
| 45 |
onSendMessage(text);
|
| 46 |
}
|
src/lib/components/chat/InProgressBlock.svelte
CHANGED
|
@@ -189,19 +189,11 @@
|
|
| 189 |
if (collapseTimeout) clearTimeout(collapseTimeout);
|
| 190 |
if (timeline) timeline.kill();
|
| 191 |
|
| 192 |
-
if (
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
});
|
| 198 |
-
gsap.to(blockElement, {
|
| 199 |
-
opacity: 0,
|
| 200 |
-
scale: 0.95,
|
| 201 |
-
duration: 0.3,
|
| 202 |
-
ease: "power2.in",
|
| 203 |
-
});
|
| 204 |
-
}
|
| 205 |
});
|
| 206 |
</script>
|
| 207 |
|
|
|
|
| 189 |
if (collapseTimeout) clearTimeout(collapseTimeout);
|
| 190 |
if (timeline) timeline.kill();
|
| 191 |
|
| 192 |
+
if (expandIcon) gsap.killTweensOf(expandIcon);
|
| 193 |
+
if (contentElement) gsap.killTweensOf(contentElement);
|
| 194 |
+
if (statusElement) gsap.killTweensOf(statusElement);
|
| 195 |
+
if (progressBar) gsap.killTweensOf(progressBar);
|
| 196 |
+
if (blockElement) gsap.killTweensOf(blockElement);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
});
|
| 198 |
</script>
|
| 199 |
|
src/lib/components/chat/ReasoningBlock.svelte
CHANGED
|
@@ -11,6 +11,7 @@
|
|
| 11 |
let contentElement: HTMLDivElement;
|
| 12 |
let blockElement: HTMLDivElement;
|
| 13 |
let collapseTimeout: number | null = null;
|
|
|
|
| 14 |
|
| 15 |
$: if (responseComplete && autoCollapse && isExpanded) {
|
| 16 |
if (collapseTimeout) clearTimeout(collapseTimeout);
|
|
@@ -35,7 +36,9 @@
|
|
| 35 |
function animateExpand() {
|
| 36 |
if (!iconElement || !contentElement) return;
|
| 37 |
|
| 38 |
-
|
|
|
|
|
|
|
| 39 |
.to(iconElement, {
|
| 40 |
rotation: 180,
|
| 41 |
duration: 0.15,
|
|
@@ -58,7 +61,9 @@
|
|
| 58 |
function animateCollapse() {
|
| 59 |
if (!iconElement || !contentElement) return;
|
| 60 |
|
| 61 |
-
|
|
|
|
|
|
|
| 62 |
.to(iconElement, {
|
| 63 |
rotation: 0,
|
| 64 |
duration: 0.1,
|
|
@@ -71,7 +76,9 @@
|
|
| 71 |
duration: 0.1,
|
| 72 |
ease: "power2.in",
|
| 73 |
onComplete: () => {
|
| 74 |
-
|
|
|
|
|
|
|
| 75 |
}
|
| 76 |
}, 0)
|
| 77 |
.to(blockElement, {
|
|
@@ -119,6 +126,11 @@
|
|
| 119 |
|
| 120 |
onDestroy(() => {
|
| 121 |
if (collapseTimeout) clearTimeout(collapseTimeout);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
});
|
| 123 |
</script>
|
| 124 |
|
|
|
|
| 11 |
let contentElement: HTMLDivElement;
|
| 12 |
let blockElement: HTMLDivElement;
|
| 13 |
let collapseTimeout: number | null = null;
|
| 14 |
+
let activeTimeline: gsap.core.Timeline | null = null;
|
| 15 |
|
| 16 |
$: if (responseComplete && autoCollapse && isExpanded) {
|
| 17 |
if (collapseTimeout) clearTimeout(collapseTimeout);
|
|
|
|
| 36 |
function animateExpand() {
|
| 37 |
if (!iconElement || !contentElement) return;
|
| 38 |
|
| 39 |
+
if (activeTimeline) activeTimeline.kill();
|
| 40 |
+
|
| 41 |
+
activeTimeline = gsap.timeline()
|
| 42 |
.to(iconElement, {
|
| 43 |
rotation: 180,
|
| 44 |
duration: 0.15,
|
|
|
|
| 61 |
function animateCollapse() {
|
| 62 |
if (!iconElement || !contentElement) return;
|
| 63 |
|
| 64 |
+
if (activeTimeline) activeTimeline.kill();
|
| 65 |
+
|
| 66 |
+
activeTimeline = gsap.timeline()
|
| 67 |
.to(iconElement, {
|
| 68 |
rotation: 0,
|
| 69 |
duration: 0.1,
|
|
|
|
| 76 |
duration: 0.1,
|
| 77 |
ease: "power2.in",
|
| 78 |
onComplete: () => {
|
| 79 |
+
if (contentElement) {
|
| 80 |
+
gsap.set(contentElement, { display: 'none' });
|
| 81 |
+
}
|
| 82 |
}
|
| 83 |
}, 0)
|
| 84 |
.to(blockElement, {
|
|
|
|
| 126 |
|
| 127 |
onDestroy(() => {
|
| 128 |
if (collapseTimeout) clearTimeout(collapseTimeout);
|
| 129 |
+
if (activeTimeline) activeTimeline.kill();
|
| 130 |
+
|
| 131 |
+
if (iconElement) gsap.killTweensOf(iconElement);
|
| 132 |
+
if (contentElement) gsap.killTweensOf(contentElement);
|
| 133 |
+
if (blockElement) gsap.killTweensOf(blockElement);
|
| 134 |
});
|
| 135 |
</script>
|
| 136 |
|
src/lib/components/chat/context.md
CHANGED
|
@@ -4,7 +4,7 @@ AI chat interface with real-time streaming and conversation control.
|
|
| 4 |
|
| 5 |
## Components
|
| 6 |
|
| 7 |
-
- `ChatPanel.svelte` - Main chat UI with header bar, clear button, smooth scroll, stop button
|
| 8 |
- `ExampleMessages.svelte` - Clickable prompt suggestions when chat is empty
|
| 9 |
- `MessageSegment.svelte` - Renders text, tool invocations, and results
|
| 10 |
- `StreamingText.svelte` - Optimized character streaming with state persistence
|
|
@@ -21,3 +21,4 @@ AI chat interface with real-time streaming and conversation control.
|
|
| 21 |
- Tool invocations displayed through dedicated segments
|
| 22 |
- Clean separation between text and tool content
|
| 23 |
- Abortable conversations via stop button during processing
|
|
|
|
|
|
| 4 |
|
| 5 |
## Components
|
| 6 |
|
| 7 |
+
- `ChatPanel.svelte` - Main chat UI with header bar, clear button, smooth scroll, stop button
|
| 8 |
- `ExampleMessages.svelte` - Clickable prompt suggestions when chat is empty
|
| 9 |
- `MessageSegment.svelte` - Renders text, tool invocations, and results
|
| 10 |
- `StreamingText.svelte` - Optimized character streaming with state persistence
|
|
|
|
| 21 |
- Tool invocations displayed through dedicated segments
|
| 22 |
- Clean separation between text and tool content
|
| 23 |
- Abortable conversations via stop button during processing
|
| 24 |
+
- All GSAP animations properly cleaned up on component destruction
|
src/main.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
| 1 |
import "./app.css";
|
| 2 |
import App from "./App.svelte";
|
| 3 |
import { consoleSyncService } from "./lib/services/console-sync";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
const app = new App({
|
| 6 |
target: document.getElementById("app")!,
|
|
|
|
| 1 |
import "./app.css";
|
| 2 |
import App from "./App.svelte";
|
| 3 |
import { consoleSyncService } from "./lib/services/console-sync";
|
| 4 |
+
import gsap from "gsap";
|
| 5 |
+
|
| 6 |
+
gsap.config({
|
| 7 |
+
nullTargetWarn: false,
|
| 8 |
+
});
|
| 9 |
|
| 10 |
const app = new App({
|
| 11 |
target: document.getElementById("app")!,
|