diff --git a/Cargo.lock b/Cargo.lock index 7fc630c45..5b226916a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7516,6 +7516,7 @@ dependencies = [ "async-openai", "buffer", "colored", + "data", "dirs 6.0.0", "encoding_rs", "futures-util", @@ -7523,9 +7524,14 @@ dependencies = [ "gbnf-validator", "include_url_macro", "indoc", + "listener-interface", "llama-cpp-2", + "minijinja", "serde", + "serde_json", + "template", "thiserror 2.0.12", + "timeline", "tokio", ] @@ -13448,9 +13454,6 @@ dependencies = [ name = "tauri-plugin-template" version = "0.1.0" dependencies = [ - "codes-iso-639", - "minijinja", - "minijinja-contrib", "serde", "serde_json", "specta", @@ -13458,7 +13461,7 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-specta", - "thiserror 2.0.12", + "template", "tracing", ] @@ -13682,6 +13685,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "template" +version = "0.1.0" +dependencies = [ + "codes-iso-639", + "minijinja", + "minijinja-contrib", + "serde", + "serde_json", + "strum 0.26.3", + "thiserror 2.0.12", +] + [[package]] name = "tendril" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 9ae3d327a..49cdeb332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ hypr-listener-interface = { path = "plugins/listener-interface", package = "list hypr-llama = { path = "crates/llama", package = "llama" } hypr-loops = { path = "crates/loops", package = "loops" } hypr-nango = { path = "crates/nango", package = "nango" } +hypr-template = { path = "crates/template", package = "template" } hypr-timeline = { path = "crates/timeline", package = "timeline" } hypr-turso = { path = "crates/turso", package = "turso" } hypr-whisper = { path = "crates/whisper", package = "whisper" } diff --git a/apps/desktop/src/components/editor-area/index.tsx b/apps/desktop/src/components/editor-area/index.tsx index aa3ba1760..ac4a7a8ce 100644 --- a/apps/desktop/src/components/editor-area/index.tsx +++ b/apps/desktop/src/components/editor-area/index.tsx @@ -5,7 +5,6 @@ import { AnimatePresence } from "motion/react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useHypr } from "@/contexts"; -import { ENHANCE_SYSTEM_TEMPLATE_KEY, ENHANCE_USER_TEMPLATE_KEY } from "@/templates"; import { commands as analyticsCommands } from "@hypr/plugin-analytics"; import { commands as dbCommands } from "@hypr/plugin-db"; import { commands as miscCommands } from "@hypr/plugin-misc"; @@ -185,12 +184,12 @@ export function useEnhanceMutation({ const timeline = await fn(sessionId); const systemMessage = await templateCommands.render( - ENHANCE_SYSTEM_TEMPLATE_KEY, + "enhance.system", { config }, ); const userMessage = await templateCommands.render( - ENHANCE_USER_TEMPLATE_KEY, + "enhance.user", { editor: rawContent, timeline: timeline, @@ -214,6 +213,7 @@ export function useEnhanceMutation({ for await (const chunk of textStream) { setAnimate(true); acc += chunk; + console.log("acc", acc); const html = await miscCommands.opinionatedMdToHtml(acc); setEnhancedContent(html); } diff --git a/apps/desktop/src/components/editor-area/note-header/listen-button/buttons.tsx b/apps/desktop/src/components/editor-area/note-header/listen-button/buttons.tsx new file mode 100644 index 000000000..3da83e056 --- /dev/null +++ b/apps/desktop/src/components/editor-area/note-header/listen-button/buttons.tsx @@ -0,0 +1,90 @@ +import { Trans } from "@lingui/react/macro"; +import { PauseIcon } from "lucide-react"; + +import SoundIndicator from "@/components/sound-indicator"; +import { Button } from "@hypr/ui/components/ui/button"; +import { PopoverTrigger } from "@hypr/ui/components/ui/popover"; +import { Spinner } from "@hypr/ui/components/ui/spinner"; +import { MicrophoneSelector } from "./microphone-selector"; + +interface ButtonBaseProps { + disabled?: boolean; + onClick: () => void; + isEnhanced?: boolean; +} + +export const LoadingButton = () => { + return ( +
+ +
+ ); +}; + +export const ResumeButton = ({ + disabled, + onClick, + isEnhanced, +}: ButtonBaseProps) => { + return ( + + + + ); +}; + +export const InitialRecordButton = ({ disabled, onClick }: ButtonBaseProps) => { + return ( + + + + ); +}; + +export const ActiveRecordButton = ({ onClick }: ButtonBaseProps) => { + return ( + + + + + + ); +}; + +export const StopRecordingButton = ({ onClick }: { onClick: () => void }) => { + return ( + + ); +}; diff --git a/apps/desktop/src/components/editor-area/note-header/listen-button.tsx b/apps/desktop/src/components/editor-area/note-header/listen-button/index.tsx similarity index 60% rename from apps/desktop/src/components/editor-area/note-header/listen-button.tsx rename to apps/desktop/src/components/editor-area/note-header/listen-button/index.tsx index 94eafa832..71744be57 100644 --- a/apps/desktop/src/components/editor-area/note-header/listen-button.tsx +++ b/apps/desktop/src/components/editor-area/note-header/listen-button/index.tsx @@ -1,17 +1,15 @@ -import { Trans } from "@lingui/react/macro"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { MicIcon, MicOffIcon, PauseIcon, Volume2Icon, VolumeOffIcon } from "lucide-react"; +import { MicIcon, MicOffIcon, Volume2Icon, VolumeOffIcon } from "lucide-react"; import { useEffect, useState } from "react"; import SoundIndicator from "@/components/sound-indicator"; import { commands as listenerCommands } from "@hypr/plugin-listener"; import { commands as localSttCommands } from "@hypr/plugin-local-stt"; import { Button } from "@hypr/ui/components/ui/button"; -import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover"; -import { Spinner } from "@hypr/ui/components/ui/spinner"; +import { Popover, PopoverContent } from "@hypr/ui/components/ui/popover"; import { toast } from "@hypr/ui/components/ui/toast"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@hypr/ui/components/ui/tooltip"; import { useOngoingSession, useSession } from "@hypr/utils/contexts"; +import { ActiveRecordButton, InitialRecordButton, LoadingButton, ResumeButton, StopRecordingButton } from "./buttons"; interface ListenButtonProps { sessionId: string; @@ -20,6 +18,14 @@ interface ListenButtonProps { export default function ListenButton({ sessionId }: ListenButtonProps) { const [open, setOpen] = useState(false); + const ongoingSessionStore = useOngoingSession((s) => ({ + start: s.start, + pause: s.pause, + isCurrent: s.sessionId === sessionId, + status: s.status, + timeline: s.timeline, + })); + const modelDownloaded = useQuery({ queryKey: ["check-stt-model-downloaded"], refetchInterval: 1000, @@ -32,14 +38,6 @@ export default function ListenButton({ sessionId }: ListenButtonProps) { }, }); - const ongoingSessionStore = useOngoingSession((s) => ({ - start: s.start, - pause: s.pause, - isCurrent: s.sessionId === sessionId, - status: s.status, - timeline: s.timeline, - })); - const startedBefore = useSession( sessionId, (s) => s.session.conversations.length > 0, @@ -119,70 +117,26 @@ export default function ListenButton({ sessionId }: ListenButtonProps) { return ( <> - {ongoingSessionStore.status === "loading" && ( -
- -
- )} + {ongoingSessionStore.status === "loading" && } + {showResumeButton && ( - + isEnhanced={!!isEnhanced} + /> )} + {ongoingSessionStore.status === "inactive" && !showResumeButton && ( - - - - - -

- Start recording -

-
-
+ )} {ongoingSessionStore.status === "active" && ( - - - - - - - -

- Pause recording -

-
-
+
@@ -198,14 +152,7 @@ export default function ListenButton({ sessionId }: ListenButtonProps) { />
- +
)} diff --git a/apps/desktop/src/components/editor-area/note-header/listen-button/microphone-selector.tsx b/apps/desktop/src/components/editor-area/note-header/listen-button/microphone-selector.tsx new file mode 100644 index 000000000..95fa84140 --- /dev/null +++ b/apps/desktop/src/components/editor-area/note-header/listen-button/microphone-selector.tsx @@ -0,0 +1,70 @@ +import { Trans } from "@lingui/react/macro"; +import React, { useState } from "react"; + +import { HoverCard, HoverCardContent, HoverCardTrigger } from "@hypr/ui/components/ui/hover-card"; + +// TODO +export const useMicrophones = () => { + const [selectedMic, setSelectedMic] = useState("default"); + + // Mock data for available microphones + const microphones = [ + { id: "default", name: "System Default" }, + { id: "headset", name: "Logitech G Pro X Headset" }, + { id: "webcam", name: "Logitech C920 Webcam" }, + { id: "usb", name: "Blue Yeti USB Microphone" }, + { id: "bluetooth", name: "AirPods Pro" }, + ]; + + return { + microphones, + selectedMic, + setSelectedMic, + isLoading: false, + error: null, + }; +}; + +export const MicrophoneSelector = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { microphones, selectedMic, setSelectedMic } = useMicrophones(); + + return ( + + {children} + +
+
+ Select Microphone +
+ +
+ {microphones.map((mic) => ( +
setSelectedMic(mic.id)} + > +
+ {selectedMic === mic.id &&
} +
+
+
{mic.name}
+
+
+ ))} +
+
+ + + ); +}; diff --git a/apps/desktop/src/locales/en/messages.po b/apps/desktop/src/locales/en/messages.po index 8c62f8848..4bb830a46 100644 --- a/apps/desktop/src/locales/en/messages.po +++ b/apps/desktop/src/locales/en/messages.po @@ -702,8 +702,7 @@ msgstr "Optional for participant suggestions" msgid "Owner" msgstr "Owner" -#: src/components/editor-area/note-header/listen-button.tsx:182 -#: src/components/editor-area/note-header/listen-button.tsx:207 +#: src/components/editor-area/note-header/listen-button/buttons.tsx:87 msgid "Pause recording" msgstr "Pause recording" @@ -761,7 +760,7 @@ msgstr "Required to transcribe other people's voice during meetings" msgid "Required to transcribe your voice during meetings" msgstr "Required to transcribe your voice during meetings" -#: src/components/editor-area/note-header/listen-button.tsx:140 +#: src/components/editor-area/note-header/listen-button/buttons.tsx:43 msgid "Resume" msgstr "Resume" @@ -819,6 +818,10 @@ msgstr "Select apps that should not trigger meeting notifications" msgid "Select Calendars" msgstr "Select Calendars" +#: src/components/editor-area/note-header/listen-button/microphone-selector.tsx:41 +msgid "Select Microphone" +msgstr "Select Microphone" + #: src/components/settings/views/team.tsx:240 msgid "Send invite" msgstr "Send invite" @@ -856,8 +859,8 @@ msgid "Start Monthly Plan" msgstr "Start Monthly Plan" #: src/components/editor-area/note-header/listen-button.tsx:158 -msgid "Start recording" -msgstr "Start recording" +#~ msgid "Start recording" +#~ msgstr "Start recording" #: src/components/settings/views/ai.tsx:199 #: src/components/settings/views/ai.tsx:373 diff --git a/apps/desktop/src/locales/ko/messages.po b/apps/desktop/src/locales/ko/messages.po index ab1a9250a..ea61f6889 100644 --- a/apps/desktop/src/locales/ko/messages.po +++ b/apps/desktop/src/locales/ko/messages.po @@ -702,8 +702,7 @@ msgstr "" msgid "Owner" msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:182 -#: src/components/editor-area/note-header/listen-button.tsx:207 +#: src/components/editor-area/note-header/listen-button/buttons.tsx:87 msgid "Pause recording" msgstr "" @@ -761,7 +760,7 @@ msgstr "" msgid "Required to transcribe your voice during meetings" msgstr "" -#: src/components/editor-area/note-header/listen-button.tsx:140 +#: src/components/editor-area/note-header/listen-button/buttons.tsx:43 msgid "Resume" msgstr "" @@ -819,6 +818,10 @@ msgstr "" msgid "Select Calendars" msgstr "" +#: src/components/editor-area/note-header/listen-button/microphone-selector.tsx:41 +msgid "Select Microphone" +msgstr "" + #: src/components/settings/views/team.tsx:240 msgid "Send invite" msgstr "" @@ -856,8 +859,8 @@ msgid "Start Monthly Plan" msgstr "" #: src/components/editor-area/note-header/listen-button.tsx:158 -msgid "Start recording" -msgstr "" +#~ msgid "Start recording" +#~ msgstr "" #: src/components/settings/views/ai.tsx:199 #: src/components/settings/views/ai.tsx:373 diff --git a/apps/desktop/src/routes/app.tsx b/apps/desktop/src/routes/app.tsx index ac6802c0e..ac526d722 100644 --- a/apps/desktop/src/routes/app.tsx +++ b/apps/desktop/src/routes/app.tsx @@ -18,7 +18,6 @@ import { useLeftSidebar, useRightPanel, } from "@/contexts"; -import { registerTemplates } from "@/templates"; import { commands } from "@/types"; import { commands as listenerCommands } from "@hypr/plugin-listener"; import { events as windowsEvents, getCurrentWebviewWindowLabel } from "@hypr/plugin-windows"; @@ -36,10 +35,6 @@ function Component() { const router = useRouter(); const { sessionsStore, isOnboardingNeeded } = Route.useLoaderData(); - useEffect(() => { - registerTemplates(); - }, []); - const windowLabel = getCurrentWebviewWindowLabel(); const showNotifications = windowLabel === "main" && !isOnboardingNeeded; diff --git a/apps/desktop/src/templates/index.ts b/apps/desktop/src/templates/index.ts deleted file mode 100644 index ea2e4dcf5..000000000 --- a/apps/desktop/src/templates/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import ENHANCE_SYSTEM_TEMPLATE_VALUE from "./enhance.system.jinja?raw"; -import ENHANCE_USER_TEMPLATE_VALUE from "./enhance.user.jinja?raw"; - -export const ENHANCE_SYSTEM_TEMPLATE_KEY = "enhance-system"; -export const ENHANCE_USER_TEMPLATE_KEY = "enhance-user"; - -import { commands as templateCommands } from "@hypr/plugin-template"; - -export const registerTemplates = () => { - return Promise.all([ - templateCommands.registerTemplate( - ENHANCE_SYSTEM_TEMPLATE_KEY, - ENHANCE_SYSTEM_TEMPLATE_VALUE, - ), - templateCommands.registerTemplate( - ENHANCE_USER_TEMPLATE_KEY, - ENHANCE_USER_TEMPLATE_VALUE, - ), - ]); -}; diff --git a/apps/desktop/src/templates/types.d.ts b/apps/desktop/src/templates/types.d.ts deleted file mode 100644 index 8c27cc1c1..000000000 --- a/apps/desktop/src/templates/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "*.jinja?raw" { - const content: string; - export default content; -} diff --git a/apps/docs/data/i18n.json b/apps/docs/data/i18n.json index 5425aa364..00c6b9cc7 100644 --- a/apps/docs/data/i18n.json +++ b/apps/docs/data/i18n.json @@ -1,12 +1,12 @@ [ { "language": "ko", - "total": 232, - "missing": 224 + "total": 233, + "missing": 225 }, { "language": "en (source)", - "total": 232, + "total": 233, "missing": 0 } ] diff --git a/crates/llama/Cargo.toml b/crates/llama/Cargo.toml index a3c3e2203..3eebaa7d8 100644 --- a/crates/llama/Cargo.toml +++ b/crates/llama/Cargo.toml @@ -4,12 +4,18 @@ version = "0.1.0" edition = "2021" [dev-dependencies] -gbnf-validator = { git = "https://github.com/fastrepl/gbnf-validator", rev = "3dec055" } hypr-buffer = { workspace = true } +hypr-data = { workspace = true } +hypr-listener-interface = { workspace = true } +hypr-template = { workspace = true } +hypr-timeline = { workspace = true } colored = "3.0.0" dirs = { workspace = true } +gbnf-validator = { git = "https://github.com/fastrepl/gbnf-validator", rev = "3dec055" } indoc = "2.0.6" +minijinja = { workspace = true } +serde_json = { workspace = true } [dependencies] include_url_macro = { workspace = true } diff --git a/crates/llama/src/grammar/markdown.gbnf b/crates/llama/src/grammar/markdown.gbnf index f0ed1cbbc..df20a0473 100644 --- a/crates/llama/src/grammar/markdown.gbnf +++ b/crates/llama/src/grammar/markdown.gbnf @@ -1,5 +1,8 @@ root ::= section+ section ::= header "\n\n" content -header ::= "# " title -title ::= [^#\n]+ -content ::= [^#] [^#]* +header ::= "# " char+ +content ::= [^#[^*\n] [^#`[^=]* + +char ::= letter | digit | " " +letter ::= [a-zA-Z] +digit ::= [0-9] \ No newline at end of file diff --git a/crates/llama/src/grammar/mod.rs b/crates/llama/src/grammar/mod.rs index 3afaec10e..7613d5f42 100644 --- a/crates/llama/src/grammar/mod.rs +++ b/crates/llama/src/grammar/mod.rs @@ -17,6 +17,7 @@ mod tests { struct TestCase { pub grammar: &'static str, + pub intent: &'static str, pub text: &'static str, pub valid: bool, pub debug: bool, @@ -27,6 +28,7 @@ mod tests { let test_cases = vec![ TestCase { grammar: MARKDOWN_GRAMMAR, + intent: "should start with a heading", text: indoc::indoc! {r#" Here's a response: @@ -36,11 +38,45 @@ mod tests { }, TestCase { grammar: MARKDOWN_GRAMMAR, + intent: "header-content pair is required", text: indoc::indoc! {r#" # This is a test - 12"#}, - valid: true, + ## This is a test"#}, + valid: false, + debug: false, + }, + TestCase { + grammar: MARKDOWN_GRAMMAR, + intent: "footnote is not allowed. Currently we prevent both '[' and '^' to achieve this.", + text: indoc::indoc! {r#" + # This is a test + + Hi Hello. [^1]"#}, + valid: false, + debug: false, + }, + TestCase { + grammar: MARKDOWN_GRAMMAR, + intent: "codeblock is not allowed. Currently we prevent '`' to achieve this.", + text: indoc::indoc! {r#" + # This is a test + + Hi Hello. `code`. + ```code + 123 + ```"#}, + valid: false, + debug: false, + }, + TestCase { + grammar: MARKDOWN_GRAMMAR, + intent: "content should not start with bold text. we prevent '*'(but allow '-') to achieve this.", + text: indoc::indoc! {r#" + # Enhanced Meeting Notes + + **What Hyprnote Does**"#}, + valid: false, debug: false, }, ]; @@ -53,7 +89,7 @@ mod tests { Ok(valid_actual) => { if valid_actual != test_case.valid { println!("{}", "=".repeat(80)); - println!("{}_failed", i); + println!("{}_failed. intent: {}", i, test_case.intent); debug_grammar_failure_point(&gbnf, test_case.grammar, test_case.text); println!("\n{}", "=".repeat(80)); @@ -62,7 +98,7 @@ mod tests { if test_case.debug { println!("{}", "=".repeat(80)); - println!("{}_passed", i); + println!("{}_passed. intent: {}", i, test_case.intent); debug_grammar_failure_point(&gbnf, test_case.grammar, test_case.text); println!("\n{}", "=".repeat(80)); } diff --git a/crates/llama/src/lib.rs b/crates/llama/src/lib.rs index c5b2c4596..40d956b4e 100644 --- a/crates/llama/src/lib.rs +++ b/crates/llama/src/lib.rs @@ -19,7 +19,7 @@ pub use message::*; const TEMPLATE_NAME: &str = "llama3"; const DEFAULT_MAX_INPUT_TOKENS: u32 = 1024 * 8; -const DEFAULT_MAX_OUTPUT_TOKENS: u32 = 1024 * 2; +const DEFAULT_MAX_OUTPUT_TOKENS: u32 = 1024; static LLAMA_BACKEND: OnceLock> = OnceLock::new(); @@ -99,8 +99,8 @@ impl Llama { let mut sampler = LlamaSampler::chain_simple([ LlamaSampler::grammar(&model, grammar::MARKDOWN_GRAMMAR, "root"), LlamaSampler::temp(0.5), - LlamaSampler::penalties(0, 1.5, 0.2, 0.0), - LlamaSampler::dist(1234), + LlamaSampler::penalties(0, 1.2, 0.2, 0.0), + LlamaSampler::mirostat_v2(1234, 4.0, 0.1), ]); while n_cur <= last_index + DEFAULT_MAX_OUTPUT_TOKENS as i32 { @@ -170,23 +170,74 @@ mod tests { .unwrap() .join("com.hyprnote.dev") .join("llm.gguf"); + Llama::new(model_path).unwrap() } - // cargo test test_simple -p llama -- --nocapture - #[tokio::test] - async fn test_simple() { - use futures_util::pin_mut; - use std::io::{self, Write}; + fn english_4_messages() -> Vec { + let timeline_view = { + let (transcripts, diarizations): ( + Vec, + Vec, + ) = ( + serde_json::from_str(hypr_data::english_4::TRANSCRIPTION_JSON).unwrap(), + serde_json::from_str(hypr_data::english_4::DIARIZATION_JSON).unwrap(), + ); - let llama = get_model(); - let prompt = "Generate a random meeting summary note."; + let mut timeline = hypr_timeline::Timeline::default(); + + for t in transcripts { + timeline.add_transcription(t); + } + for d in diarizations { + timeline.add_diarization(d); + } + + timeline.view(hypr_timeline::TimelineFilter::default()) + }; + + let mut env = hypr_template::minijinja::Environment::new(); + hypr_template::init(&mut env); + + let system = hypr_template::render( + &env, + hypr_template::PredefinedTemplate::EnhanceSystem.into(), + &serde_json::json!({ + "config": { + "general": { + "display_language": "en" + } + } + }) + .as_object() + .unwrap(), + ) + .unwrap(); + + let user = hypr_template::render( + &env, + hypr_template::PredefinedTemplate::EnhanceUser.into(), + &serde_json::json!({ + "editor": "privacy aspect seems interesting", + "timeline": timeline_view, + "participants": vec!["yujonglee".to_string()], + }) + .as_object() + .unwrap(), + ) + .unwrap(); + + vec![ + LlamaChatMessage::new("system".into(), system.into()).unwrap(), + LlamaChatMessage::new("user".into(), user.into()).unwrap(), + ] + } - let request = LlamaRequest::new(vec![ - LlamaChatMessage::new("user".into(), prompt.into()).unwrap() - ]); + async fn print_stream(model: &Llama, request: LlamaRequest) { + use futures_util::pin_mut; + use std::io::{self, Write}; - let stream = llama.generate_stream(request).unwrap(); + let stream = model.generate_stream(request).unwrap(); pin_mut!(stream); while let Some(token) = stream.next().await { @@ -195,4 +246,13 @@ mod tests { } println!(); } + + // cargo test test_english_4 -p llama -- --nocapture + #[tokio::test] + async fn test_english_4() { + let llama = get_model(); + let request = LlamaRequest::new(english_4_messages()); + + print_stream(&llama, request).await; + } } diff --git a/crates/template/Cargo.toml b/crates/template/Cargo.toml new file mode 100644 index 000000000..86bfe0030 --- /dev/null +++ b/crates/template/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "template" +version = "0.1.0" +edition = "2021" + +[dependencies] +codes-iso-639 = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +strum = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } + +minijinja = { workspace = true, features = ["loader", "preserve_order", "json"] } +minijinja-contrib = { workspace = true, features = ["pycompat"] } diff --git a/apps/desktop/src/templates/ai_chat.system.jinja b/crates/template/assets/ai_chat.system.jinja similarity index 100% rename from apps/desktop/src/templates/ai_chat.system.jinja rename to crates/template/assets/ai_chat.system.jinja diff --git a/apps/desktop/src/templates/create_title.system.jinja b/crates/template/assets/create_title.system.jinja similarity index 100% rename from apps/desktop/src/templates/create_title.system.jinja rename to crates/template/assets/create_title.system.jinja diff --git a/apps/desktop/src/templates/create_title.user.jinja b/crates/template/assets/create_title.user.jinja similarity index 100% rename from apps/desktop/src/templates/create_title.user.jinja rename to crates/template/assets/create_title.user.jinja diff --git a/apps/desktop/src/templates/enhance.system.jinja b/crates/template/assets/enhance.system.jinja similarity index 100% rename from apps/desktop/src/templates/enhance.system.jinja rename to crates/template/assets/enhance.system.jinja diff --git a/apps/desktop/src/templates/enhance.user.jinja b/crates/template/assets/enhance.user.jinja similarity index 100% rename from apps/desktop/src/templates/enhance.user.jinja rename to crates/template/assets/enhance.user.jinja diff --git a/apps/desktop/src/templates/postprocess_enhance.system.jinja b/crates/template/assets/postprocess_enhance.system.jinja similarity index 100% rename from apps/desktop/src/templates/postprocess_enhance.system.jinja rename to crates/template/assets/postprocess_enhance.system.jinja diff --git a/apps/desktop/src/templates/postprocess_enhance.user.jinja b/crates/template/assets/postprocess_enhance.user.jinja similarity index 100% rename from apps/desktop/src/templates/postprocess_enhance.user.jinja rename to crates/template/assets/postprocess_enhance.user.jinja diff --git a/apps/desktop/src/templates/show_annotation.system.jinja b/crates/template/assets/show_annotation.system.jinja similarity index 100% rename from apps/desktop/src/templates/show_annotation.system.jinja rename to crates/template/assets/show_annotation.system.jinja diff --git a/apps/desktop/src/templates/show_annotation.user.jinja b/crates/template/assets/show_annotation.user.jinja similarity index 100% rename from apps/desktop/src/templates/show_annotation.user.jinja rename to crates/template/assets/show_annotation.user.jinja diff --git a/crates/template/src/error.rs b/crates/template/src/error.rs new file mode 100644 index 000000000..b6e3a9b6f --- /dev/null +++ b/crates/template/src/error.rs @@ -0,0 +1,16 @@ +use serde::{ser::Serializer, Serialize}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + JinjaError(#[from] minijinja::Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/crates/template/src/lib.rs b/crates/template/src/lib.rs new file mode 100644 index 000000000..027109c4b --- /dev/null +++ b/crates/template/src/lib.rs @@ -0,0 +1,102 @@ +use codes_iso_639::part_1::LanguageCode; + +mod error; +pub use error::*; + +pub use minijinja; + +pub enum Template { + Static(PredefinedTemplate), + Dynamic(String), +} + +impl From for Template { + fn from(value: String) -> Self { + Template::Dynamic(value) + } +} + +impl From