Skip to content

Commit 2d85eb5

Browse files
committed
chores
1 parent 803a8e5 commit 2d85eb5

File tree

5 files changed

+187
-47
lines changed

5 files changed

+187
-47
lines changed

apps/desktop/src/components/chat/input.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ import * as main from "../../store/tinybase/main";
1717
export function ChatMessageInput({
1818
onSendMessage,
1919
disabled: disabledProp,
20+
attachedSession,
2021
}: {
2122
onSendMessage: (content: string, parts: any[]) => void;
2223
disabled?: boolean | { disabled: boolean; message?: string };
24+
attachedSession?: { id: string; title?: string };
2325
}) {
2426
const editorRef = useRef<{ editor: TiptapEditor | null }>(null);
2527
const chatShortcuts = main.UI.useResultTable(
@@ -110,6 +112,11 @@ export function ChatMessageInput({
110112

111113
return (
112114
<Container>
115+
{attachedSession && (
116+
<div className="px-3 pt-2 text-xs text-neutral-500 truncate">
117+
Attached: {attachedSession.title || "Untitled"}
118+
</div>
119+
)}
113120
<div className="flex flex-col p-2">
114121
<div className="flex-1 mb-2">
115122
<ChatEditor

apps/desktop/src/components/chat/session.tsx

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import type { ChatMessage, ChatMessageStorage } from "@hypr/store";
88
import { CustomChatTransport } from "../../chat/transport";
99
import type { HyprUIMessage } from "../../chat/types";
1010
import { useToolRegistry } from "../../contexts/tool";
11+
import { useSession } from "../../hooks/tinybase";
1112
import { useLanguageModel } from "../../hooks/useLLMConnection";
1213
import * as main from "../../store/tinybase/main";
1314
import { id } from "../../utils";
1415

1516
interface ChatSessionProps {
1617
sessionId: string;
1718
chatGroupId?: string;
19+
attachedSessionId?: string;
1820
children: (props: {
1921
messages: HyprUIMessage[];
2022
sendMessage: (message: HyprUIMessage) => void;
@@ -28,9 +30,10 @@ interface ChatSessionProps {
2830
export function ChatSession({
2931
sessionId,
3032
chatGroupId,
33+
attachedSessionId,
3134
children,
3235
}: ChatSessionProps) {
33-
const transport = useTransport();
36+
const transport = useTransport(attachedSessionId);
3437
const store = main.UI.useStore(main.STORE_ID);
3538

3639
const { user_id } = main.UI.useValues(main.STORE_ID);
@@ -161,22 +164,89 @@ export function ChatSession({
161164
);
162165
}
163166

164-
function useTransport() {
167+
function useTransport(attachedSessionId?: string) {
165168
const registry = useToolRegistry();
166169
const model = useLanguageModel();
170+
const store = main.UI.useStore(main.STORE_ID);
167171
const language = main.UI.useValue("ai_language", main.STORE_ID) ?? "en";
168172
const [systemPrompt, setSystemPrompt] = useState<string | undefined>();
169173

174+
const { title, rawMd, enhancedMd, createdAt } = useSession(
175+
attachedSessionId ?? "",
176+
);
177+
178+
const transcriptIds = main.UI.useSliceRowIds(
179+
main.INDEXES.transcriptBySession,
180+
attachedSessionId ?? "",
181+
main.STORE_ID,
182+
);
183+
const firstTranscriptId = transcriptIds?.[0];
184+
185+
const wordIds = main.UI.useSliceRowIds(
186+
main.INDEXES.wordsByTranscript,
187+
firstTranscriptId ?? "",
188+
main.STORE_ID,
189+
);
190+
191+
const words = useMemo(() => {
192+
if (!store || !wordIds || wordIds.length === 0) {
193+
return [];
194+
}
195+
196+
const result: {
197+
text: string;
198+
start_ms: number;
199+
end_ms: number;
200+
channel: number;
201+
speaker?: string;
202+
}[] = [];
203+
204+
for (const wordId of wordIds) {
205+
const row = store.getRow("words", wordId);
206+
if (row) {
207+
result.push({
208+
text: row.text as string,
209+
start_ms: row.start_ms as number,
210+
end_ms: row.end_ms as number,
211+
channel: row.channel as number,
212+
speaker: row.speaker as string | undefined,
213+
});
214+
}
215+
}
216+
217+
return result.sort((a, b) => a.start_ms - b.start_ms);
218+
}, [store, wordIds]);
219+
220+
const sessionContext = useMemo(() => {
221+
if (!attachedSessionId) {
222+
return null;
223+
}
224+
225+
return {
226+
session: true,
227+
title: title as string | undefined,
228+
rawContent: rawMd as string | undefined,
229+
enhancedContent: enhancedMd as string | undefined,
230+
date: createdAt as string | undefined,
231+
words: words.length > 0 ? words : undefined,
232+
};
233+
}, [attachedSessionId, title, rawMd, enhancedMd, createdAt, words]);
234+
170235
useEffect(() => {
236+
const templateParams = {
237+
language,
238+
...(sessionContext ?? {}),
239+
};
240+
171241
templateCommands
172-
.render("chat.system", { language })
242+
.render("chat.system", templateParams)
173243
.then((result) => {
174244
if (result.status === "ok") {
175245
setSystemPrompt(result.data);
176246
}
177247
})
178248
.catch(console.error);
179-
}, [language]);
249+
}, [language, sessionContext]);
180250

181251
const transport = useMemo(() => {
182252
if (!model) {

apps/desktop/src/components/chat/view.tsx

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { useCallback, useRef } from "react";
1+
import { useCallback, useMemo, useRef } from "react";
22

33
import type { HyprUIMessage } from "../../chat/types";
44
import { useShell } from "../../contexts/shell";
5+
import { useSession } from "../../hooks/tinybase";
56
import { useLanguageModel } from "../../hooks/useLLMConnection";
67
import * as main from "../../store/tinybase/main";
8+
import { useTabs } from "../../store/zustand/tabs";
79
import { id } from "../../utils";
810
import { ChatBody } from "./body";
911
import { ChatHeader } from "./header";
@@ -13,6 +15,10 @@ import { ChatSession } from "./session";
1315
export function ChatView() {
1416
const { chat } = useShell();
1517
const { groupId, setGroupId } = chat;
18+
const { currentTab } = useTabs();
19+
20+
const attachedSessionId =
21+
currentTab?.type === "sessions" ? currentTab.id : undefined;
1622

1723
const stableSessionId = useStableSessionId(groupId);
1824
const model = useLanguageModel();
@@ -121,30 +127,79 @@ export function ChatView() {
121127
key={stableSessionId}
122128
sessionId={stableSessionId}
123129
chatGroupId={groupId}
130+
attachedSessionId={attachedSessionId}
124131
>
125132
{({ messages, sendMessage, regenerate, stop, status, error }) => (
126-
<>
127-
<ChatBody
128-
messages={messages}
129-
status={status}
130-
error={error}
131-
onReload={regenerate}
132-
onStop={stop}
133-
isModelConfigured={!!model}
134-
/>
135-
<ChatMessageInput
136-
disabled={!model || status !== "ready"}
137-
onSendMessage={(content, parts) =>
138-
handleSendMessage(content, parts, sendMessage)
139-
}
140-
/>
141-
</>
133+
<ChatViewContent
134+
messages={messages}
135+
sendMessage={sendMessage}
136+
regenerate={regenerate}
137+
stop={stop}
138+
status={status}
139+
error={error}
140+
model={model}
141+
handleSendMessage={handleSendMessage}
142+
attachedSessionId={attachedSessionId}
143+
/>
142144
)}
143145
</ChatSession>
144146
</div>
145147
);
146148
}
147149

150+
function ChatViewContent({
151+
messages,
152+
sendMessage,
153+
regenerate,
154+
stop,
155+
status,
156+
error,
157+
model,
158+
handleSendMessage,
159+
attachedSessionId,
160+
}: {
161+
messages: HyprUIMessage[];
162+
sendMessage: (message: HyprUIMessage) => void;
163+
regenerate: () => void;
164+
stop: () => void;
165+
status: "submitted" | "streaming" | "ready" | "error";
166+
error?: Error;
167+
model: ReturnType<typeof useLanguageModel>;
168+
handleSendMessage: (
169+
content: string,
170+
parts: any[],
171+
sendMessage: (message: HyprUIMessage) => void,
172+
) => void;
173+
attachedSessionId?: string;
174+
}) {
175+
const { title } = useSession(attachedSessionId ?? "");
176+
177+
const attachedSession = useMemo(() => {
178+
if (!attachedSessionId) return undefined;
179+
return { id: attachedSessionId, title: (title as string) || undefined };
180+
}, [attachedSessionId, title]);
181+
182+
return (
183+
<>
184+
<ChatBody
185+
messages={messages}
186+
status={status}
187+
error={error}
188+
onReload={regenerate}
189+
onStop={stop}
190+
isModelConfigured={!!model}
191+
/>
192+
<ChatMessageInput
193+
disabled={!model || status !== "ready"}
194+
onSendMessage={(content, parts) =>
195+
handleSendMessage(content, parts, sendMessage)
196+
}
197+
attachedSession={attachedSession}
198+
/>
199+
</>
200+
);
201+
}
202+
148203
function useStableSessionId(groupId: string | undefined) {
149204
const sessionIdRef = useRef<string | null>(null);
150205
const lastGroupIdRef = useRef<string | undefined>(groupId);

apps/desktop/src/components/settings/ai/stt/shared.tsx

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type Provider = {
2121
requiresPro?: boolean;
2222
};
2323

24-
export type ProviderId = (typeof PROVIDERS)[number]["id"];
24+
export type ProviderId = (typeof _PROVIDERS)[number]["id"];
2525

2626
export const displayModelId = (model: string) => {
2727
if (model === "cloud") {
@@ -78,7 +78,7 @@ export const displayModelId = (model: string) => {
7878
return model;
7979
};
8080

81-
export const PROVIDERS = [
81+
const _PROVIDERS = [
8282
{
8383
disabled: false,
8484
id: "hyprnote",
@@ -96,6 +96,16 @@ export const PROVIDERS = [
9696
],
9797
requiresPro: false,
9898
},
99+
{
100+
disabled: false,
101+
id: "assemblyai",
102+
displayName: "AssemblyAI",
103+
badge: "Beta",
104+
icon: <AssemblyAI size={12} />,
105+
baseUrl: "https://api.assemblyai.com",
106+
models: ["universal"],
107+
requiresPro: false,
108+
},
99109
{
100110
disabled: false,
101111
id: "deepgram",
@@ -120,28 +130,6 @@ export const PROVIDERS = [
120130
],
121131
requiresPro: false,
122132
},
123-
{
124-
disabled: false,
125-
id: "soniox",
126-
displayName: "Soniox",
127-
badge: "Beta",
128-
icon: (
129-
<img src="/assets/soniox.jpeg" alt="Soniox" className="size-5 rounded" />
130-
),
131-
baseUrl: "https://api.soniox.com",
132-
models: ["stt-v3"],
133-
requiresPro: false,
134-
},
135-
{
136-
disabled: false,
137-
id: "assemblyai",
138-
displayName: "AssemblyAI",
139-
badge: "Beta",
140-
icon: <AssemblyAI size={12} />,
141-
baseUrl: "https://api.assemblyai.com",
142-
models: ["universal"],
143-
requiresPro: false,
144-
},
145133
{
146134
disabled: false,
147135
id: "gladia",
@@ -164,6 +152,18 @@ export const PROVIDERS = [
164152
models: ["gpt-4o-transcribe", "gpt-4o-mini-transcribe", "whisper-1"],
165153
requiresPro: false,
166154
},
155+
{
156+
disabled: false,
157+
id: "soniox",
158+
displayName: "Soniox",
159+
badge: "Beta",
160+
icon: (
161+
<img src="/assets/soniox.jpeg" alt="Soniox" className="size-5 rounded" />
162+
),
163+
baseUrl: "https://api.soniox.com",
164+
models: ["stt-v3"],
165+
requiresPro: false,
166+
},
167167
{
168168
disabled: false,
169169
id: "custom",
@@ -186,6 +186,16 @@ export const PROVIDERS = [
186186
},
187187
] as const satisfies readonly Provider[];
188188

189+
export const PROVIDERS = [..._PROVIDERS].sort((a, b) => {
190+
if (a.id === "hyprnote") return -1;
191+
if (b.id === "hyprnote") return 1;
192+
if (a.disabled && !b.disabled) return 1;
193+
if (!a.disabled && b.disabled) return -1;
194+
if (a.id === "custom") return 1;
195+
if (b.id === "custom") return -1;
196+
return a.displayName.localeCompare(b.displayName);
197+
});
198+
189199
export const sttProviderRequiresPro = (providerId: ProviderId) =>
190200
PROVIDERS.find((provider) => provider.id === providerId)?.requiresPro ??
191201
false;

apps/web/content-collections.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@ const changelog = defineCollection({
119119
});
120120

121121
const version = document._meta.path.replace(/\.mdx$/, "");
122-
const tag = `desktop_v${version}`;
123-
124122
const isNightly = version.includes("-nightly");
125123
const channel = isNightly ? "nightly" : "stable";
126124

0 commit comments

Comments
 (0)