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
11 changes: 9 additions & 2 deletions apps/desktop/src/chat/context/support-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AccountInfo } from "@hypr/plugin-auth";
import { commands as authCommands } from "@hypr/plugin-auth";
import type { DeviceInfo } from "@hypr/plugin-misc";
import { commands as miscCommands } from "@hypr/plugin-misc";
import type { ModelInfo } from "@hypr/plugin-template";
import { commands as templateCommands } from "@hypr/plugin-template";

import type { ContextEntity } from "./entities";
Expand Down Expand Up @@ -30,7 +31,9 @@ async function getDeviceInfo(): Promise<DeviceInfo | null> {
return null;
}

export async function collectSupportContextBlock(): Promise<{
export async function collectSupportContextBlock(
modelInfo?: ModelInfo | null,
): Promise<{
entities: ContextEntity[];
block: string | null;
}> {
Expand All @@ -54,7 +57,11 @@ export async function collectSupportContextBlock(): Promise<{
}

const result = await templateCommands.renderSupport({
supportContext: { account: accountInfo, device: deviceInfo },
supportContext: {
account: accountInfo,
device: deviceInfo,
models: modelInfo ?? null,
},
});

return {
Expand Down
39 changes: 38 additions & 1 deletion apps/desktop/src/chat/mcp/useSupportMCP.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,51 @@
import { useCallback } from "react";

import type { ModelInfo } from "@hypr/plugin-template";

import { useMCP } from "./useMCP";

import { collectSupportContextBlock } from "~/chat/context/support-block";
import { useConfigValues } from "~/shared/config";

export function useSupportMCP(enabled: boolean, accessToken?: string | null) {
const {
current_llm_provider,
current_llm_model,
current_stt_provider,
current_stt_model,
} = useConfigValues([
"current_llm_provider",
"current_llm_model",
"current_stt_provider",
"current_stt_model",
] as const);

const modelInfo: ModelInfo | null =
current_llm_provider || current_stt_provider
? {
llmProvider: current_llm_provider ?? null,
llmModel: current_llm_model ?? null,
sttProvider: current_stt_provider ?? null,
sttModel: current_stt_model ?? null,
}
: null;

const collectContext = useCallback(
() => collectSupportContextBlock(modelInfo),
[
current_llm_provider,
current_llm_model,
current_stt_provider,
current_stt_model,
],
);

return useMCP({
enabled,
endpoint: "/support/mcp",
clientName: "hyprnote-support-client",
accessToken,
promptName: "support_chat",
collectContext: collectSupportContextBlock,
collectContext,
});
}
1 change: 1 addition & 0 deletions crates/template-support/assets/support_chat.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Follow this workflow for bug reports and feature requests:
2. Call `search_issues` with relevant keywords to check for existing issues.
3. If a matching open issue exists, call `add_comment` with the user's additional context.
4. If no match exists, call `create_issue` with a clear title, structured body, and appropriate labels.
5. Always include the user's AI model configuration (LLM and STT provider/model) from the context in the issue body when available.

For billing questions:

Expand Down
19 changes: 19 additions & 0 deletions crates/template-support/assets/support_context.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,22 @@ The following is automatically collected context about the current user and thei
{%- if let Some(locale) = device.locale %}
- Locale: {{ locale }}
{%- endif %}
{%- if let Some(models) = models %}
{%- if models.llm_provider.is_some() || models.stt_provider.is_some() %}
- AI Models:
{%- if let Some(llm_provider) = models.llm_provider %}
{%- if let Some(llm_model) = models.llm_model %}
- LLM: {{ llm_provider }}/{{ llm_model }}
{%- else %}
- LLM: {{ llm_provider }}
{%- endif %}
{%- endif %}
{%- if let Some(stt_provider) = models.stt_provider %}
{%- if let Some(stt_model) = models.stt_model %}
- STT: {{ stt_provider }}/{{ stt_model }}
{%- else %}
- STT: {{ stt_provider }}
{%- endif %}
{%- endif %}
{%- endif %}
{%- endif %}
17 changes: 17 additions & 0 deletions crates/template-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ pub struct DeviceInfo {
pub locale: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ModelInfo {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub llm_provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub llm_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stt_provider: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stt_model: Option<String>,
}

#[derive(askama::Template)]
#[template(path = "bug_report.md.jinja", escape = "none")]
struct BugReportBody<'a> {
Expand Down Expand Up @@ -59,6 +72,7 @@ struct SupportChatPrompt;
struct SupportContextBlock<'a> {
account: Option<&'a AccountInfo>,
device: &'a DeviceInfo,
models: Option<&'a ModelInfo>,
}

#[derive(Clone, serde::Deserialize, serde::Serialize, specta::Type)]
Expand All @@ -75,6 +89,8 @@ pub enum SupportTemplate {
pub struct SupportContext {
pub account: Option<AccountInfo>,
pub device: DeviceInfo,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub models: Option<ModelInfo>,
}

#[derive(Clone, serde::Deserialize, serde::Serialize, specta::Type)]
Expand Down Expand Up @@ -111,6 +127,7 @@ pub fn render(t: SupportTemplate) -> Result<String, askama::Error> {
SupportTemplate::SupportContext(t) => askama::Template::render(&SupportContextBlock {
account: t.account.as_ref(),
device: &t.device,
models: t.models.as_ref(),
}),
SupportTemplate::BugReport(t) => askama::Template::render(&BugReportBody {
description: &t.description,
Expand Down
3 changes: 2 additions & 1 deletion plugins/template/js/bindings.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ export type FeatureRequest = { description: string; platform: string; arch: stri
export type Grammar = { task: "enhance"; sections: string[] | null } | { task: "title" } | { task: "tags" } | { task: "email-to-name" }
export type JsonValue = null | boolean | number | string | JsonValue[] | Partial<{ [key in string]: JsonValue }>
export type LogAnalysis = { summarySection: string; tail: string }
export type ModelInfo = { llmProvider?: string | null; llmModel?: string | null; sttProvider?: string | null; sttModel?: string | null }
export type Participant = { name: string; jobTitle: string | null }
export type Segment = { text: string; speaker: string }
export type Session = { title: string | null; startedAt: string | null; endedAt: string | null; event: Event | null }
export type SessionContext = { title: string | null; date: string | null; rawContent: string | null; enhancedContent: string | null; transcript: Transcript | null; participants: Participant[]; event: Event | null }
export type SupportContext = { account: AccountInfo | null; device: DeviceInfo }
export type SupportContext = { account: AccountInfo | null; device: DeviceInfo; models?: ModelInfo | null }
export type SupportTemplate = { supportContext: SupportContext } | { bugReport: BugReport } | { featureRequest: FeatureRequest } | { logAnalysis: LogAnalysis }
export type Template = { enhanceSystem: EnhanceSystem } | { enhanceUser: EnhanceUser } | { titleSystem: TitleSystem } | { titleUser: TitleUser } | { chatSystem: ChatSystem } | { contextBlock: ContextBlock } | { toolSearchSessions: ToolSearchSessions }
export type TemplateSection = { title: string; description: string | null }
Expand Down
3 changes: 1 addition & 2 deletions plugins/tray/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,14 @@ impl<'a, M: tauri::Manager<tauri::Wry>> Tray<'a, tauri::Wry, M> {
Ok(())
}

pub fn set_title(&self, title: Option<&str>) -> Result<()> {
pub fn set_title(&self, title: Option<&str>) -> Result<()> {
let app = self.manager.app_handle();
if let Some(tray) = app.tray_by_id(TRAY_ID) {
tray.set_title(title)?;
}
Ok(())
}


pub fn set_recording(&self, recording: bool) -> Result<()> {
IS_RECORDING.store(recording, Ordering::SeqCst);
Self::refresh_icon(self.manager.app_handle())
Expand Down
Loading