Skip to content

Commit 60b75f5

Browse files
feat!(app)[lm]: add LM model features like text summarization, rephrase and configs to settings
1 parent 97fab78 commit 60b75f5

11 files changed

Lines changed: 615 additions & 173 deletions

File tree

apps/app/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
"devDependencies": {
1616
"@0x-jerry/unocss-preset-daisyui": "0.2.3",
1717
"@iconify-json/famicons": "^1.2.2",
18+
"@iconify-json/healthicons": "^1.2.12",
1819
"@iconify-json/line-md": "^1.2.14",
20+
"@iconify-json/ri": "^1.2.9",
1921
"@sveltejs/vite-plugin-svelte": "^6.2.4",
20-
"@iconify-json/healthicons": "^1.2.12",
2122
"@tauri-apps/cli": "^2.9.6",
2223
"@testing-library/svelte": "^5.3.1",
2324
"@tsconfig/svelte": "^5.0.6",
@@ -35,6 +36,10 @@
3536
"vitest": "^4.0.18"
3637
},
3738
"dependencies": {
39+
"@ai-sdk/anthropic": "^3.0.36",
40+
"@ai-sdk/google": "^3.0.21",
41+
"@ai-sdk/openai": "^3.0.25",
42+
"@ai-sdk/provider": "^3.0.7",
3843
"@iconify-json/carbon": "^1.2.18",
3944
"@iconify-json/tabler": "^1.2.26",
4045
"@saurl/tauri-plugin-safe-area-insets-css-api": "^0.1.0",
@@ -49,6 +54,8 @@
4954
"@unocss/preset-mini": "^66.6.0",
5055
"@unocss/reset": "^66.6.0",
5156
"@unocss/transformer-variant-group": "^66.6.0",
57+
"ai": "^6.0.70",
58+
"bits-ui": "^2.15.5",
5259
"daisyui": "5.4.7",
5360
"html2canvas-pro": "^1.6.6",
5461
"jspdf": "^3.0.4",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script lang="ts">
2+
import LMConfig from './lm_config/index.svelte';
3+
</script>
4+
5+
<div class="mt-7">
6+
<p
7+
class="text-sm color-[color-mix(in_srgb,var(--color-base-content)_70%,black)] py-5 pl-6"
8+
>
9+
Language Model
10+
</p>
11+
<ul class=" bg-base-100 rounded-box">
12+
<li><LMConfig /></li>
13+
<li class="divider p-0 m-0 h-min px-6"></li>
14+
</ul>
15+
</div>
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<script lang="ts">
2+
import { Select } from 'bits-ui';
3+
import ListModels from './list_models.svelte';
4+
import { apply_ai_config } from './onclick';
5+
let model_providers = [
6+
{
7+
value: 'openai',
8+
label: 'Open AI',
9+
icon: 'i-ri:openai-fill',
10+
models_docs_link: '',
11+
},
12+
{ value: 'google', label: 'Google', icon: 'i-ri:google-fill' },
13+
{ value: 'anthropic', label: 'Anthropic', icon: 'i-ri:anthropic-fill' },
14+
];
15+
let sel_provider = $state<string>();
16+
let api_key = $state<string>();
17+
let model_id = $state<string>();
18+
19+
const selected_provider_label = $derived(
20+
model_providers.find((model) => model.value === sel_provider)?.label
21+
);
22+
let models_list_dialog_open = $state(false);
23+
</script>
24+
25+
<div class="p-6 flex flex-col gap-3">
26+
<div class="flex justify-between items-center">
27+
<p>Select Model Provider</p>
28+
<Select.Root
29+
type="single"
30+
items={model_providers}
31+
onValueChange={(v) => (sel_provider = v)}
32+
allowDeselect={true}
33+
>
34+
<Select.Trigger class="input">
35+
{selected_provider_label || 'Select a Model'}
36+
</Select.Trigger>
37+
<Select.Portal>
38+
<Select.Content>
39+
<Select.ScrollUpButton>
40+
<div class="i-tabler:chevrons-up size-4"></div>
41+
</Select.ScrollUpButton>
42+
<Select.Viewport class="bg-base-100 p-2 w-60 rounded-box ">
43+
{#each model_providers as model}
44+
<!-- content here -->
45+
<Select.Item
46+
value={model.value}
47+
class="flex hover:bg-gray/10 transition-all items-center rounded-field p-2 h-8 w-full"
48+
>
49+
{#snippet children({ selected })}
50+
<span class="flex gap-3 items-center">
51+
<div class={model.icon}></div>
52+
{model.label}</span
53+
>
54+
{#if selected}
55+
<div class="ml-auto">
56+
<div class="i-tabler:check size-4"></div>
57+
</div>
58+
{/if}
59+
{/snippet}
60+
</Select.Item>
61+
{/each}
62+
<Select.ScrollDownButton>
63+
<div class="i-tabler:chevrons-down size-4"></div>
64+
</Select.ScrollDownButton>
65+
</Select.Viewport>
66+
</Select.Content>
67+
</Select.Portal>
68+
</Select.Root>
69+
</div>
70+
<div class="flex justify-between items-center">
71+
<p>
72+
Enter <span class="badge badge-md {selected_provider_label || 'hidden'} "
73+
>{selected_provider_label}
74+
</span> API Key
75+
</p>
76+
77+
<label class="input">
78+
<div class="i-tabler:key size-5"></div>
79+
<input
80+
type="password"
81+
required
82+
placeholder="API Key"
83+
title="Must be more than 8 characters, including number, lowercase letter, uppercase letter"
84+
bind:value={api_key}
85+
disabled={selected_provider_label == undefined}
86+
/>
87+
</label>
88+
</div>
89+
<div class="flex justify-between items-center">
90+
<div class="">
91+
<p>
92+
Enter <span
93+
class="badge badge-md {selected_provider_label || 'hidden'} "
94+
>{selected_provider_label}
95+
</span> Model name
96+
</p>
97+
<span class="text-base-content/50 text-sm">
98+
click <button
99+
class="link link-primary"
100+
onclick={() => {
101+
models_list_dialog_open = true;
102+
}}
103+
>
104+
here</button
105+
> to see available models
106+
</span>
107+
{#if models_list_dialog_open}
108+
<ListModels
109+
model_provider={sel_provider}
110+
bind:models_list_dialog_open
111+
bind:model_name={model_id}
112+
/>
113+
{/if}
114+
</div>
115+
116+
<label class="input">
117+
<div class="i-carbon:network-1 size-5"></div>
118+
<input
119+
type="text"
120+
required
121+
bind:value={model_id}
122+
placeholder="Model name"
123+
title="Must be more than 8 characters, including number, lowercase letter, uppercase letter"
124+
disabled={!selected_provider_label || !api_key}
125+
/>
126+
</label>
127+
</div>
128+
<button
129+
class="btn btn-primary self-end"
130+
disabled={!sel_provider || !api_key || !model_id}
131+
onclick={() => {
132+
if (sel_provider && api_key && model_id)
133+
apply_ai_config(sel_provider, api_key, model_id);
134+
}}>Apply</button
135+
>
136+
</div>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<script lang="ts">
2+
import { z } from 'zod';
3+
4+
let {
5+
model_provider,
6+
models_list_dialog_open = $bindable(),
7+
model_name = $bindable(),
8+
}: {
9+
model_provider: string | undefined;
10+
models_list_dialog_open: boolean;
11+
model_name: string | undefined;
12+
} = $props();
13+
14+
const ApiSchema = z.object({
15+
data: z.array(
16+
z.object({
17+
id: z.string(),
18+
owned_by: z.string(),
19+
})
20+
),
21+
});
22+
23+
let models = $state<Array<{ id: string; owned_by: string }>>();
24+
let fetch_failed = $state(false);
25+
26+
fetch('https://ai-gateway.vercel.sh/v1/models')
27+
.then((res) => res.json())
28+
.then((raw) => {
29+
const result = ApiSchema.safeParse(raw);
30+
if (result.success) {
31+
models = result.data.data.filter(
32+
(model) => model.owned_by == model_provider
33+
);
34+
} else {
35+
fetch_failed = true;
36+
}
37+
})
38+
.catch((e) => (fetch_failed = true));
39+
</script>
40+
41+
<dialog class="modal" open={models_list_dialog_open}>
42+
<div class="modal-box">
43+
<h3 class="font-bold text-lg mb-4">Select Model</h3>
44+
45+
{#if model_provider}
46+
<!-- content here -->
47+
<ul class="menu flex-nowrap w-full max-h-96 overflow-y-auto">
48+
{#each models as model (model.id)}
49+
{@const model_id_stripped = model.id.split('/')[1]}
50+
<li>
51+
<button
52+
onclick={() => {
53+
models_list_dialog_open = false;
54+
model_name = model_id_stripped;
55+
}}>{model_id_stripped}</button
56+
>
57+
</li>
58+
{:else}
59+
{#if fetch_failed}
60+
Failed to Load model data. <br />
61+
please mannualy find the model name from your Model Provider's Docs
62+
{:else if models && !models.length}
63+
No models found for {model_provider}
64+
{:else}
65+
Loading...
66+
{/if}
67+
{/each}
68+
</ul>
69+
{:else}
70+
Please select a Model Provider first
71+
{/if}
72+
<button
73+
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
74+
onclick={() => (models_list_dialog_open = false)}>✕</button
75+
>
76+
</div>
77+
</dialog>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { model } from '@/lib/features/nlp/states.svelte';
2+
import { createGoogleGenerativeAI } from '@ai-sdk/google';
3+
import { createOpenAI } from '@ai-sdk/openai';
4+
import { createAnthropic } from '@ai-sdk/anthropic';
5+
6+
export function apply_ai_config(
7+
provider: string,
8+
api_key: string,
9+
model_id: string
10+
) {
11+
switch (provider) {
12+
case 'google':
13+
const google = createGoogleGenerativeAI({ apiKey: api_key });
14+
model.data = google(model_id);
15+
break;
16+
case 'openai':
17+
const openai = createOpenAI({ apiKey: api_key });
18+
model.data = openai(model_id);
19+
break;
20+
case 'anthropic':
21+
const anthropic = createAnthropic({ apiKey: api_key });
22+
model.data = anthropic(model_id);
23+
break;
24+
25+
default:
26+
break;
27+
}
28+
}
Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,70 @@
11
<script lang="ts">
22
import { current_platform_type } from '@/lib/file_system';
33
import { app_settings_dialog_open_state } from './states.svelte';
4+
import Editor from './editor/index.svelte';
5+
import type { Component } from 'svelte';
6+
import { sidebar_items } from './sidebar_items';
7+
import { fade } from 'svelte/transition';
8+
9+
const viewMap: Record<string, Component> = {
10+
editor: Editor,
11+
};
12+
let activeTab = $state('editor');
13+
let ActiveView: Component = $derived(viewMap[activeTab]);
414
</script>
515

6-
<dialog open={app_settings_dialog_open_state.data} class="modal z-11">
7-
<div
8-
class="
16+
{#if app_settings_dialog_open_state.data}
17+
<dialog open={app_settings_dialog_open_state.data} class="modal z-11">
18+
<div
19+
out:fade
20+
class="
921
{current_platform_type == 'desktop' && ' w-80% h-85% lt-sm:flex-col'}
1022
{current_platform_type == 'mobile' && 'size-100% lt-sm:flex-col-reverse '}
1123
modal-box p-0 flex max-w-275 b-1 b-[color-mix(in_srgb,var(--color-base-content)_32%,black)]"
12-
>
13-
<div
14-
class="max-w-63 w-full h-full b-r-1 b-r-[color-mix(in_srgb,var(--color-base-content)_22%,black)]"
15-
></div>
16-
<form method="dialog">
17-
<button
18-
class="btn btn-sm btn-circle btn-ghost absolute
19-
{current_platform_type == 'mobile'
20-
? 'top-12 right-4'
21-
: 'top-2 right-2'} "
22-
onclick={() => (app_settings_dialog_open_state.data = false)}
24+
>
25+
<aside
26+
class="max-w-63 w-full h-full b-r-1 b-r-[color-mix(in_srgb,var(--color-base-content)_22%,black)]"
2327
>
24-
25-
</button>
26-
</form>
27-
</div>
28-
</dialog>
28+
<ul class="w-full gap-2">
29+
{#each sidebar_items as category_item}
30+
<li>
31+
<p
32+
class="px-5 mt-8 text-xs capitalize color-[color-mix(in_srgb,var(--color-base-content)_52%,black)]"
33+
>
34+
{category_item.category_label}
35+
</p>
36+
<ul class="menu menu-sm w-full">
37+
{#each category_item.items as item}
38+
<li>
39+
<button
40+
class=" {activeTab == item.id && 'bg-base-content/10 '}"
41+
>
42+
<div class=" size-4 {item.icon}"></div>
43+
44+
<p class="text-3.5">{item.label}</p>
45+
</button>
46+
</li>
47+
{/each}
48+
</ul>
49+
</li>
50+
{/each}
51+
</ul>
52+
</aside>
53+
<div class="size-full px-12 bg-base-200"><ActiveView /></div>
54+
55+
<div class="modal-action">
56+
<form method="dialog">
57+
<button
58+
class="btn btn-sm btn-circle btn-ghost absolute
59+
{current_platform_type == 'mobile'
60+
? 'top-12 right-4'
61+
: 'top-2 right-2'} "
62+
onclick={() => (app_settings_dialog_open_state.data = false)}
63+
>
64+
65+
</button>
66+
</form>
67+
</div>
68+
</div>
69+
</dialog>
70+
{/if}

0 commit comments

Comments
 (0)