Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { commands as analyticsCommands } from "@hypr/plugin-analytics";
import { commands as windowsCommands } from "@hypr/plugin-windows";
import { Badge } from "@hypr/ui/components/ui/badge";
import { Trans } from "@lingui/react/macro";
import { memo, useCallback } from "react";

import { useHypr } from "@/contexts";

interface EmptyChatStateProps {
onQuickAction: (prompt: string) => void;
onFocusInput: () => void;
}

export const EmptyChatState = memo(({ onQuickAction, onFocusInput }: EmptyChatStateProps) => {
const { userId } = useHypr();

const handleContainerClick = useCallback(() => {
onFocusInput();
}, [onFocusInput]);

const handleButtonClick = useCallback((prompt: string) => (e: React.MouseEvent) => {
const handleButtonClick = useCallback((prompt: string, analyticsEvent: string) => (e: React.MouseEvent) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userId is referenced inside the useCallback but is missing from the dependency array, so the memoized handleButtonClick may use a stale userId value and send incorrect analytics data. (Based on your team's feedback about always including all hook dependencies to avoid stale closures.)

Prompt for AI agents
Address the following comment on apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx at line 21:

<comment>`userId` is referenced inside the `useCallback` but is missing from the dependency array, so the memoized `handleButtonClick` may use a stale `userId` value and send incorrect analytics data. (Based on your team&#39;s feedback about always including all hook dependencies to avoid stale closures.)</comment>

<file context>
@@ -1,19 +1,31 @@
+import { commands as analyticsCommands } from &quot;@hypr/plugin-analytics&quot;;
 import { commands as windowsCommands } from &quot;@hypr/plugin-windows&quot;;
 import { Badge } from &quot;@hypr/ui/components/ui/badge&quot;;
 import { Trans } from &quot;@lingui/react/macro&quot;;
 import { memo, useCallback } from &quot;react&quot;;
 
+import { useHypr } from &quot;@/contexts&quot;;
+
 interface EmptyChatStateProps {
</file context>

if (userId) {
analyticsCommands.event({
event: analyticsEvent,
distinct_id: userId,
});
}

onQuickAction(prompt);
}, [onQuickAction]);

Expand Down Expand Up @@ -55,28 +67,40 @@ export const EmptyChatState = memo(({ onQuickAction, onFocusInput }: EmptyChatSt

<div className="flex flex-wrap gap-2 justify-center mb-4 max-w-[280px]">
<button
onClick={handleButtonClick("Summarize this meeting")}
onClick={handleButtonClick("Make this meeting note more concise", "chat_shorten_summary")}
className="text-xs px-3 py-1 rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors"
>
<Trans>Summarize meeting</Trans>
<Trans>Shorten summary</Trans>
</button>
<button
onClick={handleButtonClick("Identify key decisions made in this meeting")}
onClick={handleButtonClick(
"Tell me the most important questions asked in this meeting and the answers",
"chat_important_qas",
)}
className="text-xs px-3 py-1 rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors"
>
<Trans>Key decisions</Trans>
<Trans>Important Q&As</Trans>
</button>
<button
onClick={handleButtonClick("Extract action items from this meeting")}
onClick={handleButtonClick("Extract action items from this meeting", "chat_extract_action_items")}
className="text-xs px-3 py-1 rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors"
>
<Trans>Extract action items</Trans>
</button>
<button
onClick={handleButtonClick("Create an agenda for next meeting")}
onClick={handleButtonClick("Create an agenda for next meeting", "chat_next_meeting_prep")}
className="text-xs px-3 py-1 rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors"
>
<Trans>Next meeting prep</Trans>
</button>
<button
onClick={handleButtonClick(
"Add more direct quotes from the transcript to the summary",
"chat_add_more_quotes",
)}
className="text-xs px-3 py-1 rounded-full bg-neutral-100 hover:bg-neutral-200 transition-colors"
>
<Trans>Create agenda</Trans>
<Trans>Add more quotes</Trans>
</button>
</div>
</div>
Expand Down
61 changes: 59 additions & 2 deletions apps/desktop/src/components/right-panel/hooks/useChatLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import { commands as connectorCommands } from "@hypr/plugin-connector";
import { commands as dbCommands } from "@hypr/plugin-db";
import { commands as miscCommands } from "@hypr/plugin-misc";
import { commands as templateCommands } from "@hypr/plugin-template";
import { modelProvider, streamText, tool } from "@hypr/utils/ai";
import { modelProvider, stepCountIs, streamText, tool } from "@hypr/utils/ai";
import { useSessions } from "@hypr/utils/contexts";
import { useQueryClient } from "@tanstack/react-query";
import { z } from "zod";

import type { ActiveEntityInfo, Message } from "../types/chat-types";
import { parseMarkdownBlocks } from "../utils/markdown-parser";

Expand Down Expand Up @@ -315,6 +314,64 @@ export function useChatLogic({
update_progress: tool({ inputSchema: z.any() }),
},
}),
...(type !== "HyprLocal" && {
stopWhen: stepCountIs(3),
tools: {
search_sessions_multi_keywords: tool({
description:
"Search for sessions (meeting notes) with multiple keywords. The keywords should be the most important things that the user is talking about. This could be either topics, people, or company names.",
inputSchema: z.object({
keywords: z.array(z.string()).min(3).max(5).describe(
"List of 3-5 keywords to search for, each keyword should be concise",
),
}),
execute: async ({ keywords }) => {
const searchPromises = keywords.map(keyword =>
dbCommands.listSessions({
type: "search",
query: keyword,
user_id: userId || "",
limit: 3,
})
);

const searchResults = await Promise.all(searchPromises);

const combinedResults = new Map();

searchResults.forEach((sessions, index) => {
const keyword = keywords[index];
sessions.forEach(session => {
if (combinedResults.has(session.id)) {
combinedResults.get(session.id).matchedKeywords.push(keyword);
} else {
combinedResults.set(session.id, {
...session,
matchedKeywords: [keyword],
});
}
});
});

const finalResults = Array.from(combinedResults.values())
.sort((a, b) => b.matchedKeywords.length - a.matchedKeywords.length);

return {
results: finalResults,
summary: {
totalSessions: finalResults.length,
keywordsSearched: keywords,
sessionsByKeywordCount: finalResults.reduce((acc, session) => {
const count = session.matchedKeywords.length;
acc[count] = (acc[count] || 0) + 1;
return acc;
}, {} as Record<number, number>),
},
};
},
}),
},
}),

onError: (error) => {
console.error("On Error Catch:", error);
Expand Down
114 changes: 113 additions & 1 deletion apps/desktop/src/components/settings/components/ai/stt-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";

// Add these imports for file operations
// import { message } from "@tauri-apps/plugin-dialog";
// import { writeFile } from "@tauri-apps/plugin-fs";

import { commands as dbCommands } from "@hypr/plugin-db";
import { commands as localSttCommands, SupportedModel } from "@hypr/plugin-local-stt";
import { Button } from "@hypr/ui/components/ui/button";
Expand Down Expand Up @@ -137,6 +141,10 @@ export function STTView({
}: STTViewProps) {
const queryClient = useQueryClient();

// Add drag and drop state
// const [isDragOver, setIsDragOver] = useState(false);
// const [isUploading, setIsUploading] = useState(false);

const currentSTTModel = useQuery({
queryKey: ["current-stt-model"],
queryFn: () => localSttCommands.getCurrentModel(),
Expand Down Expand Up @@ -243,6 +251,71 @@ export function STTView({
onError: console.error,
});

/*
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragOver(true);
}, []);

const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragOver(false);
}, []);

const handleFileDrop = useCallback(async (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragOver(false);

const file = e.dataTransfer.files[0];
if (!file) {
return;
}

// Validate file extension
const fileName = file.name.toLowerCase();
if (!fileName.endsWith(".bin") && !fileName.endsWith(".ggml")) {
await message(
"Please drop a valid STT model file (Comming Soon)",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in user-facing string: "Comming" should be "Coming".

Prompt for AI agents
Address the following comment on apps/desktop/src/components/settings/components/ai/stt-view.tsx at line 280:

<comment>Typo in user-facing string: &quot;Comming&quot; should be &quot;Coming&quot;.</comment>

<file context>
@@ -243,6 +251,69 @@ export function STTView({
     onError: console.error,
   });
 
+  const handleDragOver = useCallback((e: React.DragEvent) =&gt; {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragOver(true);
+  }, []);
+
</file context>
Suggested change
"Please drop a valid STT model file (Comming Soon)",
+ "Please drop a valid STT model file (Coming Soon)",

{ title: "Invalid File Type", kind: "error" },
);
return;
}

setIsUploading(true);
try {
// Get the STT models directory
const modelsDir = await localSttCommands.modelsDir();
const targetPath = `${modelsDir}/${file.name}`;

// Read the file content as array buffer
const fileContent = await file.arrayBuffer();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the whole model file into memory before writing can exhaust memory for large (>800 MB) models; use a streaming copy instead.

Prompt for AI agents
Address the following comment on apps/desktop/src/components/settings/components/ai/stt-view.tsx at line 293:

<comment>Reading the whole model file into memory before writing can exhaust memory for large (&gt;800 MB) models; use a streaming copy instead.</comment>

<file context>
@@ -243,6 +251,69 @@ export function STTView({
     onError: console.error,
   });
 
+  const handleDragOver = useCallback((e: React.DragEvent) =&gt; {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragOver(true);
+  }, []);
+
</file context>

const uint8Array = new Uint8Array(fileContent);

// Write the file to the models directory
await writeFile(targetPath, uint8Array);

await message(`Model file "${file.name}" copied successfully!`, {
title: "File Copied",
kind: "info",
});

// This invalidation will trigger the automatic refresh
queryClient.invalidateQueries({ queryKey: ["stt-model-download-status"] });
} catch (error) {
console.error("Error copying model file:", error);
await message(`Failed to copy model file: ${error instanceof Error ? error.message : String(error)}`, {
title: "Copy Failed",
kind: "error",
});
} finally {
setIsUploading(false);
}
}, [queryClient]);
*/

return (
<div className="space-y-6">
<div className="flex items-center gap-2">
Expand All @@ -262,6 +335,9 @@ export function STTView({
</div>

<div className="max-w-2xl">
<h3 className="text-sm font-semibold mb-3 text-gray-700">
Default
</h3>
<div className="space-y-2">
{modelsToShow.map((model) => (
<div
Expand Down Expand Up @@ -392,6 +468,42 @@ export function STTView({
</div>
</div>

{
/*
<div className="max-w-2xl">
<h3 className="text-sm font-semibold mb-3 text-gray-700">
Custom
</h3>
<div
className={cn(
"border-2 border-dashed rounded-lg p-8 text-center transition-colors",
isDragOver
? "border-blue-400 bg-blue-50"
: "border-gray-300 bg-gray-50 hover:border-gray-400",
isUploading && "opacity-50 pointer-events-none",
)}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleFileDrop}
>
{isUploading
? (
<div className="flex items-center justify-center gap-2">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600"></div>
<p className="text-gray-600 text-sm">
Copying model file...
</p>
</div>
)
: (
<p className="text-gray-500 text-sm">
Drag and drop your own STT mode file (.ggml or .bin format)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in helper text: "STT mode file" should be "STT model file".

Prompt for AI agents
Address the following comment on apps/desktop/src/components/settings/components/ai/stt-view.tsx at line 496:

<comment>Typo in helper text: &quot;STT mode file&quot; should be &quot;STT model file&quot;.</comment>

<file context>
@@ -392,6 +466,39 @@ export function STTView({
         &lt;/div&gt;
       &lt;/div&gt;
 
+      &lt;div className=&quot;max-w-2xl&quot;&gt;
+        &lt;h3 className=&quot;text-sm font-semibold mb-3 text-gray-700&quot;&gt;
+          Custom
+        &lt;/h3&gt;
+        &lt;div
+          className={cn(
</file context>
Suggested change
Drag and drop your own STT mode file (.ggml or .bin format)
+ Drag and drop your own STT model file (.ggml or .bin format)

</p>
)}
</div>
</div>
*/
}
<div className="max-w-2xl">
<div className="border rounded-lg p-4">
<h3 className="text-sm font-semibold mb-4">
Expand Down Expand Up @@ -421,7 +533,7 @@ export function STTView({
field.onChange(newValue);
aiConfigMutation.mutate({ redemptionTimeMs: newValue });
}}
className="w-[100%]"
className="w-[100%] [&>.relative>.absolute]:bg-gray-400 [&>.relative>span[data-state='active']]:bg-gray-300 [&>.relative>span]:border-gray-200"
/>
</div>
</FormControl>
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/components/settings/views/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const SUPPORTED_LANGUAGES: ISO_639_1_CODE[] = [
"ta",
"lv",
"az",
"he",
];

const schema = z.object({
Expand Down
Loading
Loading