-
Notifications
You must be signed in to change notification settings - Fork 2
Add model favorites #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
66546ce
03d3c80
76d48d8
2c264a7
a069899
d9a0881
0a1f6d8
15b72aa
16aed1b
e6b665e
a8832fa
fd4d38b
b67a0bb
1dab0fa
e99a697
087f075
d162131
9cab986
5d587f0
210b9b5
6a751a5
62370a3
78f0927
37aa350
f432c6b
ab3b2be
bc83f23
a909581
82ba1b2
cc49b53
138572b
7990520
28cd7be
1984ba5
d43386f
b6bf1fc
912d7ec
12f6be4
7d85dac
e747767
13b2839
b431e3f
878b598
49915c0
9a34f96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -114,18 +114,34 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ | |
| providerID: string | ||
| modelID: string | ||
| }[] | ||
| favorite: { | ||
| providerID: string | ||
| modelID: string | ||
| }[] | ||
| }>({ | ||
| ready: false, | ||
| model: {}, | ||
| recent: [], | ||
| favorite: [], | ||
| }) | ||
|
|
||
| const file = Bun.file(path.join(Global.Path.state, "model.json")) | ||
|
|
||
| function save() { | ||
| Bun.write( | ||
| file, | ||
| JSON.stringify({ | ||
| recent: modelStore.recent, | ||
| favorite: modelStore.favorite, | ||
| }), | ||
| ) | ||
| } | ||
|
|
||
| file | ||
| .json() | ||
| .then((x) => { | ||
| setModelStore("recent", x.recent) | ||
| if (Array.isArray(x.recent)) setModelStore("recent", x.recent) | ||
| if (Array.isArray(x.favorite)) setModelStore("favorite", x.favorite) | ||
| }) | ||
| .catch(() => {}) | ||
| .finally(() => { | ||
|
|
@@ -184,6 +200,9 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ | |
| recent() { | ||
| return modelStore.recent | ||
| }, | ||
| favorite() { | ||
| return modelStore.favorite | ||
| }, | ||
| parsed: createMemo(() => { | ||
| const value = currentModel() | ||
| const provider = sync.data.provider.find((x) => x.id === value.providerID)! | ||
|
|
@@ -206,6 +225,33 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ | |
| if (!val) return | ||
| setModelStore("model", agent.current().name, { ...val }) | ||
| }, | ||
| cycleFavorite(direction: 1 | -1) { | ||
| const favorites = modelStore.favorite.filter((item) => isModelValid(item)) | ||
| if (!favorites.length) { | ||
| toast.show({ | ||
| variant: "info", | ||
| message: "Add a favorite model to use this shortcut", | ||
| duration: 3000, | ||
| }) | ||
| return | ||
| } | ||
| const current = currentModel() | ||
| let index = favorites.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID) | ||
| if (index === -1) { | ||
| index = direction === 1 ? 0 : favorites.length - 1 | ||
| } else { | ||
| index += direction | ||
| if (index < 0) index = favorites.length - 1 | ||
| if (index >= favorites.length) index = 0 | ||
| } | ||
| const next = favorites[index] | ||
| if (!next) return | ||
| setModelStore("model", agent.current().name, { ...next }) | ||
| const uniq = uniqueBy([next, ...modelStore.recent], (x) => x.providerID + x.modelID) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using a delimiter in uniqueBy key generation. The uniqueBy predicate concatenates providerID and modelID without a delimiter. While unlikely, this could cause collisions (e.g., Apply this diff to use a delimiter: - const uniq = uniqueBy([next, ...modelStore.recent], (x) => x.providerID + x.modelID)
+ const uniq = uniqueBy([next, ...modelStore.recent], (x) => `${x.providerID}/${x.modelID}`)Also update line 266: - const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID)
+ const uniq = uniqueBy([model, ...modelStore.recent], (x) => `${x.providerID}/${x.modelID}`)Also applies to: 266-266 🤖 Prompt for AI Agents |
||
| if (uniq.length > 10) uniq.pop() | ||
| setModelStore("recent", uniq) | ||
| save() | ||
| }, | ||
| set(model: { providerID: string; modelID: string }, options?: { recent?: boolean }) { | ||
| batch(() => { | ||
| if (!isModelValid(model)) { | ||
|
|
@@ -219,15 +265,30 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ | |
| setModelStore("model", agent.current().name, model) | ||
| if (options?.recent) { | ||
| const uniq = uniqueBy([model, ...modelStore.recent], (x) => x.providerID + x.modelID) | ||
| if (uniq.length > 5) uniq.pop() | ||
| if (uniq.length > 10) uniq.pop() | ||
| setModelStore("recent", uniq) | ||
| Bun.write( | ||
| file, | ||
| JSON.stringify({ | ||
| recent: modelStore.recent, | ||
| }), | ||
| ) | ||
| save() | ||
| } | ||
| }) | ||
| }, | ||
| toggleFavorite(model: { providerID: string; modelID: string }) { | ||
| batch(() => { | ||
| if (!isModelValid(model)) { | ||
| toast.show({ | ||
| message: `Model ${model.providerID}/${model.modelID} is not valid`, | ||
| variant: "warning", | ||
| duration: 3000, | ||
| }) | ||
| return | ||
| } | ||
| const exists = modelStore.favorite.some( | ||
| (x) => x.providerID === model.providerID && x.modelID === model.modelID, | ||
| ) | ||
| const next = exists | ||
| ? modelStore.favorite.filter((x) => x.providerID !== model.providerID || x.modelID !== model.modelID) | ||
| : [model, ...modelStore.favorite] | ||
| setModelStore("favorite", next) | ||
| save() | ||
| }) | ||
| }, | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,4 +24,4 @@ | |
| "typescript": "catalog:", | ||
| "@typescript/native-preview": "catalog:" | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,4 +26,4 @@ | |
| "publishConfig": { | ||
| "directory": "dist" | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verify type safety of option.value.
The type assertion
option.value as { providerID: string; modelID: string }could be unsafe if the option's value doesn't match the expected shape.The
DialogSelectcomponent is generic (DialogSelect<T>), and while all options in this dialog appear to have the correct shape, the type assertion bypasses TypeScript's type checking. Consider one of these approaches:Option 1: Type the DialogSelect with a specific type
Then update the DialogSelect usage:
Option 2: Add runtime validation
{ keybind: Keybind.parse("ctrl+f")[0], title: "Favorite", onTrigger: (option) => { + const value = option.value as any + if (typeof value?.providerID === 'string' && typeof value?.modelID === 'string') { - local.model.toggleFavorite(option.value as { providerID: string; modelID: string }) + local.model.toggleFavorite(value) + } }, },🤖 Prompt for AI Agents