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 .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ reviews:
- "!**/*.gen.ts"
- "!plugins/**/permissions/**"
- "!**/README*"
- "!**/.cursor/**"
path_instructions:
- path: "**/*.{js,ts,tsx,rs}"
instructions: |
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop2/src/components/chat/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useChat } from "@ai-sdk/react";
import type { UIMessage } from "ai";
import { type ReactNode, useEffect, useMemo, useRef, useState } from "react";

import * as internal from "../../store/tinybase/internal";
import * as persisted from "../../store/tinybase/persisted";
import { CustomChatTransport } from "../../transport";
import { id } from "../../utils";
Expand All @@ -25,7 +26,7 @@ export function ChatSession({
const [transport] = useState(() => new CustomChatTransport());
const store = persisted.UI.useStore(persisted.STORE_ID);

const { user_id } = persisted.useConfig();
const { user_id } = internal.UI.useValues(internal.STORE_ID);

const createChatMessage = persisted.UI.useSetRowCallback(
"chat_messages",
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop2/src/components/chat/view.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { UIMessage } from "ai";
import { useCallback, useEffect, useRef, useState } from "react";

import * as internal from "../../store/tinybase/internal";
import type { ChatMessage, ChatMessageStorage } from "../../store/tinybase/persisted";
import * as persisted from "../../store/tinybase/persisted";
import { id } from "../../utils";
Expand Down Expand Up @@ -31,7 +32,7 @@ export function ChatView({
chatGroupIdRef.current = chatGroupId;
}, [chatGroupId]);

const { user_id } = persisted.useConfig();
const { user_id } = internal.UI.useValues(internal.STORE_ID);

const createChatGroup = persisted.UI.useSetRowCallback(
"chat_groups",
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop2/src/components/human.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useValidatedRow } from "../hooks/useValidatedRow";
import { useSafeObjectUpdate } from "../hooks/useSafeObjectUpdate";
import * as persisted from "../store/tinybase/persisted";

export function Human({ id }: { id: string }) {
Expand All @@ -12,7 +12,7 @@ export function Human({ id }: { id: string }) {
persisted.STORE_ID,
);

const { setField, errors } = useValidatedRow(persisted.humanSchema, human, handleUpdate);
const { setField, errors } = useSafeObjectUpdate(persisted.humanSchema, human, handleUpdate);

return (
<div className="space-y-4 p-4">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as internal from "../../../../../store/tinybase/internal";
import * as persisted from "../../../../../store/tinybase/persisted";

import { SessionEvent } from "./event";
Expand All @@ -16,9 +17,7 @@ export function OuterHeader(
isAudioPlayerVisible: boolean;
},
) {
const currentUserId = persisted.UI.useCell("configs", "singleton", "user_id", persisted.STORE_ID) as
| string
| undefined;
const { user_id } = internal.UI.useValues(internal.STORE_ID);

return (
<div className="flex items-center justify-between">
Expand All @@ -38,7 +37,7 @@ export function OuterHeader(
/>
<SessionParticipants
sessionId={sessionId}
currentUserId={currentUserId}
currentUserId={user_id}
/>
<RecordingButton
sessionRow={sessionRow}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useCallback, useMemo, useState } from "react";

import * as internal from "../../../../../store/tinybase/internal";
import * as persisted from "../../../../../store/tinybase/persisted";

import { Participant, ParticipantGroup, ParticipantsChip } from "@hypr/ui/components/block/participants-chip";
import { useQuery } from "../../../../../hooks/useQuery";
import * as persisted from "../../../../../store/tinybase/persisted";

export function SessionParticipants({
sessionId,
Expand All @@ -12,6 +14,7 @@ export function SessionParticipants({
currentUserId: string | undefined;
}) {
const [participantSearchQuery, setParticipantSearchQuery] = useState("");
const { user_id } = internal.UI.useValues(internal.STORE_ID);

const store = persisted.UI.useStore(persisted.STORE_ID);
const indexes = persisted.UI.useIndexes(persisted.STORE_ID);
Expand Down Expand Up @@ -123,10 +126,9 @@ export function SessionParticipants({
}

const mappingId = crypto.randomUUID();
const userId = store.getCell("configs", "singleton", "user_id") as string;

store.setRow("mapping_session_participant", mappingId, {
user_id: userId,
user_id,
session_id: sessionId,
human_id: participantId,
created_at: new Date().toISOString(),
Expand All @@ -141,7 +143,6 @@ export function SessionParticipants({
}

const humanId = crypto.randomUUID();
const userId = store.getCell("configs", "singleton", "user_id") as string;

let orgId: string | undefined;

Expand All @@ -160,14 +161,14 @@ export function SessionParticipants({
if (!orgId) {
orgId = crypto.randomUUID();
store.setRow("organizations", orgId, {
user_id: userId,
user_id,
name: "No organization",
created_at: new Date().toISOString(),
});
}

store.setRow("humans", humanId, {
user_id: userId,
user_id,
name: query,
email: "",
org_id: orgId,
Expand All @@ -176,14 +177,14 @@ export function SessionParticipants({

const mappingId = crypto.randomUUID();
store.setRow("mapping_session_participant", mappingId, {
user_id: userId,
user_id,
session_id: sessionId,
human_id: humanId,
created_at: new Date().toISOString(),
});

setParticipantSearchQuery("");
}, [store, sessionId]);
}, [store, sessionId, user_id]);

return (
<ParticipantsChip
Expand Down
177 changes: 177 additions & 0 deletions apps/desktop2/src/components/settings/ai.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { useState } from "react";

import { Button } from "@hypr/ui/components/ui/button";
import { Input } from "@hypr/ui/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@hypr/ui/components/ui/tabs";
import { cn } from "@hypr/ui/lib/utils";

export function SettingsAI() {
const [activeTab, setActiveTab] = useState<"transcription" | "intelligence">("transcription");

return (
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as typeof activeTab)} className="w-full">
<TabsList className="mb-6 w-full grid grid-cols-2">
<TabsTrigger value="transcription">Transcription</TabsTrigger>
<TabsTrigger value="intelligence">Intelligence</TabsTrigger>
</TabsList>
<TabsContent value="transcription" className="w-full">
<TranscriptionSettings />
</TabsContent>
<TabsContent value="intelligence" className="w-full">
<IntelligenceSettings />
</TabsContent>
</Tabs>
);
}

function TranscriptionSettings() {
return (
<div className="space-y-8">
<Section title="On-device models" description="Local transcription models">
<ModelCard
name="Parakeet-2"
description="For English-only conversations"
status="downloaded"
/>
<ModelCard
name="Parakeet-3"
description="For European languages"
status="available"
/>
</Section>

<Section title="Speech-to-text providers" description="Cloud-based transcription services">
<ProviderCard name="Deepgram" />
<ProviderCard name="Assembly AI" configured />
</Section>
</div>
);
}

function IntelligenceSettings() {
return (
<Section title="LLM providers" description="Large language model services">
<ProviderCard name="Anthropic" />
<ProviderCard name="OpenAI" configured modelInUse="chatgpt-4o-latest" />
<ProviderCard name="Ollama" />
<ProviderCard name="LM Studio" />
</Section>
);
}

function Section({
title,
description,
children,
}: {
title: string;
description: string;
children: React.ReactNode;
}) {
return (
<div>
<div className="mb-3">
<h3 className="text-sm font-semibold text-gray-900">{title}</h3>
<p className="text-xs text-gray-500">{description}</p>
</div>
<div className="space-y-2">{children}</div>
</div>
);
}

function ModelCard({
name,
description,
status,
}: {
name: string;
description: string;
status: "available" | "downloaded";
}) {
return (
<div
className={cn([
"p-4 rounded-lg border-2 transition-all cursor-pointer",
status === "downloaded"
? "border-blue-500 bg-blue-50/50"
: "border-dashed border-gray-200 bg-white hover:border-gray-300",
])}
>
<div className="flex items-center justify-between gap-4">
<div className="flex-1 min-w-0">
<h3 className="text-base font-semibold text-gray-900">{name}</h3>
<p className="text-sm text-gray-500 mt-0.5">{description}</p>
</div>
<div className="flex-shrink-0">
{status === "downloaded"
? (
<span className="text-xs font-medium text-blue-600 bg-blue-100 px-3 py-1.5 rounded">
Model Downloaded
</span>
)
: (
<Button size="sm" variant="outline" className="text-xs">
Download Model
</Button>
)}
</div>
</div>
</div>
);
}

function ProviderCard({
name,
configured,
modelInUse,
}: {
name: string;
configured?: boolean;
modelInUse?: string;
}) {
const [isOpen, setIsOpen] = useState(false);

return (
<div
className={cn([
"border rounded-lg transition-all cursor-pointer",
isOpen
? "border-blue-500 ring-2 ring-blue-500 bg-blue-50/30"
: "border-gray-200 bg-white hover:border-gray-300",
])}
>
<div className="p-4" onClick={() => setIsOpen(!isOpen)}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="font-medium text-gray-900">{name}</span>
{configured && (
<span className="text-xs text-green-700 flex items-center gap-1">
<span>✓</span>
<span>API key configured</span>
</span>
)}
</div>
<span className="text-gray-400 text-xl font-light">{isOpen ? "−" : "+"}</span>
</div>
{modelInUse && (
<p className="text-xs text-gray-500 mt-2">
Model being used: <span className="font-mono text-gray-700">{modelInUse}</span>
</p>
)}
</div>

{isOpen && (
<div className="px-4 pb-4 border-t border-gray-200 mt-2">
<div className="mt-4">
<Input
placeholder={`Paste your API key for ${name}`}
type="password"
className="placeholder:text-gray-400"
onClick={(e) => e.stopPropagation()}
/>
</div>
</div>
)}
</div>
);
}
3 changes: 3 additions & 0 deletions apps/desktop2/src/components/settings/billing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function SettingsBilling() {
return null;
}
3 changes: 3 additions & 0 deletions apps/desktop2/src/components/settings/calendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function SettingsCalendar() {
return null;
}
16 changes: 16 additions & 0 deletions apps/desktop2/src/components/settings/developers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { commands as windowsCommands } from "@hypr/plugin-windows/v1";

import { useAuth } from "../../auth";

export function SettingsDevelopers() {
const s = useAuth();

const handleAuth = () => windowsCommands.windowShow({ type: "auth" });

return (
<div>
<pre>{JSON.stringify(s?.session)}</pre>
<button onClick={handleAuth}>Auth</button>
</div>
);
}
3 changes: 3 additions & 0 deletions apps/desktop2/src/components/settings/feedback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function SettingsFeedback() {
return null;
}
16 changes: 16 additions & 0 deletions apps/desktop2/src/components/settings/general.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useUpdateGeneral } from "./shared";

export function SettingsGeneral() {
const { value, handle } = useUpdateGeneral();

return (
<div>
<pre>{JSON.stringify(value, null, 2)}</pre>
<input
type="checkbox"
checked={value.save_recordings}
onChange={(e) => handle.setField("save_recordings", e.target.checked)}
/>
</div>
);
}
3 changes: 3 additions & 0 deletions apps/desktop2/src/components/settings/integrations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function SettingsIntegrations() {
return null;
}
Loading
Loading