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
1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@hypr/utils": "workspace:^",
"@lingui/core": "^5.4.1",
"@lingui/react": "^5.4.1",
"@modelcontextprotocol/sdk": "^1.17.3",
"@mux/mux-player-react": "^3.5.3",
"@mux/mux-video": "^0.26.1",
"@radix-ui/react-dialog": "^1.1.15",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const EmptyChatState = memo(({ onQuickAction, onFocusInput }: EmptyChatSt
const handleCustomEndpointsClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
windowsCommands.windowShow({ type: "settings" }).then(() => {
windowsCommands.windowNavigate({ type: "settings" }, "/app/settings?tab=ai");
windowsCommands.windowNavigate({ type: "settings" }, "/app/settings?tab=ai-llm");
});
}, []);

Expand Down
47 changes: 46 additions & 1 deletion apps/desktop/src/components/right-panel/hooks/useChatLogic.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { message } from "@tauri-apps/plugin-dialog";
import { useCallback, useEffect, useRef, useState } from "react";

Expand All @@ -7,6 +9,7 @@ import { commands as connectorCommands } from "@hypr/plugin-connector";
import { commands as dbCommands } from "@hypr/plugin-db";
import { commands as mcpCommands } from "@hypr/plugin-mcp";
import { commands as miscCommands } from "@hypr/plugin-misc";
import { fetch as tauriFetch } from "@hypr/utils";
import {
dynamicTool,
experimental_createMCPClient,
Expand All @@ -17,10 +20,12 @@ import {
} from "@hypr/utils/ai";
import { useSessions } from "@hypr/utils/contexts";
import { useQueryClient } from "@tanstack/react-query";
import { getLicenseKey } from "tauri-plugin-keygen-api";
import { z } from "zod";
import type { ActiveEntityInfo, Message } from "../types/chat-types";
import { prepareMessageHistory } from "../utils/chat-utils";
import { parseMarkdownBlocks } from "../utils/markdown-parser";
import { buildVercelToolsFromMcp } from "../utils/mcp-http-wrapper";

interface UseChatLogicProps {
sessionId: string | null;
Expand Down Expand Up @@ -171,8 +176,10 @@ export function useChatLogic({
const apiBase = llmConnection.connection?.api_base;

let newMcpTools: Record<string, any> = {};
let hyprMcpTools: Record<string, any> = {};
let mcpToolsArray: any[] = [];
const allMcpClients: any[] = [];
let hyprMcpClient: Client | null = null;

const shouldUseTools = type !== "HyprLocal"
&& (model.modelId === "gpt-4.1" || model.modelId === "openai/gpt-4.1"
Expand All @@ -184,6 +191,34 @@ export function useChatLogic({
const mcpServers = await mcpCommands.getServers();
const enabledSevers = mcpServers.filter((server) => server.enabled);

if (apiBase?.includes("pro.hyprnote.com") && getLicense.data?.valid) {
try {
const licenseKey = await getLicenseKey();

const transport = new StreamableHTTPClientTransport(
new URL("https://pro.hyprnote.com/mcp"),
{
fetch: tauriFetch,
requestInit: {
headers: {
"x-hyprnote-license-key": licenseKey || "",
},
},
},
);
hyprMcpClient = new Client({
name: "hyprmcp",
version: "0.1.0",
});

await hyprMcpClient.connect(transport);

hyprMcpTools = await buildVercelToolsFromMcp(hyprMcpClient);
} catch (error) {
console.error("Error creating and adding hyprmcp client:", error);
}
}

for (const server of enabledSevers) {
try {
const mcpClient = await experimental_createMCPClient({
Expand Down Expand Up @@ -225,6 +260,14 @@ export function useChatLogic({
inputSchema: tool.inputSchema || "No input schema provided",
}))
: [];

for (const [toolKey, tool] of Object.entries(hyprMcpTools)) {
mcpToolsArray.push({
name: toolKey,
description: tool.description || `Tool: ${tool.name}`,
inputSchema: tool.inputSchema || "No input schema provided",
});
}
}

const searchTool = tool({
Expand Down Expand Up @@ -300,7 +343,7 @@ export function useChatLogic({
stopWhen: stepCountIs(3),
tools: {
...(type === "HyprLocal" && { update_progress: tool({ inputSchema: z.any() }) }),
...(shouldUseTools && { ...newMcpTools, search_sessions_multi_keywords: searchTool }),
...(shouldUseTools && { ...newMcpTools, search_sessions_multi_keywords: searchTool, ...hyprMcpTools }),
},
onError: (error) => {
console.error("On Error Catch:", error);
Expand All @@ -312,6 +355,8 @@ export function useChatLogic({
for (const client of allMcpClients) {
client.close();
}
// close hyprmcp client
hyprMcpClient?.close();
},
abortSignal: abortController.signal,
});
Expand Down
31 changes: 31 additions & 0 deletions apps/desktop/src/components/right-panel/utils/mcp-http-wrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { dynamicTool, jsonSchema } from "@hypr/utils/ai";
import { Client } from "@modelcontextprotocol/sdk/client";
import { z } from "zod";

export async function buildVercelToolsFromMcp(client: Client) {
const { tools: mcpTools } = await client.listTools();

const vercelTools: Record<string, ReturnType<typeof dynamicTool>> = {};

for (const mcpTool of mcpTools) {
const schema = mcpTool.inputSchema
? jsonSchema(mcpTool.inputSchema as any)
: z.any();

vercelTools[mcpTool.name] = dynamicTool({
description: mcpTool.description || (mcpTool as any).title || `Tool: ${mcpTool.name}`,
inputSchema: schema,

execute: async (args: unknown) => {
const result = await client.callTool({
name: mcpTool.name,
arguments: (args ?? undefined) as Record<string, unknown> | undefined,
});

return result.content;
},
});
}

return vercelTools;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useMatch } from "@tanstack/react-router";
import { writeText as writeTextToClipboard } from "@tauri-apps/plugin-clipboard-manager";
import { AudioLinesIcon, CheckIcon, ClipboardIcon, CopyIcon, TextSearchIcon, UploadIcon } from "lucide-react";
import {
AudioLinesIcon,
CheckIcon,
ClipboardIcon,
CopyIcon,
PencilIcon,
TextSearchIcon,
UploadIcon,
} from "lucide-react";
import { memo, useCallback, useEffect, useRef, useState } from "react";

import { ParticipantsChipInner } from "@/components/editor-area/note-header/chips/participants-chip";
Expand Down Expand Up @@ -123,6 +131,12 @@ export function TranscriptView() {
}
}, [sessionId]);

const handleFocusEditor = useCallback(() => {
if (editorRef.current?.editor) {
editorRef.current.editor.commands.focus();
}
}, []);

const handleUpdate = (words: Word2[]) => {
if (!isLive) {
dbCommands.getSession({ id: sessionId! }).then((session) => {
Expand Down Expand Up @@ -167,6 +181,16 @@ export function TranscriptView() {
</div>
)}
<div className="not-draggable flex items-center ">
{showActions && (
<Button
className="w-8 h-8"
variant="ghost"
size="icon"
onClick={handleFocusEditor}
>
<PencilIcon size={12} className="text-neutral-600" />
</Button>
)}
{showActions && (
<Button
className="w-8 h-8"
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export {
experimental_createMCPClient,
generateObject,
generateText,
jsonSchema,
type Provider,
smoothStream,
stepCountIs,
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading