|
@@ -0,0 +1,143 @@
|
|
|
+<script>
|
|
|
+ import { getContext, createEventDispatcher } from 'svelte';
|
|
|
+ import { useSvelteFlow, useNodesInitialized, useStore } from '@xyflow/svelte';
|
|
|
+ import { SvelteFlow, Controls, Background, BackgroundVariant } 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();
|
|
|
+ }
|
|
|
+
|
|
|
+ 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,
|
|
|
+ type: 'smoothstep',
|
|
|
+ animated: true
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ await edges.set([...edgeList]);
|
|
|
+ await nodes.set([...nodeList]);
|
|
|
+ };
|
|
|
+
|
|
|
+ onMount(() => {
|
|
|
+ nodesInitialized.subscribe(async (initialized) => {
|
|
|
+ if (initialized) {
|
|
|
+ await tick();
|
|
|
+ const res = await fitView();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ width.subscribe((value) => {
|
|
|
+ if (value) {
|
|
|
+ fitView();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ height.subscribe((value) => {
|
|
|
+ if (value) {
|
|
|
+ fitView();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+</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}
|
|
|
+ <Flow {nodes} {nodeTypes} {edges} />
|
|
|
+ {/if}
|
|
|
+</div>
|