2024-09-17 22:18:47 +00:00
|
|
|
<script lang="ts">
|
2024-09-18 01:47:04 +00:00
|
|
|
import { getContext, createEventDispatcher, onDestroy } from 'svelte';
|
2024-09-17 20:05:19 +00:00
|
|
|
import { useSvelteFlow, useNodesInitialized, useStore } from '@xyflow/svelte';
|
|
|
|
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
const i18n = getContext('i18n');
|
|
|
|
|
|
|
|
import { onMount, tick } from 'svelte';
|
|
|
|
|
|
|
|
import { writable } from 'svelte/store';
|
|
|
|
import { models, showOverview, theme, user } from '$lib/stores';
|
|
|
|
|
|
|
|
import '@xyflow/svelte/dist/style.css';
|
|
|
|
|
|
|
|
import CustomNode from './Overview/Node.svelte';
|
|
|
|
import Flow from './Overview/Flow.svelte';
|
|
|
|
import XMark from '../icons/XMark.svelte';
|
|
|
|
|
|
|
|
const { width, height } = useStore();
|
|
|
|
|
|
|
|
const { fitView, getViewport } = useSvelteFlow();
|
|
|
|
const nodesInitialized = useNodesInitialized();
|
|
|
|
|
|
|
|
export let history;
|
|
|
|
|
|
|
|
const nodes = writable([]);
|
|
|
|
const edges = writable([]);
|
|
|
|
|
|
|
|
const nodeTypes = {
|
|
|
|
custom: CustomNode
|
|
|
|
};
|
|
|
|
|
|
|
|
$: if (history) {
|
|
|
|
drawFlow();
|
|
|
|
}
|
|
|
|
|
2024-09-18 23:25:46 +00:00
|
|
|
$: if (history?.currentId) {
|
|
|
|
fitView({ nodes: [{ id: history.currentId }] });
|
|
|
|
}
|
|
|
|
|
2024-09-17 20:05:19 +00:00
|
|
|
const drawFlow = async () => {
|
|
|
|
const nodeList = [];
|
|
|
|
const edgeList = [];
|
|
|
|
const levelOffset = 150; // Vertical spacing between layers
|
|
|
|
const siblingOffset = 250; // Horizontal spacing between nodes at the same layer
|
|
|
|
|
|
|
|
// Map to keep track of node positions at each level
|
|
|
|
let positionMap = new Map();
|
|
|
|
|
|
|
|
// 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
|
|
|
|
let layerWidths = {}; // Track widths of each layer
|
|
|
|
|
|
|
|
Object.keys(history.messages).forEach((id) => {
|
|
|
|
const message = history.messages[id];
|
|
|
|
const level = message.parentId ? positionMap.get(message.parentId).level + 1 : 0;
|
|
|
|
if (!layerWidths[level]) layerWidths[level] = 0;
|
|
|
|
|
|
|
|
positionMap.set(id, {
|
|
|
|
id: message.id,
|
|
|
|
level,
|
|
|
|
position: layerWidths[level]++
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Adjust positions based on siblings count to centralize vertical spacing
|
|
|
|
Object.keys(history.messages).forEach((id) => {
|
|
|
|
const pos = positionMap.get(id);
|
|
|
|
const xOffset = pos.position * siblingOffset;
|
|
|
|
const y = pos.level * levelOffset;
|
|
|
|
const x = xOffset;
|
|
|
|
|
|
|
|
nodeList.push({
|
|
|
|
id: pos.id,
|
|
|
|
type: 'custom',
|
|
|
|
data: {
|
|
|
|
user: $user,
|
|
|
|
message: history.messages[id],
|
|
|
|
model: $models.find((model) => model.id === history.messages[id].model),
|
|
|
|
label: createLabel(history.messages[id].content)
|
|
|
|
},
|
|
|
|
position: { x, y }
|
|
|
|
});
|
|
|
|
|
|
|
|
// Create edges
|
|
|
|
const parentId = history.messages[id].parentId;
|
|
|
|
if (parentId) {
|
|
|
|
edgeList.push({
|
|
|
|
id: parentId + '-' + pos.id,
|
|
|
|
source: parentId,
|
|
|
|
target: pos.id,
|
2024-09-17 22:18:47 +00:00
|
|
|
selectable: false,
|
2024-09-18 00:42:19 +00:00
|
|
|
class: ' dark:fill-gray-300 fill-gray-300',
|
2024-09-17 20:05:19 +00:00
|
|
|
type: 'smoothstep',
|
2024-09-18 00:42:19 +00:00
|
|
|
animated: history.currentId === id || recurseCheckChild(id, history.currentId)
|
2024-09-17 20:05:19 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
await edges.set([...edgeList]);
|
|
|
|
await nodes.set([...nodeList]);
|
|
|
|
};
|
|
|
|
|
2024-09-18 00:42:19 +00:00
|
|
|
const recurseCheckChild = (nodeId, currentId) => {
|
|
|
|
const node = history.messages[nodeId];
|
|
|
|
return (
|
|
|
|
node.childrenIds &&
|
|
|
|
node.childrenIds.some((id) => id === currentId || recurseCheckChild(id, currentId))
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-09-17 20:05:19 +00:00
|
|
|
onMount(() => {
|
2024-09-18 01:47:04 +00:00
|
|
|
drawFlow();
|
|
|
|
|
2024-09-17 20:05:19 +00:00
|
|
|
nodesInitialized.subscribe(async (initialized) => {
|
|
|
|
if (initialized) {
|
|
|
|
await tick();
|
2024-09-18 23:25:46 +00:00
|
|
|
const res = await fitView({ nodes: [{ id: history.currentId }] });
|
2024-09-17 20:05:19 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
width.subscribe((value) => {
|
|
|
|
if (value) {
|
2024-09-18 23:25:46 +00:00
|
|
|
// fitView();
|
|
|
|
fitView({ nodes: [{ id: history.currentId }] });
|
2024-09-17 20:05:19 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
height.subscribe((value) => {
|
|
|
|
if (value) {
|
2024-09-18 23:25:46 +00:00
|
|
|
// fitView();
|
|
|
|
fitView({ nodes: [{ id: history.currentId }] });
|
2024-09-17 20:05:19 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2024-09-18 01:47:04 +00:00
|
|
|
|
|
|
|
onDestroy(() => {
|
|
|
|
console.log('Overview destroyed');
|
|
|
|
|
|
|
|
nodes.set([]);
|
|
|
|
edges.set([]);
|
|
|
|
});
|
2024-09-17 20:05:19 +00:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<div class="w-full h-full relative">
|
|
|
|
<div class=" absolute z-50 w-full flex justify-between dark:text-gray-100 px-5 py-4">
|
|
|
|
<div class=" text-lg font-medium self-center font-primary">{$i18n.t('Chat Overview')}</div>
|
|
|
|
<button
|
|
|
|
class="self-center"
|
|
|
|
on:click={() => {
|
|
|
|
dispatch('close');
|
|
|
|
showOverview.set(false);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<XMark className="size-4" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{#if $nodes.length > 0}
|
2024-09-18 01:13:37 +00:00
|
|
|
<Flow {nodes} {nodeTypes} {edges} on:nodeclick={(e) => dispatch('nodeclick', e.detail)} />
|
2024-09-17 20:05:19 +00:00
|
|
|
{/if}
|
|
|
|
</div>
|