File size: 3,482 Bytes
9ab40fd
936176c
 
7357f85
9ab40fd
7357f85
 
9ab40fd
64cfbce
812d95a
9ab40fd
936176c
 
 
 
 
 
 
9ab40fd
b924465
 
 
 
9ab40fd
 
936176c
 
 
 
9ab40fd
 
 
 
 
 
936176c
 
 
 
 
 
9ab40fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ba9894c
 
 
 
 
9ab40fd
f5e7fbe
01e2d92
 
9ab40fd
64cfbce
 
 
 
 
9ab40fd
 
693ced9
01e2d92
b7dc03e
9ab40fd
 
 
 
 
 
b7dc03e
9ab40fd
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<script lang="ts">
	import { run } from 'svelte/legacy';

	import type { Conversation } from "$lib/types.js";

	import { randomPick } from "$lib/utils/array.js";
	import { cn } from "$lib/utils/cn.js";
	import { createSelect, createSync } from "@melt-ui/svelte";
	import IconCaret from "~icons/carbon/chevron-down";
	import IconProvider from "../icon-provider.svelte";

	interface Props {
		conversation: Conversation;
		class?: string | undefined;
	}

	let { conversation = $bindable(), class: classes = undefined }: Props = $props();
	

	function reset(providers: typeof conversation.model.inferenceProviderMapping) {
		const validProvider = providers.find(p => p.provider === conversation.provider);
		if (validProvider) return;
		conversation.provider = randomPick(providers)?.provider;
	}

	let providers = $derived(conversation.model.inferenceProviderMapping);
	run(() => {
		reset(providers);
	});

	const {
		elements: { trigger, menu, option },
		states: { selected },
	} = createSelect<string, false>();
	const sync = createSync({ selected });
	run(() => {
		sync.selected(
			conversation.provider ? { value: conversation.provider } : undefined,
			p => (conversation.provider = p?.value)
		);
	});

	const nameMap: Record<string, string> = {
		"sambanova": "SambaNova",
		"fal": "fal",
		"cerebras": "Cerebras",
		"replicate": "Replicate",
		"black-forest-labs": "Black Forest Labs",
		"fireworks-ai": "Fireworks",
		"together": "Together AI",
		"nebius": "Nebius AI Studio",
		"hyperbolic": "Hyperbolic",
		"novita": "Novita",
		"cohere": "Nohere",
		"hf-inference": "HF Inference API",
	};
	const UPPERCASE_WORDS = ["hf", "ai"];

	function formatName(provider: string) {
		if (provider in nameMap) return nameMap[provider];

		const words = provider
			.toLowerCase()
			.split("-")
			.map(word => {
				if (UPPERCASE_WORDS.includes(word)) {
					return word.toUpperCase();
				} else {
					return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
				}
			});

		return words.join(" ");
	}
</script>

<div class="flex flex-col gap-2">
	<!--
	<label class="flex items-baseline gap-2 text-sm font-medium text-gray-900 dark:text-white">
		Providers<span class="text-xs font-normal text-gray-400"></span>
	</label>
	-->

	<button
		{...$trigger}
		use:trigger
		class={cn(
			"relative flex items-center justify-between gap-6 overflow-hidden rounded-lg border bg-gray-100/80 px-3 py-1.5 leading-tight whitespace-nowrap shadow-sm",
			"hover:brightness-95 dark:border-gray-700 dark:bg-gray-800 dark:hover:brightness-110",
			classes
		)}
	>
		<div class="flex items-center gap-1 text-sm">
			<IconProvider provider={conversation.provider} />
			{formatName(conversation.provider ?? "") ?? "loading"}
		</div>
		<div
			class="absolute right-2 grid size-4 flex-none place-items-center rounded-sm bg-gray-100 text-xs dark:bg-gray-600"
		>
			<IconCaret />
		</div>
	</button>

	<div {...$menu} use:menu class="rounded-lg border bg-gray-100 dark:border-gray-700 dark:bg-gray-800">
		{#each conversation.model.inferenceProviderMapping as { provider, providerId } (provider + providerId)}
			<button {...$option({ value: provider })} use:option class="group block w-full p-1 text-sm dark:text-white">
				<div
					class="flex items-center gap-2 rounded-md px-2 py-1.5 group-data-[highlighted]:bg-gray-200 dark:group-data-[highlighted]:bg-gray-700"
				>
					<IconProvider {provider} />
					{formatName(provider)}
				</div>
			</button>
		{/each}
	</div>
</div>