Skip to content
Merged

toast #123

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
129 changes: 96 additions & 33 deletions apps/desktop/src/components/toast/model.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useQuery } from "@tanstack/react-query";
import { Channel } from "@tauri-apps/api/core";
import { useEffect } from "react";
import { toast } from "sonner";
import { useEffect, useState } from "react";

import { commands as localLlmCommands } from "@hypr/plugin-local-llm";
import { commands as localSttCommands } from "@hypr/plugin-local-stt";
import { Progress } from "@hypr/ui/components/ui/progress";
import { toast } from "@hypr/ui/components/ui/toast";

export default function ModelDownloadNotification() {
const checkForModelDownload = useQuery({
Expand All @@ -27,41 +28,103 @@ export default function ModelDownloadNotification() {
const sttChannel = new Channel();
const llmChannel = new Channel();

toast.custom(
(id) => (
<div className="flex flex-col gap-2 p-4 bg-white border rounded-lg shadow-lg">
<div className="font-medium">Model Download Needed</div>
toast({
title: "Model Download Needed",
content: "Local models are required for offline functionality.",
buttons: [
{
label: "Download Models",
onClick: () => {
if (!checkForModelDownload.data?.stt) {
localSttCommands.downloadModel(sttChannel);

{!checkForModelDownload.data?.stt && (
<div>
<button onClick={() => localSttCommands.downloadModel(sttChannel)}>
Download STT Model
</button>
</div>
)}
toast(
{
title: "Speech-to-Text Model",
content: (
<div className="space-y-1">
<div>Downloading the speech-to-text model...</div>
<ModelDownloadProgress
channel={sttChannel}
onComplete={() => {
toast({
title: "Speech-to-Text Model",
content: "Download complete!",
dismissible: true,
});
}}
/>
</div>
),
dismissible: false,
},
);
}

{!checkForModelDownload.data?.llm && (
<div>
<button onClick={() => localLlmCommands.downloadModel(llmChannel)}>
Download LLM Model
</button>
</div>
)}
if (!checkForModelDownload.data?.llm) {
localLlmCommands.downloadModel(llmChannel);

<button
onClick={() => toast.dismiss(id)}
className="px-3 py-1.5 text-sm bg-neutral-200 text-neutral-800 rounded-md hover:bg-neutral-300"
>
Dismiss
</button>
</div>
),
{
id: "model-download-notification",
duration: Infinity,
},
);
toast(
{
title: "Large Language Model",
content: (
<div className="space-y-1">
<div>Downloading the large language model...</div>
<ModelDownloadProgress
channel={llmChannel}
onComplete={() => {
toast({
title: "Large Language Model",
content: "Download complete!",
dismissible: true,
});
}}
/>
</div>
),
dismissible: false,
},
);
}
},
primary: true,
},
],
dismissible: false,
});
}, [checkForModelDownload.data]);

return null;
}

interface ProgressPayload {
progress: number;
}

const ModelDownloadProgress = ({
channel,
onComplete,
}: {
channel: Channel<unknown>;
onComplete?: () => void;
}) => {
const [progress, setProgress] = useState(0);

useEffect(() => {
channel.onmessage = (response) => {
const data = response as unknown as ProgressPayload;
setProgress(data.progress);

if (data.progress >= 100 && onComplete) {
onComplete();
}
};
}, [channel, onComplete]);

return (
<div className="w-full space-y-2">
<Progress value={progress} className="h-2" />
<div className="text-xs text-right">{Math.round(progress)}%</div>
</div>
);
};
77 changes: 31 additions & 46 deletions apps/desktop/src/components/toast/ota.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { ask } from "@tauri-apps/plugin-dialog";
import { relaunch } from "@tauri-apps/plugin-process";
import { check } from "@tauri-apps/plugin-updater";
import { useEffect } from "react";
import { toast } from "sonner";

import { toast } from "@hypr/ui/components/ui/toast";

export default function OtaNotification() {
const checkForUpdate = useQuery({
Expand All @@ -29,52 +30,36 @@ export default function OtaNotification() {

const update = checkForUpdate.data;

toast.custom(
(id) => (
<div className="flex flex-col gap-2 p-4 bg-white border rounded-lg shadow-lg">
<div className="font-medium">Update Available</div>
<div className="text-sm text-neutral-600">
Version {update.version} is available to install
</div>
<div className="flex gap-2 mt-2">
<button
onClick={async () => {
const yes = await ask(
`
Update to ${update.version} is available!
Release notes: ${update.body}
`,
{
title: "Update Now!",
kind: "info",
okLabel: "Update",
cancelLabel: "Cancel",
},
);
toast({
title: "Update Available",
content: `Version ${update.version} is available to install`,
buttons: [
{
label: "Update Now",
onClick: async () => {
const yes = await ask(
`
Update to ${update.version} is available!
Release notes: ${update.body}
`,
{
title: "Update Now!",
kind: "info",
okLabel: "Update",
cancelLabel: "Cancel",
},
);

if (yes && process.env.NODE_ENV === "production") {
await update.downloadAndInstall();
await relaunch();
}
}}
className="px-3 py-1.5 text-sm bg-neutral-800 text-white rounded-md hover:bg-neutral-700"
>
Update Now
</button>
<button
onClick={() => toast.dismiss(id)}
className="px-3 py-1.5 text-sm bg-neutral-200 text-neutral-800 rounded-md hover:bg-neutral-300"
>
Dismiss
</button>
</div>
</div>
),
{
id: "update-notification",
duration: Infinity,
},
);
if (yes && process.env.NODE_ENV === "production") {
await update.downloadAndInstall();
await relaunch();
}
},
primary: true,
},
],
dismissible: true,
});
}, [checkForUpdate.data?.available]);

return null;
Expand Down
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-progress": "^1.1.2",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
Expand Down
28 changes: 28 additions & 0 deletions packages/ui/src/components/ui/progress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import * as ProgressPrimitive from "@radix-ui/react-progress";
import * as React from "react";

import { cn } from "@hypr/ui/lib/utils";

const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
className,
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;

export { Progress };
27 changes: 0 additions & 27 deletions packages/ui/src/components/ui/sonner.tsx

This file was deleted.

Loading
Loading