This commit is contained in:
Timothy Jaeryang Baek committed 2026-07-27 01:32:41 -04:00
1 parent 8a90bf6256
commit 067cf31f40
5 files changed
+73 -65

No files matched your search

@@ -372,7 +372,6 @@
const node = e.node; const node = e.node;
showMessage(node.data.message, true); showMessage(node.data.message, true);
}} }}
onClose={() => showControls.set(false)}
/> />
{:else if activeTab === 'files' && $selectedTerminalId} {:else if activeTab === 'files' && $selectedTerminalId}
<FileNav onAttach={handleTerminalAttach} {chatId} /> <FileNav onAttach={handleTerminalAttach} {chatId} />
@@ -522,7 +521,6 @@
} }
showMessage(node.data.message, true); showMessage(node.data.message, true);
}} }}
onClose={() => showControls.set(false)}
/> />
{:else if activeTab === 'files' && $selectedTerminalId} {:else if activeTab === 'files' && $selectedTerminalId}
<FileNav onAttach={handleTerminalAttach} overlay={dragged} {chatId} /> <FileNav onAttach={handleTerminalAttach} overlay={dragged} {chatId} />
+2 -7
View File
@@ -1,17 +1,12 @@
<script lang="ts"> <script lang="ts">
import { getContext, createEventDispatcher, onDestroy } from 'svelte'; import { SvelteFlowProvider } from '@xyflow/svelte';
import { useSvelteFlow, useNodesInitialized, useStore, SvelteFlowProvider } from '@xyflow/svelte';
const dispatch = createEventDispatcher();
import View from './Overview/View.svelte'; import View from './Overview/View.svelte';
export let history; export let history;
export let onClose;
export let onNodeClick; export let onNodeClick;
</script> </script>
<SvelteFlowProvider> <SvelteFlowProvider>
<View {history} {onClose} {onNodeClick} /> <View {history} {onNodeClick} />
</SvelteFlowProvider> </SvelteFlowProvider>
+15 -2
View File
@@ -1,7 +1,9 @@
<script> <script>
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { getContext } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
import { theme } from '$lib/stores'; import { theme } from '$lib/stores';
import { import {
@@ -11,15 +13,16 @@
BackgroundVariant, BackgroundVariant,
ControlButton ControlButton
} from '@xyflow/svelte'; } from '@xyflow/svelte';
import BarsArrowUp from '$lib/components/icons/BarsArrowUp.svelte';
import Bars3BottomLeft from '$lib/components/icons/Bars3BottomLeft.svelte';
import AlignVertical from '$lib/components/icons/AlignVertical.svelte'; import AlignVertical from '$lib/components/icons/AlignVertical.svelte';
import AlignHorizontal from '$lib/components/icons/AlignHorizontal.svelte'; import AlignHorizontal from '$lib/components/icons/AlignHorizontal.svelte';
import Pin from '$lib/components/icons/Pin.svelte';
import PinSlash from '$lib/components/icons/PinSlash.svelte';
export let nodes; export let nodes;
export let nodeTypes; export let nodeTypes;
export let edges; export let edges;
export let setLayoutDirection; export let setLayoutDirection;
export let pinned = false;
</script> </script>
<SvelteFlow <SvelteFlow
@@ -43,6 +46,16 @@
}} }}
> >
<Controls showLock={false}> <Controls showLock={false}>
<ControlButton
on:click={() => (pinned = !pinned)}
title={pinned ? $i18n.t('Viewport Pinned') : $i18n.t('Viewport Unpinned')}
>
{#if pinned}
<Pin />
{:else}
<PinSlash />
{/if}
</ControlButton>
<ControlButton on:click={() => setLayoutDirection('vertical')} title="Vertical Layout"> <ControlButton on:click={() => setLayoutDirection('vertical')} title="Vertical Layout">
<AlignVertical className="size-4" /> <AlignVertical className="size-4" />
</ControlButton> </ControlButton>
+2 -2
View File
@@ -33,7 +33,7 @@
src={`${WEBUI_API_BASE_URL}/users/${data.user.id}/profile/image`} src={`${WEBUI_API_BASE_URL}/users/${data.user.id}/profile/image`}
className={'size-5 -translate-y-[1px] flex-shrink-0'} className={'size-5 -translate-y-[1px] flex-shrink-0'}
/> />
<div class="ml-2"> <div class="ml-2 flex-1 min-w-0">
<div class=" flex justify-between items-center"> <div class=" flex justify-between items-center">
<div class="text-xs text-black dark:text-white font-normal line-clamp-1"> <div class="text-xs text-black dark:text-white font-normal line-clamp-1">
{data?.user?.name ?? 'User'} {data?.user?.name ?? 'User'}
@@ -54,7 +54,7 @@
className={'size-5 -translate-y-[1px] flex-shrink-0'} className={'size-5 -translate-y-[1px] flex-shrink-0'}
/> />
<div class="ml-2"> <div class="ml-2 flex-1 min-w-0">
<div class=" flex justify-between items-center"> <div class=" flex justify-between items-center">
<div class="text-xs text-black dark:text-white font-normal line-clamp-1"> <div class="text-xs text-black dark:text-white font-normal line-clamp-1">
{data?.model?.name ?? data?.message?.model ?? 'Assistant'} {data?.model?.name ?? data?.message?.model ?? 'Assistant'}
+54 -52
View File
@@ -1,37 +1,43 @@
<script lang="ts"> <script lang="ts">
import { getContext, createEventDispatcher, onDestroy } from 'svelte';
import { useSvelteFlow, useNodesInitialized, useStore } from '@xyflow/svelte';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import {
useSvelteFlow,
useNodesInitialized,
useStore,
type Edge,
type Node
} from '@xyflow/svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { models, theme, user } from '$lib/stores'; import { models, user } from '$lib/stores';
import '@xyflow/svelte/dist/style.css'; import '@xyflow/svelte/dist/style.css';
import CustomNode from './Node.svelte'; import CustomNode from './Node.svelte';
import Flow from './Flow.svelte'; import Flow from './Flow.svelte';
import XMark from '../../icons/XMark.svelte';
import ArrowLeft from '../../icons/ArrowLeft.svelte';
const { width, height } = useStore(); const { width, height } = useStore();
const { fitView, getViewport } = useSvelteFlow(); const { fitView } = useSvelteFlow();
const nodesInitialized = useNodesInitialized(); const nodesInitialized = useNodesInitialized();
export let history; export let history;
export let onClose;
export let onNodeClick; export let onNodeClick;
let selectedMessageId = null; type LayoutDirection = 'vertical' | 'horizontal';
type PositionMapEntry = {
id: string;
level: number;
position: number;
};
const nodes = writable([]); let selectedMessageId: string | null = null;
const edges = writable([]); let pinned = false;
let layoutDirection = 'vertical'; const nodes = writable<Node[]>([]);
const edges = writable<Edge[]>([]);
let layoutDirection: LayoutDirection = 'vertical';
const nodeTypes = { const nodeTypes = {
custom: CustomNode custom: CustomNode
@@ -41,7 +47,7 @@
drawFlow(layoutDirection); drawFlow(layoutDirection);
} }
$: if (history && history.currentId) { $: if (history && history.currentId && !pinned) {
focusNode(); focusNode();
} }
@@ -55,23 +61,17 @@
selectedMessageId = null; selectedMessageId = null;
}; };
const drawFlow = async (direction) => { const drawFlow = async (direction: LayoutDirection) => {
const nodeList = []; const nodeList: Node[] = [];
const edgeList = []; const edgeList: Edge[] = [];
const levelOffset = direction === 'vertical' ? 150 : 300; const levelOffset = direction === 'vertical' ? 150 : 300;
const siblingOffset = direction === 'vertical' ? 250 : 150; const siblingOffset = direction === 'vertical' ? 250 : 150;
// Map to keep track of node positions at each level // Map to keep track of node positions at each level
let positionMap = new Map(); let positionMap = new Map<string, PositionMapEntry>();
// Helper function to truncate labels
function createLabel(content) {
const maxLength = 100;
return content.length > maxLength ? content.substr(0, maxLength) + '...' : content;
}
// Create nodes and map children to ensure alignment in width // Create nodes and map children to ensure alignment in width
let layerWidths = {}; // Track widths of each layer let layerWidths: Record<number, number> = {}; // Track widths of each layer
Object.keys(history.messages).forEach((id) => { Object.keys(history.messages).forEach((id) => {
const message = history.messages[id]; const message = history.messages[id];
@@ -125,15 +125,15 @@
await nodes.set([...nodeList]); await nodes.set([...nodeList]);
}; };
const recurseCheckChild = (nodeId, currentId) => { const recurseCheckChild = (nodeId: string, currentId: string): boolean => {
const node = history.messages[nodeId]; const node = history.messages[nodeId];
return ( return (
node.childrenIds && node.childrenIds &&
node.childrenIds.some((id) => id === currentId || recurseCheckChild(id, currentId)) node.childrenIds.some((id: string) => id === currentId || recurseCheckChild(id, currentId))
); );
}; };
const setLayoutDirection = (direction) => { const setLayoutDirection = (direction: LayoutDirection) => {
layoutDirection = direction; layoutDirection = direction;
drawFlow(layoutDirection); drawFlow(layoutDirection);
}; };
@@ -141,33 +141,31 @@
onMount(() => { onMount(() => {
drawFlow(layoutDirection); drawFlow(layoutDirection);
nodesInitialized.subscribe(async (initialized) => { const stopNodesInitialized = nodesInitialized.subscribe(async (initialized) => {
if (initialized) { if (initialized && !pinned) {
await tick(); await tick();
const res = await fitView({ nodes: [{ id: history.currentId }] }); await fitView({ nodes: [{ id: history.currentId }] });
} }
}); });
const stopWidth = width.subscribe((value) => {
width.subscribe((value) => { if (value && !pinned) {
if (value) { fitView({ nodes: [{ id: history.currentId }] });
// fitView(); }
});
const stopHeight = height.subscribe((value) => {
if (value && !pinned) {
fitView({ nodes: [{ id: history.currentId }] }); fitView({ nodes: [{ id: history.currentId }] });
} }
}); });
height.subscribe((value) => { return () => {
if (value) { console.log('Overview destroyed');
// fitView(); stopNodesInitialized();
fitView({ nodes: [{ id: history.currentId }] }); stopWidth();
} stopHeight();
}); nodes.set([]);
}); edges.set([]);
};
onDestroy(() => {
console.log('Overview destroyed');
nodes.set([]);
edges.set([]);
}); });
</script> </script>
@@ -178,10 +176,14 @@
{nodeTypes} {nodeTypes}
{edges} {edges}
{setLayoutDirection} {setLayoutDirection}
bind:pinned
on:nodeclick={(e) => { on:nodeclick={(e) => {
onNodeClick(e.detail); onNodeClick(e.detail);
selectedMessageId = e.detail.node.data.message.id; const clickedMessageId = e.detail.node.data.message.id as string;
fitView({ nodes: [{ id: selectedMessageId }] }); selectedMessageId = clickedMessageId;
if (!pinned) {
fitView({ nodes: [{ id: clickedMessageId }] });
}
}} }}
/> />
{/if} {/if}