Spaces:
Running
Running
responsive
Browse files- src/lib/components/chat/User.svelte +48 -22
- src/lib/components/flow/FitViewOnResize.svelte +6 -5
- src/lib/components/flow/actions/PanelRightActions.svelte +15 -11
- src/lib/components/model/ComboBoxModels.svelte +1 -2
- src/lib/consts.ts +32 -0
- src/lib/helpers/nodes-and-edges.ts +0 -0
- src/lib/helpers/utils.ts +0 -6
- src/lib/index.ts +4 -1
- src/lib/state/breakpoints.svelte.ts +3 -0
- src/routes/+error.svelte +6 -0
- src/routes/+layout.svelte +8 -0
- src/routes/+page.svelte +9 -7
- src/routes/api/+server.ts +3 -4
src/lib/components/chat/User.svelte
CHANGED
|
@@ -19,6 +19,9 @@
|
|
| 19 |
import Spinner from '$lib/components/loading/Spinner.svelte';
|
| 20 |
import Message from './Message.svelte';
|
| 21 |
import SettingsModel from '$lib/components/model/SettingsModel.svelte';
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
let { id }: NodeProps = $props();
|
| 24 |
|
|
@@ -31,18 +34,23 @@
|
|
| 31 |
let selectedModels = $derived<ChatModel[]>(
|
| 32 |
(nodeData.current?.data.selectedModels as ChatModel[]) ?? []
|
| 33 |
);
|
|
|
|
| 34 |
let prompt = $state.raw<string>((nodeData.current?.data.prompt as string) ?? '');
|
| 35 |
let loading = $state.raw<boolean>(false);
|
| 36 |
let messages = $state.raw<ChatMessage[]>(
|
| 37 |
(nodeData.current?.data?.messages as ChatMessage[]) ?? []
|
| 38 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
function addModel(model: ChatModel) {
|
| 41 |
if (!selectedModels.some((m) => m.id === model.id)) {
|
| 42 |
updateNodeData(
|
| 43 |
id,
|
| 44 |
{
|
| 45 |
-
...nodeData.current,
|
| 46 |
selectedModels: [...selectedModels, model]
|
| 47 |
},
|
| 48 |
{ replace: true }
|
|
@@ -56,7 +64,7 @@
|
|
| 56 |
updateNodeData(
|
| 57 |
id,
|
| 58 |
{
|
| 59 |
-
...nodeData.current,
|
| 60 |
selectedModels: selectedModels.filter((m) => m.id !== model.id)
|
| 61 |
},
|
| 62 |
{ replace: true }
|
|
@@ -95,12 +103,6 @@
|
|
| 95 |
});
|
| 96 |
updateNodes((currentNodes) => [...currentNodes, ...newNodes]);
|
| 97 |
updateEdges((currentEdges) => [...currentEdges, ...newEdges]);
|
| 98 |
-
fitView({
|
| 99 |
-
maxZoom: 1,
|
| 100 |
-
minZoom: 1,
|
| 101 |
-
interpolate: 'smooth',
|
| 102 |
-
duration: 500
|
| 103 |
-
});
|
| 104 |
handleTriggerAiCall(newNodes);
|
| 105 |
}
|
| 106 |
|
|
@@ -114,8 +116,8 @@
|
|
| 114 |
method: 'POST',
|
| 115 |
body: JSON.stringify({
|
| 116 |
model: model.id,
|
| 117 |
-
prompt,
|
| 118 |
provider: model.provider,
|
|
|
|
| 119 |
options: {
|
| 120 |
temperature: model.temperature,
|
| 121 |
max_tokens: model.max_tokens,
|
|
@@ -182,7 +184,9 @@
|
|
| 182 |
);
|
| 183 |
</script>
|
| 184 |
|
| 185 |
-
<article
|
|
|
|
|
|
|
| 186 |
<div class="nodrag cursor-auto">
|
| 187 |
<header class="mb-3 flex items-center justify-between">
|
| 188 |
<div class="flex flex-wrap items-center gap-1">
|
|
@@ -221,7 +225,7 @@
|
|
| 221 |
</Button>
|
| 222 |
</SettingsModel>
|
| 223 |
{/each}
|
| 224 |
-
{#if selectedModels.length <
|
| 225 |
<ComboBoxModels onSelect={addModel} excludeIds={selectedModels.map((m) => m.id)} />
|
| 226 |
{/if}
|
| 227 |
</div>
|
|
@@ -247,19 +251,41 @@
|
|
| 247 |
}
|
| 248 |
}}
|
| 249 |
></textarea>
|
| 250 |
-
<
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
{:else}
|
| 260 |
-
<
|
| 261 |
{/if}
|
| 262 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
</footer>
|
| 264 |
{/if}
|
| 265 |
</div>
|
|
|
|
| 19 |
import Spinner from '$lib/components/loading/Spinner.svelte';
|
| 20 |
import Message from './Message.svelte';
|
| 21 |
import SettingsModel from '$lib/components/model/SettingsModel.svelte';
|
| 22 |
+
import { MAX_MODELS_PER_NODE, MAX_SUGGESTIONS } from '$lib';
|
| 23 |
+
import { SUGGESTIONS_PROMPT } from '$lib/consts';
|
| 24 |
+
import { breakpointsState } from '$lib/state/breakpoints.svelte';
|
| 25 |
|
| 26 |
let { id }: NodeProps = $props();
|
| 27 |
|
|
|
|
| 34 |
let selectedModels = $derived<ChatModel[]>(
|
| 35 |
(nodeData.current?.data.selectedModels as ChatModel[]) ?? []
|
| 36 |
);
|
| 37 |
+
let isFirstNode = $derived((nodeData.current?.data.isFirstNode as boolean) ?? false);
|
| 38 |
let prompt = $state.raw<string>((nodeData.current?.data.prompt as string) ?? '');
|
| 39 |
let loading = $state.raw<boolean>(false);
|
| 40 |
let messages = $state.raw<ChatMessage[]>(
|
| 41 |
(nodeData.current?.data?.messages as ChatMessage[]) ?? []
|
| 42 |
);
|
| 43 |
+
let randomSuggestions = SUGGESTIONS_PROMPT.sort(() => Math.random() - 0.5).slice(
|
| 44 |
+
0,
|
| 45 |
+
MAX_SUGGESTIONS
|
| 46 |
+
);
|
| 47 |
|
| 48 |
function addModel(model: ChatModel) {
|
| 49 |
if (!selectedModels.some((m) => m.id === model.id)) {
|
| 50 |
updateNodeData(
|
| 51 |
id,
|
| 52 |
{
|
| 53 |
+
...nodeData.current?.data,
|
| 54 |
selectedModels: [...selectedModels, model]
|
| 55 |
},
|
| 56 |
{ replace: true }
|
|
|
|
| 64 |
updateNodeData(
|
| 65 |
id,
|
| 66 |
{
|
| 67 |
+
...nodeData.current?.data,
|
| 68 |
selectedModels: selectedModels.filter((m) => m.id !== model.id)
|
| 69 |
},
|
| 70 |
{ replace: true }
|
|
|
|
| 103 |
});
|
| 104 |
updateNodes((currentNodes) => [...currentNodes, ...newNodes]);
|
| 105 |
updateEdges((currentEdges) => [...currentEdges, ...newEdges]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
handleTriggerAiCall(newNodes);
|
| 107 |
}
|
| 108 |
|
|
|
|
| 116 |
method: 'POST',
|
| 117 |
body: JSON.stringify({
|
| 118 |
model: model.id,
|
|
|
|
| 119 |
provider: model.provider,
|
| 120 |
+
messages,
|
| 121 |
options: {
|
| 122 |
temperature: model.temperature,
|
| 123 |
max_tokens: model.max_tokens,
|
|
|
|
| 184 |
);
|
| 185 |
</script>
|
| 186 |
|
| 187 |
+
<article
|
| 188 |
+
class="w-full min-w-sm rounded-3xl border border-border bg-background p-5 shadow-lg/5 lg:w-[600px]"
|
| 189 |
+
>
|
| 190 |
<div class="nodrag cursor-auto">
|
| 191 |
<header class="mb-3 flex items-center justify-between">
|
| 192 |
<div class="flex flex-wrap items-center gap-1">
|
|
|
|
| 225 |
</Button>
|
| 226 |
</SettingsModel>
|
| 227 |
{/each}
|
| 228 |
+
{#if selectedModels.length < MAX_MODELS_PER_NODE && !loading}
|
| 229 |
<ComboBoxModels onSelect={addModel} excludeIds={selectedModels.map((m) => m.id)} />
|
| 230 |
{/if}
|
| 231 |
</div>
|
|
|
|
| 251 |
}
|
| 252 |
}}
|
| 253 |
></textarea>
|
| 254 |
+
<div class="flex w-full items-center justify-between gap-1">
|
| 255 |
+
{#if isFirstNode && !loading && !lastMessage}
|
| 256 |
+
<div class="items flex w-full gap-1">
|
| 257 |
+
{#each randomSuggestions as suggestion}
|
| 258 |
+
<Button
|
| 259 |
+
variant="outline"
|
| 260 |
+
size="2xs"
|
| 261 |
+
class="rounded-full! shadow-none!"
|
| 262 |
+
disabled={!selectedModels.length}
|
| 263 |
+
onclick={() => {
|
| 264 |
+
prompt = suggestion;
|
| 265 |
+
handleTriggerAction();
|
| 266 |
+
}}
|
| 267 |
+
>
|
| 268 |
+
{suggestion}
|
| 269 |
+
</Button>
|
| 270 |
+
{/each}
|
| 271 |
+
</div>
|
| 272 |
{:else}
|
| 273 |
+
<div></div>
|
| 274 |
{/if}
|
| 275 |
+
<Button
|
| 276 |
+
variant={mode.current === 'dark' ? 'default' : 'outline'}
|
| 277 |
+
size="icon-sm"
|
| 278 |
+
class=""
|
| 279 |
+
disabled={!selectedModels.length || !prompt || loading}
|
| 280 |
+
onclick={() => handleTriggerAction()}
|
| 281 |
+
>
|
| 282 |
+
{#if loading}
|
| 283 |
+
<Spinner className="size-5" />
|
| 284 |
+
{:else}
|
| 285 |
+
<Send />
|
| 286 |
+
{/if}
|
| 287 |
+
</Button>
|
| 288 |
+
</div>
|
| 289 |
</footer>
|
| 290 |
{/if}
|
| 291 |
</div>
|
src/lib/components/flow/FitViewOnResize.svelte
CHANGED
|
@@ -3,14 +3,15 @@
|
|
| 3 |
import type { Node, Edge } from '@xyflow/svelte';
|
| 4 |
import { useNodes, useEdges } from '@xyflow/svelte';
|
| 5 |
import { useSvelteFlow } from '@xyflow/svelte';
|
|
|
|
| 6 |
|
| 7 |
let { initialNodes }: { initialNodes: Node[] } = $props();
|
| 8 |
|
| 9 |
// Fallback dimensions (used before nodes are measured by xyflow)
|
| 10 |
-
const DEFAULT_WIDTH = 600;
|
| 11 |
const DEFAULT_HEIGHT = 200;
|
| 12 |
-
const H_SPACING =
|
| 13 |
-
const V_SPACING =
|
| 14 |
|
| 15 |
const { fitView } = useSvelteFlow();
|
| 16 |
const nodesStore = useNodes();
|
|
@@ -68,7 +69,7 @@
|
|
| 68 |
function handleWindowResize() {
|
| 69 |
fitView({
|
| 70 |
maxZoom: 1,
|
| 71 |
-
minZoom: 0.8,
|
| 72 |
interpolate: 'smooth',
|
| 73 |
duration: 500
|
| 74 |
});
|
|
@@ -198,7 +199,7 @@
|
|
| 198 |
|
| 199 |
fitView({
|
| 200 |
maxZoom: 1,
|
| 201 |
-
minZoom: 0.8,
|
| 202 |
interpolate: 'smooth',
|
| 203 |
duration: 250
|
| 204 |
});
|
|
|
|
| 3 |
import type { Node, Edge } from '@xyflow/svelte';
|
| 4 |
import { useNodes, useEdges } from '@xyflow/svelte';
|
| 5 |
import { useSvelteFlow } from '@xyflow/svelte';
|
| 6 |
+
import { breakpointsState } from '$lib/state/breakpoints.svelte';
|
| 7 |
|
| 8 |
let { initialNodes }: { initialNodes: Node[] } = $props();
|
| 9 |
|
| 10 |
// Fallback dimensions (used before nodes are measured by xyflow)
|
| 11 |
+
const DEFAULT_WIDTH = breakpointsState.isMobile ? 300 : 600;
|
| 12 |
const DEFAULT_HEIGHT = 200;
|
| 13 |
+
const H_SPACING = 60;
|
| 14 |
+
const V_SPACING = 60;
|
| 15 |
|
| 16 |
const { fitView } = useSvelteFlow();
|
| 17 |
const nodesStore = useNodes();
|
|
|
|
| 69 |
function handleWindowResize() {
|
| 70 |
fitView({
|
| 71 |
maxZoom: 1,
|
| 72 |
+
minZoom: breakpointsState.isMobile ? 1 : 0.8,
|
| 73 |
interpolate: 'smooth',
|
| 74 |
duration: 500
|
| 75 |
});
|
|
|
|
| 199 |
|
| 200 |
fitView({
|
| 201 |
maxZoom: 1,
|
| 202 |
+
minZoom: breakpointsState.isMobile ? 1 : 0.8,
|
| 203 |
interpolate: 'smooth',
|
| 204 |
duration: 250
|
| 205 |
});
|
src/lib/components/flow/actions/PanelRightActions.svelte
CHANGED
|
@@ -7,7 +7,10 @@
|
|
| 7 |
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
| 8 |
import { tokenModalState } from '$lib/state/token-modal.svelte';
|
| 9 |
|
|
|
|
|
|
|
| 10 |
function handleReset() {
|
|
|
|
| 11 |
const ok = confirm('Are you sure you want to reset the flow?');
|
| 12 |
if (ok) {
|
| 13 |
location.reload();
|
|
@@ -15,9 +18,10 @@
|
|
| 15 |
}
|
| 16 |
</script>
|
| 17 |
|
| 18 |
-
<Panel position="top-right" class="flex items-center justify-end gap-2 p-3">
|
| 19 |
-
<Button variant="outline"
|
| 20 |
<RefreshCcw />
|
|
|
|
| 21 |
</Button>
|
| 22 |
<DropdownMenu.Root>
|
| 23 |
<DropdownMenu.Trigger>
|
|
@@ -29,10 +33,10 @@
|
|
| 29 |
</DropdownMenu.Trigger>
|
| 30 |
<DropdownMenu.Content class="w-56" align="start">
|
| 31 |
<DropdownMenu.Group>
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
</DropdownMenu.Group>
|
| 37 |
<DropdownMenu.Separator />
|
| 38 |
<DropdownMenu.Group>
|
|
@@ -42,11 +46,11 @@
|
|
| 42 |
Appearance
|
| 43 |
</DropdownMenu.SubTrigger>
|
| 44 |
<DropdownMenu.SubContent>
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
</DropdownMenu.SubContent>
|
| 51 |
</DropdownMenu.Sub>
|
| 52 |
</DropdownMenu.Group>
|
|
|
|
| 7 |
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
|
| 8 |
import { tokenModalState } from '$lib/state/token-modal.svelte';
|
| 9 |
|
| 10 |
+
let { canReset }: { canReset: boolean } = $props();
|
| 11 |
+
|
| 12 |
function handleReset() {
|
| 13 |
+
if (!canReset) return;
|
| 14 |
const ok = confirm('Are you sure you want to reset the flow?');
|
| 15 |
if (ok) {
|
| 16 |
location.reload();
|
|
|
|
| 18 |
}
|
| 19 |
</script>
|
| 20 |
|
| 21 |
+
<Panel position="top-right" class="flex items-center justify-end gap-2 p-1 lg:p-3">
|
| 22 |
+
<Button variant="outline" class="" onclick={handleReset}>
|
| 23 |
<RefreshCcw />
|
| 24 |
+
Start new chat
|
| 25 |
</Button>
|
| 26 |
<DropdownMenu.Root>
|
| 27 |
<DropdownMenu.Trigger>
|
|
|
|
| 33 |
</DropdownMenu.Trigger>
|
| 34 |
<DropdownMenu.Content class="w-56" align="start">
|
| 35 |
<DropdownMenu.Group>
|
| 36 |
+
<DropdownMenu.Item onclick={() => (tokenModalState.open = true)}>
|
| 37 |
+
Token Management
|
| 38 |
+
<DropdownMenu.Shortcut>⇧⌘T</DropdownMenu.Shortcut>
|
| 39 |
+
</DropdownMenu.Item>
|
| 40 |
</DropdownMenu.Group>
|
| 41 |
<DropdownMenu.Separator />
|
| 42 |
<DropdownMenu.Group>
|
|
|
|
| 46 |
Appearance
|
| 47 |
</DropdownMenu.SubTrigger>
|
| 48 |
<DropdownMenu.SubContent>
|
| 49 |
+
<DropdownMenu.Item onclick={() => setMode('light')}><Sun />Light</DropdownMenu.Item>
|
| 50 |
+
<DropdownMenu.Item onclick={() => setMode('dark')}><Moon />Dark</DropdownMenu.Item>
|
| 51 |
+
<DropdownMenu.Item onclick={() => setMode('system')}
|
| 52 |
+
><Monitor />System</DropdownMenu.Item
|
| 53 |
+
>
|
| 54 |
</DropdownMenu.SubContent>
|
| 55 |
</DropdownMenu.Sub>
|
| 56 |
</DropdownMenu.Group>
|
src/lib/components/model/ComboBoxModels.svelte
CHANGED
|
@@ -5,6 +5,7 @@
|
|
| 5 |
import { Button } from '$lib/components/ui/button/';
|
| 6 |
import { modelsState } from '$lib/state/models.svelte';
|
| 7 |
import type { ChatModel } from '$lib/helpers/types';
|
|
|
|
| 8 |
|
| 9 |
interface Props {
|
| 10 |
onSelect?: (model: ChatModel) => void;
|
|
@@ -23,8 +24,6 @@
|
|
| 23 |
})
|
| 24 |
);
|
| 25 |
|
| 26 |
-
const MAX_TRENDING_MODELS = 6;
|
| 27 |
-
|
| 28 |
let trendingModels = $derived(filteredModels.slice(0, MAX_TRENDING_MODELS));
|
| 29 |
let otherModels = $derived(filteredModels.slice(MAX_TRENDING_MODELS));
|
| 30 |
</script>
|
|
|
|
| 5 |
import { Button } from '$lib/components/ui/button/';
|
| 6 |
import { modelsState } from '$lib/state/models.svelte';
|
| 7 |
import type { ChatModel } from '$lib/helpers/types';
|
| 8 |
+
import { MAX_TRENDING_MODELS } from '$lib';
|
| 9 |
|
| 10 |
interface Props {
|
| 11 |
onSelect?: (model: ChatModel) => void;
|
|
|
|
| 24 |
})
|
| 25 |
);
|
| 26 |
|
|
|
|
|
|
|
| 27 |
let trendingModels = $derived(filteredModels.slice(0, MAX_TRENDING_MODELS));
|
| 28 |
let otherModels = $derived(filteredModels.slice(MAX_TRENDING_MODELS));
|
| 29 |
</script>
|
src/lib/consts.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const SUGGESTIONS_PROMPT = [
|
| 2 |
+
'Roast my code skills',
|
| 3 |
+
'Why do LLMs hallucinate?',
|
| 4 |
+
'Explain AI like I\'m 5',
|
| 5 |
+
'Is a hotdog a sandwich?',
|
| 6 |
+
'Pineapple on pizza: yes or no?',
|
| 7 |
+
'Write me a bad poem',
|
| 8 |
+
'Why is CSS so painful?',
|
| 9 |
+
'Tell me a dad joke',
|
| 10 |
+
'Best excuse to skip work?',
|
| 11 |
+
'Are robots coming for us?',
|
| 12 |
+
'Why do cats judge us?',
|
| 13 |
+
'Convince me earth is flat',
|
| 14 |
+
'Invent a new pasta shape',
|
| 15 |
+
'What would dogs google?',
|
| 16 |
+
'Why is Monday the worst?',
|
| 17 |
+
'Rate my mass: 70kg',
|
| 18 |
+
'Pick my lunch for me',
|
| 19 |
+
'Write a haiku about bugs',
|
| 20 |
+
'Am I in a simulation?',
|
| 21 |
+
'Explain blockchain to my grandma',
|
| 22 |
+
'Why do devs love coffee?',
|
| 23 |
+
'Sell me this pen',
|
| 24 |
+
'Make up a conspiracy theory',
|
| 25 |
+
'Worst date idea ever?',
|
| 26 |
+
'Name a fake startup',
|
| 27 |
+
'Describe WiFi to a pirate',
|
| 28 |
+
'Why do we yawn?',
|
| 29 |
+
'Invent a useless invention',
|
| 30 |
+
'Teach me a fun fact',
|
| 31 |
+
'What came first: egg or chicken?'
|
| 32 |
+
];
|
src/lib/helpers/nodes-and-edges.ts
DELETED
|
File without changes
|
src/lib/helpers/utils.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
import { type ClassValue, clsx } from 'clsx';
|
| 2 |
-
import { twMerge } from 'tailwind-merge';
|
| 3 |
-
|
| 4 |
-
export function cn(...inputs: ClassValue[]) {
|
| 5 |
-
return twMerge(clsx(inputs));
|
| 6 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/lib/index.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const MAX_MODELS_PER_NODE = 5;
|
| 2 |
+
export const MAX_TRENDING_MODELS = 6;
|
| 3 |
+
export const MAX_SUGGESTIONS = 2;
|
| 4 |
+
export const MAX_DEFAULT_MODELS = 1;
|
src/lib/state/breakpoints.svelte.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const breakpointsState = $state({
|
| 2 |
+
isMobile: false
|
| 3 |
+
});
|
src/routes/+error.svelte
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
import { page } from '$app/state';
|
| 3 |
+
</script>
|
| 4 |
+
|
| 5 |
+
<h1>{page.status} {page.error.message}</h1>
|
| 6 |
+
<span style="font-size: 10em"> 🙅 </span>
|
src/routes/+layout.svelte
CHANGED
|
@@ -7,6 +7,7 @@
|
|
| 7 |
import { fetchModels, modelsState } from '$lib/state/models.svelte';
|
| 8 |
import MainLoading from '$lib/components/loading/MainLoading.svelte';
|
| 9 |
import TokenManagementModal from '$lib/components/token/TokenManagementModal.svelte';
|
|
|
|
| 10 |
|
| 11 |
interface Props {
|
| 12 |
children?: import('svelte').Snippet;
|
|
@@ -14,8 +15,15 @@
|
|
| 14 |
|
| 15 |
onMount(() => {
|
| 16 |
fetchModels();
|
|
|
|
|
|
|
|
|
|
| 17 |
});
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
let { children }: Props = $props();
|
| 20 |
</script>
|
| 21 |
|
|
|
|
| 7 |
import { fetchModels, modelsState } from '$lib/state/models.svelte';
|
| 8 |
import MainLoading from '$lib/components/loading/MainLoading.svelte';
|
| 9 |
import TokenManagementModal from '$lib/components/token/TokenManagementModal.svelte';
|
| 10 |
+
import { breakpointsState } from '$lib/state/breakpoints.svelte';
|
| 11 |
|
| 12 |
interface Props {
|
| 13 |
children?: import('svelte').Snippet;
|
|
|
|
| 15 |
|
| 16 |
onMount(() => {
|
| 17 |
fetchModels();
|
| 18 |
+
handleBreakoints();
|
| 19 |
+
window.addEventListener('resize', handleBreakoints);
|
| 20 |
+
return () => window.removeEventListener('resize', handleBreakoints);
|
| 21 |
});
|
| 22 |
|
| 23 |
+
function handleBreakoints() {
|
| 24 |
+
breakpointsState.isMobile = window.innerWidth < 768;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
let { children }: Props = $props();
|
| 28 |
</script>
|
| 29 |
|
src/routes/+page.svelte
CHANGED
|
@@ -6,11 +6,10 @@
|
|
| 6 |
Panel,
|
| 7 |
BackgroundVariant,
|
| 8 |
type Node,
|
| 9 |
-
type Edge
|
| 10 |
-
type ColorMode
|
| 11 |
} from '@xyflow/svelte';
|
| 12 |
import '@xyflow/svelte/dist/style.css';
|
| 13 |
-
import {
|
| 14 |
|
| 15 |
import { modelsState } from '$lib/state/models.svelte';
|
| 16 |
import { tokenModalState } from '$lib/state/token-modal.svelte';
|
|
@@ -20,6 +19,8 @@
|
|
| 20 |
import type { ChatModel } from '$lib/helpers/types';
|
| 21 |
import FitViewOnResize from '$lib/components/flow/FitViewOnResize.svelte';
|
| 22 |
import PanelRightActions from '$lib/components/flow/actions/PanelRightActions.svelte';
|
|
|
|
|
|
|
| 23 |
|
| 24 |
const nodeTypes = {
|
| 25 |
user: User,
|
|
@@ -33,7 +34,8 @@
|
|
| 33 |
type: 'user',
|
| 34 |
position: { x: 0, y: 0 },
|
| 35 |
data: {
|
| 36 |
-
|
|
|
|
| 37 |
}
|
| 38 |
}
|
| 39 |
];
|
|
@@ -65,11 +67,11 @@
|
|
| 65 |
proOptions={{ hideAttribution: true }}
|
| 66 |
fitViewOptions={{
|
| 67 |
maxZoom: 1,
|
| 68 |
-
minZoom: 0.8,
|
| 69 |
interpolate: 'smooth',
|
| 70 |
duration: 500
|
| 71 |
}}
|
| 72 |
-
defaultEdgeOptions={{ type: '
|
| 73 |
class="bg-background!"
|
| 74 |
>
|
| 75 |
<FitViewOnResize {initialNodes} />
|
|
@@ -84,7 +86,7 @@
|
|
| 84 |
patternColor={mode.current === 'dark' ? 'rgba(255, 255, 255, 0.04)' : 'rgba(0, 0, 0, 0.04)'}
|
| 85 |
class="bg-background!"
|
| 86 |
/>
|
| 87 |
-
<PanelRightActions />
|
| 88 |
<Panel
|
| 89 |
position="bottom-left"
|
| 90 |
class="flex items-center justify-center gap-1.5 rounded-lg border border-border bg-background py-1.5 pr-3.5 pl-2.5 shadow-xs"
|
|
|
|
| 6 |
Panel,
|
| 7 |
BackgroundVariant,
|
| 8 |
type Node,
|
| 9 |
+
type Edge
|
|
|
|
| 10 |
} from '@xyflow/svelte';
|
| 11 |
import '@xyflow/svelte/dist/style.css';
|
| 12 |
+
import { mode } from 'mode-watcher';
|
| 13 |
|
| 14 |
import { modelsState } from '$lib/state/models.svelte';
|
| 15 |
import { tokenModalState } from '$lib/state/token-modal.svelte';
|
|
|
|
| 19 |
import type { ChatModel } from '$lib/helpers/types';
|
| 20 |
import FitViewOnResize from '$lib/components/flow/FitViewOnResize.svelte';
|
| 21 |
import PanelRightActions from '$lib/components/flow/actions/PanelRightActions.svelte';
|
| 22 |
+
import { MAX_DEFAULT_MODELS } from '$lib';
|
| 23 |
+
import { breakpointsState } from '$lib/state/breakpoints.svelte';
|
| 24 |
|
| 25 |
const nodeTypes = {
|
| 26 |
user: User,
|
|
|
|
| 34 |
type: 'user',
|
| 35 |
position: { x: 0, y: 0 },
|
| 36 |
data: {
|
| 37 |
+
isFirstNode: true,
|
| 38 |
+
selectedModels: modelsState.models.slice(0, MAX_DEFAULT_MODELS) as ChatModel[]
|
| 39 |
}
|
| 40 |
}
|
| 41 |
];
|
|
|
|
| 67 |
proOptions={{ hideAttribution: true }}
|
| 68 |
fitViewOptions={{
|
| 69 |
maxZoom: 1,
|
| 70 |
+
minZoom: breakpointsState.isMobile ? 1 : 0.8,
|
| 71 |
interpolate: 'smooth',
|
| 72 |
duration: 500
|
| 73 |
}}
|
| 74 |
+
defaultEdgeOptions={{ type: 'smoothstep' }}
|
| 75 |
class="bg-background!"
|
| 76 |
>
|
| 77 |
<FitViewOnResize {initialNodes} />
|
|
|
|
| 86 |
patternColor={mode.current === 'dark' ? 'rgba(255, 255, 255, 0.04)' : 'rgba(0, 0, 0, 0.04)'}
|
| 87 |
class="bg-background!"
|
| 88 |
/>
|
| 89 |
+
<PanelRightActions canReset={nodes.length > 1} />
|
| 90 |
<Panel
|
| 91 |
position="bottom-left"
|
| 92 |
class="flex items-center justify-center gap-1.5 rounded-lg border border-border bg-background py-1.5 pr-3.5 pl-2.5 shadow-xs"
|
src/routes/api/+server.ts
CHANGED
|
@@ -4,9 +4,9 @@ import { InferenceClient } from '@huggingface/inference';
|
|
| 4 |
import { env } from '$env/dynamic/private';
|
| 5 |
|
| 6 |
export async function POST({ request }: RequestEvent) {
|
| 7 |
-
const { model,
|
| 8 |
|
| 9 |
-
if (!model
|
| 10 |
return json({ error: 'Model and prompt are required' }, { status: 400 });
|
| 11 |
}
|
| 12 |
|
|
@@ -22,8 +22,7 @@ export async function POST({ request }: RequestEvent) {
|
|
| 22 |
content:
|
| 23 |
"You are a helpful assistant. You are very helpful and friendly. Use markdown to format your responses, but don't include array start and end markers."
|
| 24 |
},
|
| 25 |
-
...(messages ?? [])
|
| 26 |
-
{ role: 'user', content: prompt }
|
| 27 |
]
|
| 28 |
},
|
| 29 |
{
|
|
|
|
| 4 |
import { env } from '$env/dynamic/private';
|
| 5 |
|
| 6 |
export async function POST({ request }: RequestEvent) {
|
| 7 |
+
const { model, messages, options, provider = 'auto' } = await request.json();
|
| 8 |
|
| 9 |
+
if (!model) {
|
| 10 |
return json({ error: 'Model and prompt are required' }, { status: 400 });
|
| 11 |
}
|
| 12 |
|
|
|
|
| 22 |
content:
|
| 23 |
"You are a helpful assistant. You are very helpful and friendly. Use markdown to format your responses, but don't include array start and end markers."
|
| 24 |
},
|
| 25 |
+
...(messages ?? [])
|
|
|
|
| 26 |
]
|
| 27 |
},
|
| 28 |
{
|