1
0
Timothy Jaeryang Baek 2 долоо хоног өмнө
parent
commit
a5d8882bba

+ 9 - 9
src/lib/components/chat/Chat.svelte

@@ -465,6 +465,15 @@
 			return;
 		}
 
+		if (event.data.type === 'action:submit') {
+			console.debug(event.data.text);
+
+			if (prompt !== '') {
+				await tick();
+				submitPrompt(prompt);
+			}
+		}
+
 		// Replace with your iframe's origin
 		if (event.data.type === 'input:prompt') {
 			console.debug(event.data.text);
@@ -477,15 +486,6 @@
 			}
 		}
 
-		if (event.data.type === 'action:submit') {
-			console.debug(event.data.text);
-
-			if (prompt !== '') {
-				await tick();
-				submitPrompt(prompt);
-			}
-		}
-
 		if (event.data.type === 'input:prompt:submit') {
 			console.debug(event.data.text);
 

+ 192 - 149
src/lib/components/common/Collapsible.svelte

@@ -89,54 +89,55 @@
 </script>
 
 <div {id} class={className}>
-	{#if title !== null}
-		<!-- svelte-ignore a11y-no-static-element-interactions -->
-		<!-- svelte-ignore a11y-click-events-have-key-events -->
-		<div
-			class="{buttonClassName} cursor-pointer"
-			on:pointerup={() => {
-				if (!disabled) {
-					open = !open;
-				}
-			}}
-		>
+	{#if attributes?.type === 'tool_calls'}
+		{@const args = decode(attributes?.arguments)}
+		{@const result = decode(attributes?.result ?? '')}
+		{@const files = parseJSONString(decode(attributes?.files ?? ''))}
+		{@const embeds = parseJSONString(decode(attributes?.embeds ?? ''))}
+
+		{#if embeds && Array.isArray(embeds) && embeds.length > 0}
+			<div class="py-1 w-full cursor-pointer">
+				<div class=" w-full text-xs text-gray-500">
+					<div class="">
+						{attributes.name}
+					</div>
+				</div>
+
+				{#each embeds as embed, idx}
+					<div class="my-2" id={`${collapsibleId}-tool-calls-${attributes?.id}-embed-${idx}`}>
+						<FullHeightIframe
+							src={embed}
+							allowScripts={true}
+							allowForms={true}
+							allowSameOrigin={true}
+							allowPopups={true}
+						/>
+					</div>
+				{/each}
+			</div>
+		{:else}
 			<div
-				class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done &&
-				attributes?.done !== 'true'
-					? 'shimmer'
-					: ''}
-			"
+				class="{buttonClassName} cursor-pointer"
+				on:pointerup={() => {
+					if (!disabled) {
+						open = !open;
+					}
+				}}
 			>
-				{#if attributes?.done && attributes?.done !== 'true'}
-					<div>
-						<Spinner className="size-4" />
-					</div>
-				{/if}
+				<div
+					class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done &&
+					attributes?.done !== 'true'
+						? 'shimmer'
+						: ''}
+			"
+				>
+					{#if attributes?.done && attributes?.done !== 'true'}
+						<div>
+							<Spinner className="size-4" />
+						</div>
+					{/if}
 
-				<div class="">
-					{#if attributes?.type === 'reasoning'}
-						{#if attributes?.done === 'true' && attributes?.duration}
-							{#if attributes.duration < 1}
-								{$i18n.t('Thought for less than a second')}
-							{:else if attributes.duration < 60}
-								{$i18n.t('Thought for {{DURATION}} seconds', {
-									DURATION: attributes.duration
-								})}
-							{:else}
-								{$i18n.t('Thought for {{DURATION}}', {
-									DURATION: dayjs.duration(attributes.duration, 'seconds').humanize()
-								})}
-							{/if}
-						{:else}
-							{$i18n.t('Thinking...')}
-						{/if}
-					{:else if attributes?.type === 'code_interpreter'}
-						{#if attributes?.done === 'true'}
-							{$i18n.t('Analyzed')}
-						{:else}
-							{$i18n.t('Analyzing...')}
-						{/if}
-					{:else if attributes?.type === 'tool_calls'}
+					<div class="">
 						{#if attributes?.done === 'true'}
 							<Markdown
 								id={`${collapsibleId}-tool-calls-${attributes?.id}`}
@@ -152,130 +153,172 @@
 								})}
 							/>
 						{/if}
-					{:else}
-						{title}
-					{/if}
-				</div>
+					</div>
 
-				<div class="flex self-center translate-y-[1px]">
-					{#if open}
-						<ChevronUp strokeWidth="3.5" className="size-3.5" />
-					{:else}
-						<ChevronDown strokeWidth="3.5" className="size-3.5" />
-					{/if}
+					<div class="flex self-center translate-y-[1px]">
+						{#if open}
+							<ChevronUp strokeWidth="3.5" className="size-3.5" />
+						{:else}
+							<ChevronDown strokeWidth="3.5" className="size-3.5" />
+						{/if}
+					</div>
 				</div>
 			</div>
-		</div>
-	{:else}
-		<!-- svelte-ignore a11y-no-static-element-interactions -->
-		<!-- svelte-ignore a11y-click-events-have-key-events -->
-		<div
-			class="{buttonClassName} cursor-pointer"
-			on:click={(e) => {
-				e.stopPropagation();
-			}}
-			on:pointerup={(e) => {
-				if (!disabled) {
-					open = !open;
-				}
-			}}
-		>
-			<div>
-				<div class="flex items-start justify-between">
-					<slot />
 
-					{#if chevron}
-						<div class="flex self-start translate-y-1">
-							{#if open}
-								<ChevronUp strokeWidth="3.5" className="size-3.5" />
+			{#if !grow}
+				{#if open && !hide}
+					<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
+						{#if attributes?.type === 'tool_calls'}
+							{#if attributes?.done === 'true'}
+								<Markdown
+									id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
+									content={`> \`\`\`json
+> ${formatJSONString(args)}
+> ${formatJSONString(result)}
+> \`\`\``}
+								/>
 							{:else}
-								<ChevronDown strokeWidth="3.5" className="size-3.5" />
+								<Markdown
+									id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
+									content={`> \`\`\`json
+> ${formatJSONString(args)}
+> \`\`\``}
+								/>
 							{/if}
-						</div>
-					{/if}
-				</div>
-
-				{#if grow}
-					{#if open && !hide}
-						<div
-							transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}
-							on:pointerup={(e) => {
-								e.stopPropagation();
-							}}
-						>
+						{:else}
 							<slot name="content" />
-						</div>
+						{/if}
+					</div>
+				{/if}
+
+				{#if attributes?.done === 'true'}
+					{#if typeof files === 'object'}
+						{#each files ?? [] as file, idx}
+							{#if file.startsWith('data:image/')}
+								<Image
+									id={`${collapsibleId}-tool-calls-${attributes?.id}-result-${idx}`}
+									src={file}
+									alt="Image"
+								/>
+							{/if}
+						{/each}
 					{/if}
 				{/if}
+			{/if}
+		{/if}
+	{:else}
+		{#if title !== null}
+			<!-- svelte-ignore a11y-no-static-element-interactions -->
+			<!-- svelte-ignore a11y-click-events-have-key-events -->
+			<div
+				class="{buttonClassName} cursor-pointer"
+				on:pointerup={() => {
+					if (!disabled) {
+						open = !open;
+					}
+				}}
+			>
+				<div
+					class=" w-full font-medium flex items-center justify-between gap-2 {attributes?.done &&
+					attributes?.done !== 'true'
+						? 'shimmer'
+						: ''}
+			"
+				>
+					{#if attributes?.done && attributes?.done !== 'true'}
+						<div>
+							<Spinner className="size-4" />
+						</div>
+					{/if}
+
+					<div class="">
+						{#if attributes?.type === 'reasoning'}
+							{#if attributes?.done === 'true' && attributes?.duration}
+								{#if attributes.duration < 1}
+									{$i18n.t('Thought for less than a second')}
+								{:else if attributes.duration < 60}
+									{$i18n.t('Thought for {{DURATION}} seconds', {
+										DURATION: attributes.duration
+									})}
+								{:else}
+									{$i18n.t('Thought for {{DURATION}}', {
+										DURATION: dayjs.duration(attributes.duration, 'seconds').humanize()
+									})}
+								{/if}
+							{:else}
+								{$i18n.t('Thinking...')}
+							{/if}
+						{:else if attributes?.type === 'code_interpreter'}
+							{#if attributes?.done === 'true'}
+								{$i18n.t('Analyzed')}
+							{:else}
+								{$i18n.t('Analyzing...')}
+							{/if}
+						{:else}
+							{title}
+						{/if}
+					</div>
+
+					<div class="flex self-center translate-y-[1px]">
+						{#if open}
+							<ChevronUp strokeWidth="3.5" className="size-3.5" />
+						{:else}
+							<ChevronDown strokeWidth="3.5" className="size-3.5" />
+						{/if}
+					</div>
+				</div>
 			</div>
-		</div>
-	{/if}
+		{:else}
+			<!-- svelte-ignore a11y-no-static-element-interactions -->
+			<!-- svelte-ignore a11y-click-events-have-key-events -->
+			<div
+				class="{buttonClassName} cursor-pointer"
+				on:click={(e) => {
+					e.stopPropagation();
+				}}
+				on:pointerup={(e) => {
+					if (!disabled) {
+						open = !open;
+					}
+				}}
+			>
+				<div>
+					<div class="flex items-start justify-between">
+						<slot />
 
-	{#if attributes?.type === 'tool_calls'}
-		{@const args = decode(attributes?.arguments)}
-		{@const result = decode(attributes?.result ?? '')}
-		{@const files = parseJSONString(decode(attributes?.files ?? ''))}
-		{@const embeds = parseJSONString(decode(attributes?.embeds ?? ''))}
+						{#if chevron}
+							<div class="flex self-start translate-y-1">
+								{#if open}
+									<ChevronUp strokeWidth="3.5" className="size-3.5" />
+								{:else}
+									<ChevronDown strokeWidth="3.5" className="size-3.5" />
+								{/if}
+							</div>
+						{/if}
+					</div>
 
-		{#if embeds && Array.isArray(embeds) && embeds.length > 0}
-			{#each embeds as embed, idx}
-				<div class="my-2" id={`${collapsibleId}-tool-calls-${attributes?.id}-embed-${idx}`}>
-					<FullHeightIframe
-						src={embed}
-						allowScripts={true}
-						allowForms={$settings?.iframeSandboxAllowForms ?? false}
-						allowSameOrigin={$settings?.iframeSandboxAllowSameOrigin ?? false}
-						allowPopups={$settings?.iframeSandboxAllowPopups ?? false}
-					/>
+					{#if grow}
+						{#if open && !hide}
+							<div
+								transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}
+								on:pointerup={(e) => {
+									e.stopPropagation();
+								}}
+							>
+								<slot name="content" />
+							</div>
+						{/if}
+					{/if}
 				</div>
-			{/each}
+			</div>
 		{/if}
 
 		{#if !grow}
 			{#if open && !hide}
 				<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
-					{#if attributes?.type === 'tool_calls'}
-						{#if attributes?.done === 'true'}
-							<Markdown
-								id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
-								content={`> \`\`\`json
-> ${formatJSONString(args)}
-> ${formatJSONString(result)}
-> \`\`\``}
-							/>
-						{:else}
-							<Markdown
-								id={`${collapsibleId}-tool-calls-${attributes?.id}-result`}
-								content={`> \`\`\`json
-> ${formatJSONString(args)}
-> \`\`\``}
-							/>
-						{/if}
-					{:else}
-						<slot name="content" />
-					{/if}
+					<slot name="content" />
 				</div>
 			{/if}
-
-			{#if attributes?.done === 'true'}
-				{#if typeof files === 'object'}
-					{#each files ?? [] as file, idx}
-						{#if file.startsWith('data:image/')}
-							<Image
-								id={`${collapsibleId}-tool-calls-${attributes?.id}-result-${idx}`}
-								src={file}
-								alt="Image"
-							/>
-						{/if}
-					{/each}
-				{/if}
-			{/if}
-		{/if}
-	{:else if !grow}
-		{#if open && !hide}
-			<div transition:slide={{ duration: 300, easing: quintOut, axis: 'y' }}>
-				<slot name="content" />
-			</div>
 		{/if}
 	{/if}
 </div>

+ 13 - 53
src/lib/components/common/FullHeightIframe.svelte

@@ -4,7 +4,8 @@
 	// Props
 	export let src: string | null = null; // URL or raw HTML (auto-detected)
 	export let title = 'Embedded Content';
-	export let initialHeight = 400; // fallback height if we can't measure
+	export let initialHeight: number | null = null; // initial height in px, null = auto
+
 	export let allowScripts = true;
 	export let allowForms = false;
 
@@ -33,13 +34,14 @@
 	// Detect URL vs raw HTML and prep src/srcdoc
 	$: isUrl = typeof src === 'string' && /^(https?:)?\/\//i.test(src);
 	$: iframeSrc = isUrl ? (src as string) : null;
-	$: iframeDoc = !isUrl && src ? ensureAutosizer(src) : null;
+	$: iframeDoc = !isUrl ? src : null;
 
 	// Try to measure same-origin content safely
 	function resizeSameOrigin() {
 		if (!iframe) return;
 		try {
 			const doc = iframe.contentDocument || iframe.contentWindow?.document;
+			console.log('iframe doc:', doc);
 			if (!doc) return;
 			const h = Math.max(doc.documentElement?.scrollHeight ?? 0, doc.body?.scrollHeight ?? 0);
 			if (h > 0) iframe.style.height = h + 20 + 'px';
@@ -57,6 +59,11 @@
 		}
 	}
 
+	// When the iframe loads, try same-origin resize (cross-origin will noop)
+	function onLoad() {
+		requestAnimationFrame(resizeSameOrigin);
+	}
+
 	// Ensure event listener bound only while component lives
 	onMount(() => {
 		window.addEventListener('message', onMessage);
@@ -65,52 +72,6 @@
 	onDestroy(() => {
 		window.removeEventListener('message', onMessage);
 	});
-
-	// When the iframe loads, try same-origin resize (cross-origin will noop)
-	function onLoad() {
-		// schedule after layout
-		requestAnimationFrame(resizeSameOrigin);
-	}
-
-	/**
-	 * If user passes raw HTML, we inject a tiny autosizer that posts height.
-	 * This helps both same-origin and "about:srcdoc" cases.
-	 * (No effect if the caller already includes their own autosizer.)
-	 */
-	function ensureAutosizer(html: string): string {
-		const hasOurHook = /iframe:height/.test(html) || /postMessage\(.+height/i.test(html);
-		if (hasOurHook) return html;
-
-		// This script uses ResizeObserver to post the document height
-		const autosizer = `
-<script>
-(function () {
-  function send() {
-    try {
-      var h = Math.max(
-        document.documentElement.scrollHeight || 0,
-        document.body ? document.body.scrollHeight : 0
-      );
-      parent.postMessage({ type: 'iframe:height', height: h + 20 }, '*');
-    } catch (e) {}
-  }
-  var ro = new ResizeObserver(function(){ send(); });
-  ro.observe(document.documentElement);
-  window.addEventListener('load', send);
-  // Also observe body if present
-  if (document.body) ro.observe(document.body);
-  // Periodic guard in case of late content
-  setTimeout(send, 0);
-  setTimeout(send, 250);
-  setTimeout(send, 1000);
-})();
-<\/script>`;
-		// inject before </body> if present, else append
-		return (
-			html.replace(/<\/body\s*>/i, autosizer + '</body>') +
-			(/<\/body\s*>/i.test(html) ? '' : autosizer)
-		);
-	}
 </script>
 
 {#if iframeDoc}
@@ -118,12 +79,11 @@
 		bind:this={iframe}
 		srcdoc={iframeDoc}
 		{title}
-		class="w-full rounded-xl"
-		style={`height:${initialHeight}px;`}
+		class="w-full rounded-2xl"
+		style={`${initialHeight ? `height:${initialHeight}px;` : ''}`}
 		width="100%"
 		frameborder="0"
 		{sandbox}
-		referrerpolicy={referrerPolicy}
 		{allowFullscreen}
 		on:load={onLoad}
 	/>
@@ -132,8 +92,8 @@
 		bind:this={iframe}
 		src={iframeSrc}
 		{title}
-		class="w-full rounded-xl"
-		style={`height:${initialHeight}px;`}
+		class="w-full rounded-2xl"
+		style={`${initialHeight ? `height:${initialHeight}px;` : ''}`}
 		width="100%"
 		frameborder="0"
 		{sandbox}