Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ temp/
.worktrees/
.e2e-runtime/
.e2e-tools/
.vite-cache/
**/.vite-cache/
.cargo-temp/
rust-target/
rust-temp/
Expand Down
37 changes: 36 additions & 1 deletion apps/desktop/src/components/AlertMessage.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
<!-- Copyright (c) 2025-2026. Qian Cheng. Licensed under GPL v3 -->

<script lang="ts">
type AlertSonnerOwnerListener = () => void;

let sonnerOwnerUid: number | null = null;
const alertSonnerOwnerListeners = new Set<AlertSonnerOwnerListener>();

/**
* Coordinates the single Sonner host shared by all alert components.
*
* Vue runs this ownership protocol on the browser's single JavaScript thread,
* so checking and assigning the owner is atomic enough without locks. When the
* owning component unmounts it clears the owner and notifies the remaining
* subscribers; the first still-mounted instance that refreshes claims the host.
* If no instances remain, the owner stays empty until another AlertMessage
* mounts and claims it.
*/
const notifyAlertSonnerOwnerChanged = (): void => {
for (const listener of [...alertSonnerOwnerListeners]) {
listener();
}
};

const claimAlertSonnerOwner = (uid: number): boolean => {
if (sonnerOwnerUid === null) {
Expand All @@ -15,8 +34,16 @@
const releaseAlertSonnerOwner = (uid: number): void => {
if (sonnerOwnerUid === uid) {
sonnerOwnerUid = null;
notifyAlertSonnerOwnerChanged();
}
};

const subscribeAlertSonnerOwner = (listener: AlertSonnerOwnerListener): (() => void) => {
alertSonnerOwnerListeners.add(listener);
return () => {
alertSonnerOwnerListeners.delete(listener);
};
};
</script>

<script setup lang="ts">
Expand All @@ -35,12 +62,20 @@

const isSonnerOwner = ref(false);
const instanceUid = getCurrentInstance()?.uid;
let unsubscribeAlertSonnerOwner: (() => void) | undefined;

if (instanceUid !== undefined) {
const refreshSonnerOwner = () => {
if (instanceUid === undefined) return;
isSonnerOwner.value = claimAlertSonnerOwner(instanceUid);
};

if (instanceUid !== undefined) {
refreshSonnerOwner();
unsubscribeAlertSonnerOwner = subscribeAlertSonnerOwner(refreshSonnerOwner);
}

onUnmounted(() => {
unsubscribeAlertSonnerOwner?.();
if (instanceUid !== undefined && isSonnerOwner.value) {
releaseAlertSonnerOwner(instanceUid);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/DialogShell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<template>
<Dialog :open="true" @update:open="onOpenChange">
<DialogContent
:class="`w-full ${maxWidthClass} rounded-xl border border-gray-200 bg-white p-6 shadow-xl ${contentClass}`"
:class="`w-full ${maxWidthClass} rounded-xl border border-gray-200 bg-white p-6 font-serif text-[13px] shadow-xl ${contentClass}`"
:overlay-class="'fixed inset-0 z-50 bg-black/40 backdrop-blur-sm'"
@escape-key-down="preventWhenLocked"
@pointer-down-outside="preventWhenLocked"
Expand Down
8 changes: 6 additions & 2 deletions apps/desktop/src/components/appIconMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import IconArrowDown from '~icons/lucide/arrow-down';
import IconArrowLeft from '~icons/lucide/arrow-left';
import IconArrowRight from '~icons/lucide/arrow-right';
import IconBlocks from '~icons/lucide/blocks';
import IconBoxes from '~icons/lucide/boxes';
import IconBrain from '~icons/lucide/brain-circuit';
import IconBriefcase from '~icons/lucide/briefcase';
import IconBug from '~icons/lucide/bug';
import IconChevronDown from '~icons/lucide/chevron-down';
import IconChevronRight from '~icons/lucide/chevron-right';
import IconChevronUp from '~icons/lucide/chevron-up';
import IconCheckCircle from '~icons/lucide/circle-check';
import IconXCircle from '~icons/lucide/circle-x';
import IconCloud from '~icons/lucide/cloud';
import IconCopy from '~icons/lucide/copy';
import IconData from '~icons/lucide/database';
import IconLinkExternal from '~icons/lucide/external-link';
Expand All @@ -36,6 +37,7 @@ import IconPin from '~icons/lucide/pin';
import IconPlay from '~icons/lucide/play';
import IconPlus from '~icons/lucide/plus';
import IconRefresh from '~icons/lucide/refresh-cw';
import IconRoute from '~icons/lucide/route';
import IconSearch from '~icons/lucide/search';
import IconCog from '~icons/lucide/settings';
import IconStop from '~icons/lucide/square';
Expand All @@ -53,6 +55,7 @@ export const appIconMap = {
'chevron-right': IconChevronRight,
'chevron-up': IconChevronUp,
close: IconX,
cloud: IconCloud,
copy: IconCopy,
database: IconData,
delete: IconTrash,
Expand All @@ -74,7 +77,7 @@ export const appIconMap = {
maximize: IconMaximize,
restore: IconWindowRestore,
'information-circle': IconInfoCircle,
llm: IconBoxes,
llm: IconBrain,
leaf: IconLeaf,
'external-link': IconLinkExternal,
mcp: IconBlocks,
Expand All @@ -83,6 +86,7 @@ export const appIconMap = {
play: IconPlay,
plus: IconPlus,
refresh: IconRefresh,
route: IconRoute,
search: IconSearch,
settings: IconCog,
stop: IconStop,
Expand Down
20 changes: 20 additions & 0 deletions apps/desktop/src/database/drizzle/0004_model_preferences.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
CREATE TABLE IF NOT EXISTS `model_preferences` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`description` text NOT NULL,
`provider_id` integer,
`model_id` integer,
`priority` integer DEFAULT 0 NOT NULL,
`created_at` text DEFAULT (datetime('now')) NOT NULL,
`updated_at` text DEFAULT (datetime('now')) NOT NULL,
FOREIGN KEY (`provider_id`) REFERENCES `providers`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`model_id`) REFERENCES `models`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS `model_preferences_priority_idx` ON `model_preferences` (`priority`);
--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS `model_preferences_name_unique` ON `model_preferences` (`name`);
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS `model_preferences_model_id_idx` ON `model_preferences` (`model_id`);
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS `model_preferences_provider_id_idx` ON `model_preferences` (`provider_id`);
9 changes: 8 additions & 1 deletion apps/desktop/src/database/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
"when": 1781021002699,
"tag": "0003_tough_amazoness",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1780051200000,
"tag": "0004_model_preferences",
"breakpoints": true
}
]
}
}
Loading
Loading