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
@@ -0,0 +1,70 @@
import { ArrowRightIcon } from "lucide-react";

import { commands as windowsCommands } from "@hypr/plugin-windows";
import { Button } from "@hypr/ui/components/ui/button";

import type { LLMConnectionStatus } from "../../../../../../hooks/useLLMConnection";

export function ConfigError({ status }: { status: LLMConnectionStatus }) {
const handleConfigureClick = () => {
windowsCommands
.windowShow({ type: "settings" })
.then(() => new Promise((resolve) => setTimeout(resolve, 1000)))
.then(() =>
windowsCommands.windowEmitNavigate(
{ type: "settings" },
{
path: "/app/settings",
search: { tab: "intelligence" },
},
),
);
};

const message = getMessageForStatus(status);

return (
<div className="flex flex-col items-center justify-center h-full min-h-[400px]">
<p className="text-sm text-center text-neutral-700 mb-6 max-w-lg">
{message}
</p>
<Button
onClick={handleConfigureClick}
className="flex items-center gap-2"
variant="default"
>
<span>Configure</span>
<ArrowRightIcon size={16} />
</Button>
</div>
);
}

function getMessageForStatus(status: LLMConnectionStatus): string {
if (status.status === "pending" && status.reason === "missing_provider") {
return "You need to configure a language model to summarize this meeting";
}

if (status.status === "pending" && status.reason === "missing_model") {
return "You need to select a model to summarize this meeting";
}

if (status.status === "error" && status.reason === "unauthenticated") {
return "You need to sign in to use Hyprnote's language model";
}

if (status.status === "error" && status.reason === "missing_config") {
const missing = status.missing;
if (missing.includes("api_key") && missing.includes("base_url")) {
return "You need to configure the API key and base URL for your language model provider";
}
if (missing.includes("api_key")) {
return "You need to configure the API key for your language model provider";
}
if (missing.includes("base_url")) {
return "You need to configure the base URL for your language model provider";
}
}

return "You need to configure a language model to summarize this meeting";
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { forwardRef } from "react";
import { type TiptapEditor } from "@hypr/tiptap/editor";

import { useAITaskTask } from "../../../../../../hooks/useAITaskTask";
import { useLLMConnectionStatus } from "../../../../../../hooks/useLLMConnection";
import { createTaskId } from "../../../../../../store/zustand/ai-task/task-configs";
import { ConfigError } from "./config-error";
import { EnhancedEditor } from "./editor";
import { StreamingView } from "./streaming";

Expand All @@ -12,9 +14,19 @@ export const Enhanced = forwardRef<
{ sessionId: string; enhancedNoteId: string }
>(({ sessionId, enhancedNoteId }, ref) => {
const taskId = createTaskId(enhancedNoteId, "enhance");

const llmStatus = useLLMConnectionStatus();
const { status } = useAITaskTask(taskId, "enhance");

const isConfigError =
llmStatus.status === "pending" ||
(llmStatus.status === "error" &&
(llmStatus.reason === "missing_config" ||
llmStatus.reason === "unauthenticated"));

if (status === "idle" && isConfigError) {
return <ConfigError status={llmStatus} />;
}

if (status === "error") {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { cn } from "@hypr/utils";
import { useListener } from "../../../../../contexts/listener";
import { useAITaskTask } from "../../../../../hooks/useAITaskTask";
import { useCreateEnhancedNote } from "../../../../../hooks/useEnhancedNotes";
import { useLanguageModel } from "../../../../../hooks/useLLMConnection";
import {
useLanguageModel,
useLLMConnectionStatus,
} from "../../../../../hooks/useLLMConnection";
import * as main from "../../../../../store/tinybase/main";
import { createTaskId } from "../../../../../store/zustand/ai-task/task-configs";
import { type EditorView } from "../../../../../store/zustand/tabs/schema";
Expand Down Expand Up @@ -409,6 +412,7 @@ function labelForEditorView(view: EditorView): string {

function useEnhanceLogic(sessionId: string, enhancedNoteId: string) {
const model = useLanguageModel();
const llmStatus = useLLMConnectionStatus();
const taskId = createTaskId(enhancedNoteId, "enhance");
const [missingModelError, setMissingModelError] = useState<Error | null>(
null,
Expand Down Expand Up @@ -460,8 +464,17 @@ function useEnhanceLogic(sessionId: string, enhancedNoteId: string) {
}
}, [model, missingModelError]);

const isConfigError =
llmStatus.status === "pending" ||
(llmStatus.status === "error" &&
(llmStatus.reason === "missing_config" ||
llmStatus.reason === "unauthenticated"));

const isIdleWithConfigError = enhanceTask.isIdle && isConfigError;

const error = missingModelError ?? enhanceTask.error;
const isError = !!missingModelError || enhanceTask.isError;
const isError =
!!missingModelError || enhanceTask.isError || isIdleWithConfigError;

return {
isGenerating: enhanceTask.isGenerating,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import type { TiptapEditor } from "@hypr/tiptap/editor";
Expand Down Expand Up @@ -28,9 +28,15 @@ export function NoteInput({
useAutoEnhance(tab);
useAutoTitle(tab);

const handleTabChange = (view: EditorView) => {
updateSessionTabState(tab, { editor: view });
};
const tabRef = useRef(tab);
tabRef.current = tab;

const handleTabChange = useCallback(
(view: EditorView) => {
updateSessionTabState(tabRef.current, { editor: view });
},
[updateSessionTabState],
);

const currentTab: EditorView = useCurrentNoteTab(tab);

Expand Down
15 changes: 5 additions & 10 deletions apps/desktop/src/hooks/useAutoEnhance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export function useAutoEnhance(tab: Extract<Tab, { type: "sessions" }>) {
);

const startedTasksRef = useRef<Set<string>>(new Set());
const tabRef = useRef(tab);
tabRef.current = tab;

const enhanceTaskId = autoEnhancedNoteId
? createTaskId(autoEnhancedNoteId, "enhance")
Expand All @@ -54,7 +56,7 @@ export function useAutoEnhance(tab: Extract<Tab, { type: "sessions" }>) {
});

const createAndStartEnhance = useCallback(() => {
if (!model || !hasTranscript) {
if (!hasTranscript) {
return;
}

Expand All @@ -63,17 +65,10 @@ export function useAutoEnhance(tab: Extract<Tab, { type: "sessions" }>) {

setAutoEnhancedNoteId(enhancedNoteId);

updateSessionTabState(tab, {
updateSessionTabState(tabRef.current, {
editor: { type: "enhanced", id: enhancedNoteId },
});
}, [
hasTranscript,
model,
sessionId,
tab,
updateSessionTabState,
createEnhancedNote,
]);
}, [hasTranscript, sessionId, updateSessionTabState, createEnhancedNote]);

useEffect(() => {
if (
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src/hooks/useLLMConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type LLMConnectionInfo = {
apiKey: string;
};

type LLMConnectionStatus =
export type LLMConnectionStatus =
| { status: "pending"; reason: "missing_provider" }
| { status: "pending"; reason: "missing_model"; providerId: ProviderId }
| { status: "error"; reason: "provider_not_found"; providerId: string }
Expand Down Expand Up @@ -240,6 +240,11 @@ export const useLLMConnection = (): LLMConnectionResult => {
}, [auth, current_llm_model, current_llm_provider, providerConfig]);
};

export const useLLMConnectionStatus = (): LLMConnectionStatus => {
const { status } = useLLMConnection();
return status;
};

const wrapWithThinkingMiddleware = (model: Exclude<LanguageModel, string>) => {
return wrapLanguageModel({
model,
Expand Down
12 changes: 8 additions & 4 deletions apps/desktop/src/hooks/useRunBatch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback } from "react";
import { useCallback, useRef } from "react";

import type { BatchParams } from "@hypr/plugin-listener";

Expand Down Expand Up @@ -34,6 +34,9 @@ export const useRunBatch = (sessionId: string) => {
});
const updateSessionTabState = useTabs((state) => state.updateSessionTabState);

const sessionTabRef = useRef(sessionTab);
sessionTabRef.current = sessionTab;

const { conn } = useSTTConnection();
const keywords = useKeywords(sessionId);
const languages = useConfigValue("spoken_languages");
Expand Down Expand Up @@ -62,8 +65,10 @@ export const useRunBatch = (sessionId: string) => {
return;
}

if (sessionTab) {
updateSessionTabState(sessionTab, { editor: { type: "transcript" } });
if (sessionTabRef.current) {
updateSessionTabState(sessionTabRef.current, {
editor: { type: "transcript" },
});
}

const transcriptId = id();
Expand Down Expand Up @@ -150,7 +155,6 @@ export const useRunBatch = (sessionId: string) => {
languages,
runBatch,
sessionId,
sessionTab,
store,
updateSessionTabState,
user_id,
Expand Down
Loading