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
36 changes: 34 additions & 2 deletions apps/desktop/src/components/editor-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,28 @@ export function useEnhanceMutation({
const [actualIsLocalLlm, setActualIsLocalLlm] = useState(isLocalLlm);
const queryClient = useQueryClient();

// Extract H1 headers at component level (always available)
const extractH1Headers = useCallback((htmlContent: string): string[] => {
if (!htmlContent) {
return [];
}

const h1Regex = /<h1[^>]*>(.*?)<\/h1>/gi;
const headers: string[] = [];
let match;

while ((match = h1Regex.exec(htmlContent)) !== null) {
const headerText = match[1].replace(/<[^>]*>/g, "").trim();
if (headerText) {
headers.push(headerText);
}
}

return headers;
}, []);

const h1Headers = useMemo(() => extractH1Headers(rawContent), [rawContent, extractH1Headers]);

const preMeetingText = extractTextFromHtml(preMeetingNote);
const rawText = extractTextFromHtml(rawContent);

Expand Down Expand Up @@ -315,11 +337,21 @@ export function useEnhanceMutation({

const selectedTemplate = await TemplateService.getTemplate(effectiveTemplateId ?? "");

const shouldUseH1Headers = !effectiveTemplateId && h1Headers.length > 0;
const grammarSections = selectedTemplate?.sections.map(s => s.title) || null;

const participants = await dbCommands.sessionListParticipants(sessionId);

const systemMessage = await templateCommands.render(
"enhance.system",
{ config, type, templateInfo: selectedTemplate },
{
config,
type,
// Pass userHeaders when using H1 headers, templateInfo otherwise
...(shouldUseH1Headers
? { userHeaders: h1Headers }
: { templateInfo: selectedTemplate }),
},
);

const userMessage = await templateCommands.render(
Expand Down Expand Up @@ -372,7 +404,7 @@ export function useEnhanceMutation({
metadata: {
grammar: {
task: "enhance",
sections: selectedTemplate?.sections.map(s => s.title) || null,
sections: grammarSections,
} satisfies Grammar,
},
},
Expand Down
129 changes: 129 additions & 0 deletions apps/desktop/src/components/settings/views/ai.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import { useForm } from "react-hook-form";
import { z } from "zod";

import { commands as connectorCommands, type Connection } from "@hypr/plugin-connector";
import { commands as dbCommands } from "@hypr/plugin-db";
import { commands as localLlmCommands, SupportedModel } from "@hypr/plugin-local-llm";

import { commands as localSttCommands } from "@hypr/plugin-local-stt";
import { Button } from "@hypr/ui/components/ui/button";
import {
Expand Down Expand Up @@ -121,6 +123,34 @@ const initialLlmModels = [
},
];

const aiConfigSchema = z.object({
aiSpecificity: z.number().int().min(1).max(4).optional(),
});
type AIConfigValues = z.infer<typeof aiConfigSchema>;

const specificityLevels = {
1: {
title: "Conservative",
description:
"Minimal creative changes. Preserves your original writing style and content while making only essential improvements to clarity and flow.",
},
2: {
title: "Balanced",
description:
"Moderate creative input. Enhances your content with some stylistic improvements while maintaining the core message and tone.",
},
3: {
title: "Creative",
description:
"More creative freedom. Actively improves and expands content with additional context, examples, and engaging language.",
},
4: {
title: "Innovative",
description:
"Maximum creativity. Transforms content with rich language, fresh perspectives, and creative restructuring while preserving key information.",
},
} as const;

export default function LocalAI() {
const queryClient = useQueryClient();
const [isWerModalOpen, setIsWerModalOpen] = useState(false);
Expand Down Expand Up @@ -242,6 +272,49 @@ export default function LocalAI() {
},
});

const config = useQuery({
queryKey: ["config", "ai"],
queryFn: async () => {
const result = await dbCommands.getConfig();
return result;
},
});

const aiConfigForm = useForm<AIConfigValues>({
resolver: zodResolver(aiConfigSchema),
defaultValues: {
aiSpecificity: 3,
},
});

useEffect(() => {
if (config.data) {
aiConfigForm.reset({
aiSpecificity: config.data.ai.ai_specificity ?? 3,
});
}
}, [config.data, aiConfigForm]);

const aiConfigMutation = useMutation({
mutationFn: async (values: AIConfigValues) => {
if (!config.data) {
return;
}

await dbCommands.setConfig({
...config.data,
ai: {
...config.data.ai,
ai_specificity: values.aiSpecificity ?? 3,
},
});
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["config", "ai"] });
},
onError: console.error,
});

const form = useForm<FormValues>({
resolver: zodResolver(endpointSchema),
mode: "onChange",
Expand Down Expand Up @@ -682,6 +755,62 @@ export default function LocalAI() {
</FormItem>
)}
/>

{/* NEW: Detail Level Configuration */}
<Form {...aiConfigForm}>
<FormField
control={aiConfigForm.control}
name="aiSpecificity"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium">
<Trans>Creativity Level</Trans>
</FormLabel>
<FormDescription className="text-xs">
<Trans>Control how creative the AI enhancement should be</Trans>
</FormDescription>
<FormControl>
<div className="space-y-3">
{/* Button bar - matching form element width */}
<div className="w-full">
<div className="flex justify-between rounded-md p-0.5 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500 shadow-sm">
{[1, 2, 3, 4].map((level) => (
<button
key={level}
type="button"
onClick={() => {
field.onChange(level);
aiConfigMutation.mutate({ aiSpecificity: level });
}}
disabled={!customLLMEnabled.data}
className={cn(
"py-1.5 px-2 flex-1 text-center text-sm font-medium rounded transition-all duration-150 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-transparent",
field.value === level
? "bg-white text-black shadow-sm"
: "text-white hover:bg-white/20",
!customLLMEnabled.data && "opacity-50 cursor-not-allowed",
)}
>
{specificityLevels[level as keyof typeof specificityLevels]?.title}
</button>
))}
</div>
</div>

{/* Current selection description in card */}
<div className="p-3 rounded-md bg-neutral-50 border border-neutral-200">
<div className="text-xs text-muted-foreground">
{specificityLevels[field.value as keyof typeof specificityLevels]?.description
|| specificityLevels[3].description}
</div>
</div>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</Form>
</form>
</Form>
</div>
Expand Down
32 changes: 20 additions & 12 deletions apps/desktop/src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,12 @@ msgstr "Annual"
msgid "Anyone with the link can view this page"
msgstr "Anyone with the link can view this page"

#: src/components/settings/views/ai.tsx:585
#: src/components/settings/views/ai.tsx:658
msgid "API Base URL"
msgstr "API Base URL"

#: src/components/settings/views/integrations.tsx:197
#: src/components/settings/views/ai.tsx:611
#: src/components/settings/views/ai.tsx:684
msgid "API Key"
msgstr "API Key"

Expand Down Expand Up @@ -460,7 +460,7 @@ msgstr "Company name"
#~ msgid "Connect"
#~ msgstr "Connect"

#: src/components/settings/views/ai.tsx:565
#: src/components/settings/views/ai.tsx:638
msgid "Connect to a self-hosted or third-party LLM endpoint (OpenAI API compatible)."
msgstr "Connect to a self-hosted or third-party LLM endpoint (OpenAI API compatible)."

Expand Down Expand Up @@ -492,6 +492,10 @@ msgstr "Contacts Access"
msgid "Continue"
msgstr "Continue"

#: src/components/settings/views/ai.tsx:770
msgid "Control how creative the AI enhancement should be"
msgstr "Control how creative the AI enhancement should be"

#: src/routes/app.human.$id.tsx:535
#: src/components/editor-area/note-header/chips/participants-chip.tsx:399
msgid "Create"
Expand All @@ -513,11 +517,15 @@ msgstr "Create Note"
msgid "Create your first template to get started"
msgstr "Create your first template to get started"

#: src/components/settings/views/ai.tsx:767
msgid "Creativity Level"
msgstr "Creativity Level"

#: src/components/settings/views/billing.tsx:66
msgid "Current Plan"
msgstr "Current Plan"

#: src/components/settings/views/ai.tsx:562
#: src/components/settings/views/ai.tsx:635
msgid "Custom Endpoint"
msgstr "Custom Endpoint"

Expand Down Expand Up @@ -580,7 +588,7 @@ msgstr "Enable"
msgid "Enable Integration"
msgstr "Enable Integration"

#: src/components/settings/views/ai.tsx:436
#: src/components/settings/views/ai.tsx:509
msgid "Enhancing"
msgstr "Enhancing"

Expand All @@ -592,11 +600,11 @@ msgstr "Enter a section title"
#~ msgid "Enter model name (e.g., gpt-4, llama3.2:3b)"
#~ msgstr "Enter model name (e.g., gpt-4, llama3.2:3b)"

#: src/components/settings/views/ai.tsx:614
#: src/components/settings/views/ai.tsx:687
msgid "Enter the API key for your custom LLM endpoint"
msgstr "Enter the API key for your custom LLM endpoint"

#: src/components/settings/views/ai.tsx:588
#: src/components/settings/views/ai.tsx:661
msgid "Enter the base URL for your custom LLM endpoint"
msgstr "Enter the base URL for your custom LLM endpoint"

Expand Down Expand Up @@ -759,7 +767,7 @@ msgstr "LinkedIn username"
msgid "Live summary of the meeting"
msgstr "Live summary of the meeting"

#: src/components/settings/views/ai.tsx:648
#: src/components/settings/views/ai.tsx:721
msgid "Loading available models..."
msgstr "Loading available models..."

Expand Down Expand Up @@ -805,7 +813,7 @@ msgstr "Members"
msgid "Microphone Access"
msgstr "Microphone Access"

#: src/components/settings/views/ai.tsx:636
#: src/components/settings/views/ai.tsx:709
msgid "Model Name"
msgstr "Model Name"

Expand Down Expand Up @@ -947,7 +955,7 @@ msgstr "Pause"
msgid "people"
msgstr "people"

#: src/components/settings/views/ai.tsx:298
#: src/components/settings/views/ai.tsx:371
msgid "Performance difference between languages"
msgstr "Performance difference between languages"

Expand Down Expand Up @@ -1037,7 +1045,7 @@ msgstr "Search..."
msgid "Sections"
msgstr "Sections"

#: src/components/settings/views/ai.tsx:639
#: src/components/settings/views/ai.tsx:712
msgid "Select a model from the dropdown (if available) or manually enter the model name required by your endpoint."
msgstr "Select a model from the dropdown (if available) or manually enter the model name required by your endpoint."

Expand Down Expand Up @@ -1179,7 +1187,7 @@ msgstr "Toggle left sidebar"
msgid "Toggle widget panel"
msgstr "Toggle widget panel"

#: src/components/settings/views/ai.tsx:289
#: src/components/settings/views/ai.tsx:362
msgid "Transcribing"
msgstr "Transcribing"

Expand Down
Loading
Loading