mirror of
https://github.com/zadam/trilium.git
synced 2026-05-07 16:45:30 +02:00
Merge branch 'main' of https://github.com/TriliumNext/Trilium
This commit is contained in:
@@ -118,6 +118,8 @@ Trilium provides powerful user scripting capabilities:
|
||||
### Internationalization
|
||||
- Translation files in `apps/client/src/translations/`
|
||||
- Supported languages: English, German, Spanish, French, Romanian, Chinese
|
||||
- **Only add new translation keys to `en/translation.json`** — translations for other languages are managed via Weblate and will be contributed by the community
|
||||
- Third-party components (e.g., mind-map context menu) should use i18next `t()` for their labels, with the English strings added to `en/translation.json` under a dedicated namespace (e.g., `"mind-map"`)
|
||||
|
||||
### Security Considerations
|
||||
- Per-note encryption with granular protected sessions
|
||||
@@ -153,6 +155,12 @@ Trilium provides powerful user scripting capabilities:
|
||||
- Add migration scripts in `apps/server/src/migrations/`
|
||||
- Update schema in `apps/server/src/assets/db/schema.sql`
|
||||
|
||||
### Server-Side Static Assets
|
||||
- Static assets (templates, SQL, translations, etc.) go in `apps/server/src/assets/`
|
||||
- Access them at runtime via `RESOURCE_DIR` from `apps/server/src/services/resource_dir.ts` (e.g. `path.join(RESOURCE_DIR, "llm", "skills", "file.md")`)
|
||||
- **Do not use `import.meta.url`/`fileURLToPath`** to resolve file paths — the server is bundled into CJS for production, so `import.meta.url` will not point to the source directory
|
||||
- **Do not use `__dirname` with relative paths** from source files — after bundling, `__dirname` points to the bundle output, not the original source tree
|
||||
|
||||
## Build System Notes
|
||||
- Uses pnpm for monorepo management
|
||||
- Vite for fast development builds
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.25.2",
|
||||
"@redocly/cli": "2.25.3",
|
||||
"archiver": "7.0.1",
|
||||
"fs-extra": "11.3.4",
|
||||
"js-yaml": "4.1.1",
|
||||
|
||||
@@ -35,14 +35,14 @@
|
||||
"@triliumnext/highlightjs": "workspace:*",
|
||||
"@triliumnext/share-theme": "workspace:*",
|
||||
"@triliumnext/split.js": "workspace:*",
|
||||
"@univerjs/preset-sheets-conditional-formatting": "0.18.0",
|
||||
"@univerjs/preset-sheets-core": "0.18.0",
|
||||
"@univerjs/preset-sheets-data-validation": "0.18.0",
|
||||
"@univerjs/preset-sheets-filter": "0.18.0",
|
||||
"@univerjs/preset-sheets-find-replace": "0.18.0",
|
||||
"@univerjs/preset-sheets-note": "0.18.0",
|
||||
"@univerjs/preset-sheets-sort": "0.18.0",
|
||||
"@univerjs/presets": "0.18.0",
|
||||
"@univerjs/preset-sheets-conditional-formatting": "0.19.0",
|
||||
"@univerjs/preset-sheets-core": "0.19.0",
|
||||
"@univerjs/preset-sheets-data-validation": "0.19.0",
|
||||
"@univerjs/preset-sheets-filter": "0.19.0",
|
||||
"@univerjs/preset-sheets-find-replace": "0.19.0",
|
||||
"@univerjs/preset-sheets-note": "0.19.0",
|
||||
"@univerjs/preset-sheets-sort": "0.19.0",
|
||||
"@univerjs/presets": "0.19.0",
|
||||
"@zumer/snapdom": "2.7.0",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.8",
|
||||
@@ -65,7 +65,7 @@
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "17.0.5",
|
||||
"mermaid": "11.13.0",
|
||||
"mind-elixir": "5.9.3",
|
||||
"mind-elixir": "5.10.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.4",
|
||||
"preact": "10.29.0",
|
||||
|
||||
@@ -3,10 +3,10 @@ import type { LlmChatConfig, LlmCitation, LlmMessage, LlmModelInfo,LlmUsage } fr
|
||||
import server from "./server.js";
|
||||
|
||||
/**
|
||||
* Fetch available models for a provider.
|
||||
* Fetch available models from all configured providers.
|
||||
*/
|
||||
export async function getAvailableModels(provider: string = "anthropic"): Promise<LlmModelInfo[]> {
|
||||
const response = await server.get<{ models?: LlmModelInfo[] }>(`llm-chat/models?provider=${encodeURIComponent(provider)}`);
|
||||
export async function getAvailableModels(): Promise<LlmModelInfo[]> {
|
||||
const response = await server.get<{ models?: LlmModelInfo[] }>("llm-chat/models");
|
||||
return response.models ?? [];
|
||||
}
|
||||
|
||||
|
||||
@@ -2277,6 +2277,20 @@
|
||||
"sample_venn": "Venn",
|
||||
"sample_ishikawa": "Ishikawa"
|
||||
},
|
||||
"mind-map": {
|
||||
"addChild": "Add child",
|
||||
"addParent": "Add parent",
|
||||
"addSibling": "Add sibling",
|
||||
"removeNode": "Remove node",
|
||||
"focus": "Focus Mode",
|
||||
"cancelFocus": "Cancel Focus Mode",
|
||||
"moveUp": "Move up",
|
||||
"moveDown": "Move down",
|
||||
"link": "Link",
|
||||
"linkBidirectional": "Bidirectional Link",
|
||||
"clickTips": "Please click the target node",
|
||||
"summary": "Summary"
|
||||
},
|
||||
"llm": {
|
||||
"settings_title": "AI / LLM",
|
||||
"settings_description": "Configure AI and Large Language Model integrations.",
|
||||
|
||||
@@ -446,7 +446,8 @@
|
||||
"and_more": "... et {{count}} plus.",
|
||||
"print_landscape": "Lors de l'exportation en PDF, change l'orientation de la page en paysage au lieu de portrait.",
|
||||
"print_page_size": "Lors de l'exportation en PDF, change la taille de la page. Valeurs supportées : <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
|
||||
"color_type": "Couleur"
|
||||
"color_type": "Couleur",
|
||||
"textarea": "Texte multiligne"
|
||||
},
|
||||
"attribute_editor": {
|
||||
"help_text_body1": "Pour ajouter un label, tapez simplement par ex. <code>#rock</code>, ou si vous souhaitez également ajouter une valeur, tapez par ex. <code>#année = 2020</code>",
|
||||
@@ -662,7 +663,8 @@
|
||||
"show-cheatsheet": "Afficher l'aide rapide",
|
||||
"toggle-zen-mode": "Zen Mode",
|
||||
"new-version-available": "Nouvelle mise à jour disponible",
|
||||
"download-update": "Obtenir la version {{latestVersion}}"
|
||||
"download-update": "Obtenir la version {{latestVersion}}",
|
||||
"search_notes": "Rechercher notes"
|
||||
},
|
||||
"zen_mode": {
|
||||
"button_exit": "Sortir du Zen mode"
|
||||
@@ -706,7 +708,8 @@
|
||||
"advanced": "Avancé",
|
||||
"export_as_image": "Exporter en tant qu'image",
|
||||
"export_as_image_png": "PNG",
|
||||
"export_as_image_svg": "SVG (vectoriel)"
|
||||
"export_as_image_svg": "SVG (vectoriel)",
|
||||
"note_map": "Note Carte"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "Le widget bouton '{{componentId}}' n'a pas de gestionnaire de clic défini"
|
||||
@@ -744,7 +747,7 @@
|
||||
"button_title": "Exporter le diagramme au format SVG"
|
||||
},
|
||||
"relation_map_buttons": {
|
||||
"create_child_note_title": "Créer une nouvelle note enfant et l'ajouter à cette carte de relation",
|
||||
"create_child_note_title": "Créer une note enfant et l'ajouter à la carte",
|
||||
"reset_pan_zoom_title": "Réinitialiser le panoramique et le zoom aux coordonnées et à la position initiales",
|
||||
"zoom_in_title": "Zoomer",
|
||||
"zoom_out_title": "Zoom arrière"
|
||||
@@ -760,7 +763,9 @@
|
||||
"delete_this_note": "Supprimer cette note",
|
||||
"error_cannot_get_branch_id": "Impossible d'obtenir branchId pour notePath '{{notePath}}'",
|
||||
"error_unrecognized_command": "Commande non reconnue {{command}}",
|
||||
"note_revisions": "Révision de la note"
|
||||
"note_revisions": "Révision de la note",
|
||||
"backlinks": "Rétro-liens",
|
||||
"content_language_switcher": "Langue du contenu: {{language}}"
|
||||
},
|
||||
"note_icon": {
|
||||
"change_note_icon": "Changer l'icône de note",
|
||||
@@ -770,7 +775,11 @@
|
||||
"filter-none": "Toutes les icônes",
|
||||
"filter-default": "Icônes par défaut",
|
||||
"icon_tooltip": "{{name}}\nPack d'icônes : {{iconPack}}",
|
||||
"no_results": "Aucune icône trouvée."
|
||||
"no_results": "Aucune icône trouvée.",
|
||||
"search_placeholder_one": "Rechercher {{number}} icônes dans {{count}} packs",
|
||||
"search_placeholder_many": "Rechercher {{number}} icônes dans {{count}} packs",
|
||||
"search_placeholder_other": "Rechercher les icônes {{number}} dans les paquets {{count}}",
|
||||
"search_placeholder_filtered": "Rechercher {{number}} icônes dans {{name}}"
|
||||
},
|
||||
"basic_properties": {
|
||||
"note_type": "Type de note",
|
||||
@@ -1461,11 +1470,13 @@
|
||||
"import-into-note": "Importer dans la note",
|
||||
"apply-bulk-actions": "Appliquer des Actions groupées",
|
||||
"converted-to-attachments": "Les notes {{count}} ont été converties en pièces jointes.",
|
||||
"convert-to-attachment-confirm": "Êtes-vous sûr de vouloir convertir les notes sélectionnées en pièces jointes de leurs notes parentes ?",
|
||||
"convert-to-attachment-confirm": "Êtes-vous sûr de vouloir convertir les notes sélectionnées en pièces jointes de leurs notes parentales ? Cette opération s'applique uniquement aux notes d'image, les autres notes seront ignorées.",
|
||||
"archive": "Archive",
|
||||
"unarchive": "Désarchiver",
|
||||
"open-in-popup": "Modification rapide",
|
||||
"open-in-a-new-window": "Ouvrir dans une nouvelle fenêtre"
|
||||
"open-in-a-new-window": "Ouvrir dans une nouvelle fenêtre",
|
||||
"hide-subtree": "Masquer le sous-arbre",
|
||||
"show-subtree": "Afficher le sous-arbre"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Cette note est partagée publiquement sur {{- link}}.",
|
||||
@@ -1494,7 +1505,10 @@
|
||||
"task-list": "Liste de tâches",
|
||||
"book": "Collection",
|
||||
"new-feature": "Nouveau",
|
||||
"collections": "Collections"
|
||||
"collections": "Collections",
|
||||
"ai-chat": "Chat IA",
|
||||
"llm-chat": "Chat AI",
|
||||
"spreadsheet": "Feuille de calcul"
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-on": "Protéger la note",
|
||||
@@ -1982,7 +1996,9 @@
|
||||
"title": "Options expérimentales",
|
||||
"disclaimer": "Ces options sont expérimentales et peuvent provoquer une instabilité. Utilisez avec prudence.",
|
||||
"new_layout_name": "Nouvelle mise en page",
|
||||
"new_layout_description": "Essayez la nouvelle mise en page pour un look plus moderne et un usage améliorée. Sous réserve de changements importants dans les prochaines versions."
|
||||
"new_layout_description": "Essayez la nouvelle mise en page pour un look plus moderne et un usage améliorée. Sous réserve de changements importants dans les prochaines versions.",
|
||||
"llm_name": "AI / LLM Chat",
|
||||
"llm_description": "Activer la barre de chat AI et les notes de chat LLM alimentées par de grands modèles de langage."
|
||||
},
|
||||
"read-only-info": {
|
||||
"read-only-note": "Vous consultez actuellement une note en lecture seule.",
|
||||
@@ -2027,5 +2043,21 @@
|
||||
"invalid_url_message": "Insérer une adresse Web valide, par exemple https://triliumnotes.org.",
|
||||
"disabled_description": "Cette vue Web a été importée à partir d'une source externe. Pour vous protéger du phishing ou du contenu malveillant, elle ne se charge pas automatiquement. Vous pouvez l'activer si vous faites confiance à la source.",
|
||||
"disabled_button_enable": "Activer la vue Web"
|
||||
},
|
||||
"llm_chat": {
|
||||
"placeholder": "Tapez un message...",
|
||||
"send": "Envoyer",
|
||||
"sending": "Envoi...",
|
||||
"empty_state": "Démarrez une conversation en tapant un message ci-dessous.",
|
||||
"searching_web": "Recherche sur le Web...",
|
||||
"web_search": "Recherche sur le Web",
|
||||
"note_tools": "Accès aux notes",
|
||||
"sources": "Sources",
|
||||
"extended_thinking": "Réflexion étendue",
|
||||
"legacy_models": "Modèles hérités",
|
||||
"thinking": "Réflexion...",
|
||||
"thought_process": "Processus de réflexion",
|
||||
"tool_calls": "{{count}} appel(s) d'outil",
|
||||
"input": "Entrée"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1718,7 +1718,8 @@
|
||||
"new-feature": "Nuovo",
|
||||
"collections": "Collezioni",
|
||||
"ai-chat": "Chat con IA",
|
||||
"spreadsheet": "Foglio di calcolo"
|
||||
"spreadsheet": "Foglio di calcolo",
|
||||
"llm-chat": "Chat con IA"
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-on": "Proteggi la nota",
|
||||
@@ -2051,7 +2052,9 @@
|
||||
"title": "Opzioni sperimentali",
|
||||
"disclaimer": "Queste opzioni sono sperimentali e potrebbero causare instabilità. Usare con cautela.",
|
||||
"new_layout_name": "Nuovo layout",
|
||||
"new_layout_description": "Prova il nuovo layout per un look più moderno e una maggiore usabilità. Soggetto a modifiche significative nelle prossime versioni."
|
||||
"new_layout_description": "Prova il nuovo layout per un look più moderno e una maggiore usabilità. Soggetto a modifiche significative nelle prossime versioni.",
|
||||
"llm_name": "Chat con IA / LLM",
|
||||
"llm_description": "Attiva la barra laterale della chat con IA e le note della chat LLM basate su modelli linguistici di grandi dimensioni."
|
||||
},
|
||||
"server": {
|
||||
"unknown_http_error_title": "Errore di comunicazione con il server",
|
||||
@@ -2245,5 +2248,64 @@
|
||||
"sample_xy": "XY",
|
||||
"sample_venn": "Venn",
|
||||
"sample_ishikawa": "Ishikawa"
|
||||
},
|
||||
"llm_chat": {
|
||||
"placeholder": "Scrivi un messaggio...",
|
||||
"send": "Invia",
|
||||
"sending": "Invio in corso...",
|
||||
"empty_state": "Inizia una conversazione scrivendo un messaggio qui sotto.",
|
||||
"searching_web": "Ricerca sul web...",
|
||||
"web_search": "Ricerca sul web",
|
||||
"note_tools": "Nota di accesso",
|
||||
"sources": "Fonti",
|
||||
"extended_thinking": "Riflessioni approfondite",
|
||||
"legacy_models": "Modelli precedenti",
|
||||
"thinking": "Sto riflettendo...",
|
||||
"thought_process": "Processo mentale",
|
||||
"tool_calls": "{{count}} chiamata/e di funzione",
|
||||
"input": "Dati in ingresso",
|
||||
"result": "Risultato",
|
||||
"error": "Errore",
|
||||
"tool_error": "fallito",
|
||||
"total_tokens": "{{total}} gettoni",
|
||||
"tokens_detail": "{{prompt}} prompt + {{completion}} completamento",
|
||||
"tokens_used": "{{prompt}} prompt + {{completion}} completamento = {{total}} token",
|
||||
"tokens_used_with_cost": "{{prompt}} prompt + {{completion}} completamento = {{total}} token (~${{cost}})",
|
||||
"tokens_used_with_model": "{{model}}: {{prompt}} prompt + {{completion}} completamento = {{total}} token",
|
||||
"tokens_used_with_model_and_cost": "{{model}}: {{prompt}} prompt + {{completion}} completamento = {{total}} token (~${{cost}})",
|
||||
"tokens": "tokens",
|
||||
"context_used": "{{percentage}}% utilizzato",
|
||||
"note_context_enabled": "Clicca qui per disattivare il contesto della nota: {{title}}",
|
||||
"note_context_disabled": "Clicca per includere la nota corrente nel contesto",
|
||||
"no_provider_message": "Non è stato configurato alcun fornitore di IA. Aggiungine uno per iniziare a chattare.",
|
||||
"add_provider": "Aggiungi un fornitore di IA",
|
||||
"role_user": "Tu",
|
||||
"role_assistant": "Assistente"
|
||||
},
|
||||
"sidebar_chat": {
|
||||
"title": "Chat AI",
|
||||
"launcher_title": "Apri Chat AI",
|
||||
"new_chat": "Inizia una nuova chat",
|
||||
"save_chat": "Salva la chat negli appunti",
|
||||
"empty_state": "Avvia una conversazione",
|
||||
"history": "Cronologia delle chat",
|
||||
"recent_chats": "Conversazioni recenti",
|
||||
"no_chats": "Nessuna conversazione precedente"
|
||||
},
|
||||
"llm": {
|
||||
"settings_title": "AI / LLM",
|
||||
"settings_description": "Configurare le integrazioni con l'intelligenza artificiale e i modelli linguistici di grandi dimensioni.",
|
||||
"add_provider": "Aggiungi fornitore",
|
||||
"add_provider_title": "Aggiungi un fornitore di IA",
|
||||
"configured_providers": "Fornitori configurati",
|
||||
"no_providers_configured": "Non sono stati ancora configurati fornitori.",
|
||||
"provider_name": "Nome",
|
||||
"provider_type": "Fornitore",
|
||||
"actions": "Azioni",
|
||||
"delete_provider": "Elimina",
|
||||
"delete_provider_confirmation": "Sei sicuro di voler eliminare il provider \"{{name}}\"?",
|
||||
"api_key": "Chiave API",
|
||||
"api_key_placeholder": "Inserisci la tua chiave API",
|
||||
"cancel": "Annulla"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import "./MindMap.css";
|
||||
|
||||
// allow node-menu plugin css to be bundled by webpack
|
||||
import nodeMenu from "@mind-elixir/node-menu";
|
||||
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
|
||||
import { snapdom } from "@zumer/snapdom";
|
||||
import { DARK_THEME, default as VanillaMindElixir, MindElixirData, MindElixirInstance, Operation, Options, THEME as LIGHT_THEME } from "mind-elixir";
|
||||
import { t } from "i18next";
|
||||
import { DARK_THEME, default as VanillaMindElixir, MindElixirData, MindElixirInstance, Operation, THEME as LIGHT_THEME } from "mind-elixir";
|
||||
import type { LangPack } from "mind-elixir/i18n";
|
||||
import { HTMLAttributes, RefObject } from "preact";
|
||||
import { useCallback, useEffect, useRef } from "preact/hooks";
|
||||
|
||||
@@ -25,27 +26,22 @@ interface MindElixirProps {
|
||||
onChange?: () => void;
|
||||
}
|
||||
|
||||
const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, Options["locale"] | null> = {
|
||||
ar: null,
|
||||
cn: "zh_CN",
|
||||
de: null,
|
||||
en: "en",
|
||||
en_rtl: "en",
|
||||
"en-GB": "en",
|
||||
es: "es",
|
||||
fr: "fr",
|
||||
ga: null,
|
||||
it: "it",
|
||||
hi: null,
|
||||
ja: "ja",
|
||||
pt: "pt",
|
||||
pl: null,
|
||||
pt_br: "pt",
|
||||
ro: "ro",
|
||||
ru: "ru",
|
||||
tw: "zh_TW",
|
||||
uk: null
|
||||
};
|
||||
function buildMindElixirLangPack(): LangPack {
|
||||
return {
|
||||
addChild: t("mind-map.addChild"),
|
||||
addParent: t("mind-map.addParent"),
|
||||
addSibling: t("mind-map.addSibling"),
|
||||
removeNode: t("mind-map.removeNode"),
|
||||
focus: t("mind-map.focus"),
|
||||
cancelFocus: t("mind-map.cancelFocus"),
|
||||
moveUp: t("mind-map.moveUp"),
|
||||
moveDown: t("mind-map.moveDown"),
|
||||
link: t("mind-map.link"),
|
||||
linkBidirectional: t("mind-map.linkBidirectional"),
|
||||
clickTips: t("mind-map.clickTips"),
|
||||
summary: t("mind-map.summary")
|
||||
};
|
||||
}
|
||||
|
||||
export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
|
||||
const apiRef = useRef<MindElixirInstance>(null);
|
||||
@@ -161,8 +157,8 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef
|
||||
|
||||
const mind = new VanillaMindElixir({
|
||||
el: containerRef.current,
|
||||
locale: LOCALE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined,
|
||||
editable,
|
||||
contextMenu: { locale: buildMindElixirLangPack() },
|
||||
theme: defaultColorScheme.current === "dark" ? DARK_THEME : LIGHT_THEME
|
||||
});
|
||||
|
||||
|
||||
@@ -239,8 +239,10 @@ export function useLlmChat(
|
||||
.join("")
|
||||
}));
|
||||
|
||||
const selectedModelProvider = availableModels.find(m => m.id === selectedModel)?.provider;
|
||||
const streamOptions: Parameters<typeof streamChatCompletion>[1] = {
|
||||
model: selectedModel || undefined,
|
||||
provider: selectedModelProvider,
|
||||
enableWebSearch,
|
||||
enableNoteTools,
|
||||
contextNoteId,
|
||||
|
||||
@@ -19,7 +19,9 @@ export interface ProviderType {
|
||||
}
|
||||
|
||||
export const PROVIDER_TYPES: ProviderType[] = [
|
||||
{ id: "anthropic", name: "Anthropic" }
|
||||
{ id: "anthropic", name: "Anthropic" },
|
||||
{ id: "openai", name: "OpenAI" },
|
||||
{ id: "google", name: "Google Gemini" }
|
||||
];
|
||||
|
||||
interface AddProviderModalProps {
|
||||
|
||||
@@ -30,8 +30,10 @@
|
||||
"proxy-nginx-subdir": "docker run --name trilium-nginx-subdir --rm --network=host -v ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro nginx:latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.0",
|
||||
"ai": "^5.0.0",
|
||||
"@ai-sdk/anthropic": "3.0.64",
|
||||
"@ai-sdk/google": "3.0.54",
|
||||
"@ai-sdk/openai": "3.0.49",
|
||||
"ai": "6.0.142",
|
||||
"better-sqlite3": "12.8.0",
|
||||
"html-to-text": "9.0.5",
|
||||
"node-html-parser": "7.1.0",
|
||||
@@ -92,7 +94,7 @@
|
||||
"express": "5.2.1",
|
||||
"express-http-proxy": "2.1.2",
|
||||
"express-openid-connect": "2.20.1",
|
||||
"express-rate-limit": "8.3.1",
|
||||
"express-rate-limit": "8.3.2",
|
||||
"express-session": "1.19.0",
|
||||
"file-uri-to-path": "2.0.0",
|
||||
"fs-extra": "11.3.4",
|
||||
|
||||
156
apps/server/src/assets/llm/skills/backend_scripting.md
Normal file
156
apps/server/src/assets/llm/skills/backend_scripting.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Trilium Backend Scripting
|
||||
|
||||
Backend scripts run in Node.js on the server. They have direct access to notes in memory and can interact with the system (files, processes).
|
||||
|
||||
## Creating a backend script
|
||||
|
||||
1. Create a Code note with language "JS backend".
|
||||
2. The script can be run manually (Execute button) or triggered automatically.
|
||||
|
||||
## Script API (`api` global)
|
||||
|
||||
### Note retrieval
|
||||
- `api.getNote(noteId)` - get note by ID
|
||||
- `api.searchForNotes(query, searchParams)` - search notes (returns array)
|
||||
- `api.searchForNote(query)` - search notes (returns first match)
|
||||
- `api.getNotesWithLabel(name, value?)` - find notes by label
|
||||
- `api.getNoteWithLabel(name, value?)` - find first note by label
|
||||
- `api.getBranch(branchId)` - get branch by ID
|
||||
- `api.getAttribute(attributeId)` - get attribute by ID
|
||||
|
||||
### Note creation
|
||||
- `api.createTextNote(parentNoteId, title, content)` - create text note
|
||||
- `api.createDataNote(parentNoteId, title, content)` - create JSON note
|
||||
- `api.createNewNote({ parentNoteId, title, content, type })` - create note with full options
|
||||
|
||||
### Branch management
|
||||
- `api.ensureNoteIsPresentInParent(noteId, parentNoteId, prefix?)` - create or reuse branch
|
||||
- `api.ensureNoteIsAbsentFromParent(noteId, parentNoteId)` - remove branch if exists
|
||||
- `api.toggleNoteInParent(present, noteId, parentNoteId, prefix?)` - toggle branch
|
||||
|
||||
### Calendar/date notes
|
||||
- `api.getTodayNote()` - get/create today's day note
|
||||
- `api.getDayNote(date)` - get/create day note (YYYY-MM-DD)
|
||||
- `api.getWeekNote(date)` - get/create week note
|
||||
- `api.getMonthNote(date)` - get/create month note (YYYY-MM)
|
||||
- `api.getYearNote(year)` - get/create year note (YYYY)
|
||||
|
||||
### Utilities
|
||||
- `api.log(message)` - log to Trilium logs and UI
|
||||
- `api.randomString(length)` - generate random string
|
||||
- `api.escapeHtml(string)` / `api.unescapeHtml(string)`
|
||||
- `api.getInstanceName()` - get instance name
|
||||
- `api.getAppInfo()` - get application info
|
||||
|
||||
### Libraries
|
||||
- `api.axios` - HTTP client
|
||||
- `api.dayjs` - date manipulation
|
||||
- `api.xml2js` - XML parser
|
||||
- `api.cheerio` - HTML/XML parser
|
||||
|
||||
### Advanced
|
||||
- `api.transactional(func)` - wrap code in a database transaction
|
||||
- `api.sql` - direct SQL access
|
||||
- `api.sortNotes(parentNoteId, sortConfig)` - sort child notes
|
||||
- `api.runOnFrontend(script, params)` - execute code on all connected frontends
|
||||
- `api.backupNow(backupName)` - create a backup
|
||||
- `api.exportSubtreeToZipFile(noteId, format, zipFilePath)` - export subtree (format: "markdown" or "html")
|
||||
- `api.duplicateSubtree(origNoteId, newParentNoteId)` - clone note and children
|
||||
|
||||
## BNote object
|
||||
|
||||
Available on notes returned from API methods (`api.getNote()`, `api.originEntity`, etc.).
|
||||
|
||||
### Content
|
||||
- `note.getContent()` / `note.setContent(content)`
|
||||
- `note.getJsonContent()` / `note.setJsonContent(obj)`
|
||||
- `note.getJsonContentSafely()` - returns null on parse error
|
||||
|
||||
### Properties
|
||||
- `note.noteId`, `note.title`, `note.type`, `note.mime`
|
||||
- `note.dateCreated`, `note.dateModified`
|
||||
- `note.isProtected`, `note.isArchived`
|
||||
|
||||
### Hierarchy
|
||||
- `note.getParentNotes()` / `note.getChildNotes()`
|
||||
- `note.getParentBranches()` / `note.getChildBranches()`
|
||||
- `note.hasChildren()`, `note.getAncestors()`
|
||||
- `note.getSubtreeNoteIds()` - all descendant IDs
|
||||
- `note.hasAncestor(ancestorNoteId)`
|
||||
|
||||
### Attributes (including inherited)
|
||||
- `note.getLabels(name?)` / `note.getLabelValue(name)`
|
||||
- `note.getRelations(name?)` / `note.getRelation(name)`
|
||||
- `note.hasLabel(name, value?)` / `note.hasRelation(name, value?)`
|
||||
|
||||
### Attribute modification
|
||||
- `note.setLabel(name, value?)` / `note.removeLabel(name, value?)`
|
||||
- `note.setRelation(name, targetNoteId)` / `note.removeRelation(name, value?)`
|
||||
- `note.addLabel(name, value?, isInheritable?)` / `note.addRelation(name, targetNoteId, isInheritable?)`
|
||||
- `note.toggleLabel(enabled, name, value?)`
|
||||
|
||||
### Operations
|
||||
- `note.save()` - persist changes
|
||||
- `note.deleteNote()` - soft delete
|
||||
- `note.cloneTo(parentNoteId)` - clone to another parent
|
||||
|
||||
### Type checks
|
||||
- `note.isJson()`, `note.isJavaScript()`, `note.isHtml()`, `note.isImage()`
|
||||
- `note.hasStringContent()` - true if not binary
|
||||
|
||||
## Events and triggers
|
||||
|
||||
### Global events (via `#run` label on the script note)
|
||||
- `#run=backendStartup` - run when server starts
|
||||
- `#run=hourly` - run once per hour (use `#runAtHour=N` to specify which hours)
|
||||
- `#run=daily` - run once per day
|
||||
|
||||
### Entity events (via relation from the entity to the script note)
|
||||
These are defined as relations. `api.originEntity` contains the entity that triggered the event.
|
||||
|
||||
| Relation | Trigger | originEntity |
|
||||
|---|---|---|
|
||||
| `~runOnNoteCreation` | note created | BNote |
|
||||
| `~runOnChildNoteCreation` | child note created under this note | BNote (child) |
|
||||
| `~runOnNoteTitleChange` | note title changed | BNote |
|
||||
| `~runOnNoteContentChange` | note content changed | BNote |
|
||||
| `~runOnNoteChange` | note metadata changed (not content) | BNote |
|
||||
| `~runOnNoteDeletion` | note deleted | BNote |
|
||||
| `~runOnBranchCreation` | branch created (clone/move) | BBranch |
|
||||
| `~runOnBranchChange` | branch updated | BBranch |
|
||||
| `~runOnBranchDeletion` | branch deleted | BBranch |
|
||||
| `~runOnAttributeCreation` | attribute created on this note | BAttribute |
|
||||
| `~runOnAttributeChange` | attribute changed/deleted on this note | BAttribute |
|
||||
|
||||
Relations can be inheritable — when set, they apply to all descendant notes.
|
||||
|
||||
## Example: auto-color notes by category
|
||||
|
||||
```javascript
|
||||
// Attach via ~runOnAttributeChange relation
|
||||
const attr = api.originEntity;
|
||||
if (attr.name !== "mycategory") return;
|
||||
const note = api.getNote(attr.noteId);
|
||||
if (attr.value === "Health") {
|
||||
note.setLabel("color", "green");
|
||||
} else {
|
||||
note.removeLabel("color");
|
||||
}
|
||||
```
|
||||
|
||||
## Example: create a daily summary
|
||||
|
||||
```javascript
|
||||
// Attach #run=daily label
|
||||
const today = api.getTodayNote();
|
||||
const tasks = api.searchForNotes('#task #!completed');
|
||||
let summary = "## Open Tasks\n";
|
||||
for (const task of tasks) {
|
||||
summary += `- ${task.title}\n`;
|
||||
}
|
||||
api.createTextNote(today.noteId, "Daily Summary", summary);
|
||||
```
|
||||
|
||||
## Module system
|
||||
|
||||
Child notes of a script act as modules. Export with `module.exports = ...` and import via function parameters matching the child note title, or use `require('noteName')`.
|
||||
240
apps/server/src/assets/llm/skills/frontend_scripting.md
Normal file
240
apps/server/src/assets/llm/skills/frontend_scripting.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# Trilium Frontend Scripting
|
||||
|
||||
Frontend scripts run in the browser. They can manipulate the UI, navigate notes, show dialogs, and create custom widgets.
|
||||
|
||||
IMPORTANT: Always prefer Preact JSX widgets over legacy jQuery widgets. Use JSX code notes with `import`/`export` syntax.
|
||||
|
||||
CRITICAL: In JSX notes, always use top-level `import` statements (e.g. `import { useState } from "trilium:preact"`). NEVER use dynamic `await import()` for Preact imports — this will break hooks and components. Dynamic imports are not needed because JSX notes natively support ES module `import`/`export` syntax.
|
||||
|
||||
## Creating a frontend script
|
||||
|
||||
1. Create a Code note with language "JSX" (preferred) or "JS frontend" (legacy only).
|
||||
2. Add `#widget` label for widgets, or `#run=frontendStartup` for auto-run scripts.
|
||||
3. For mobile, use `#run=mobileStartup` instead.
|
||||
|
||||
## Script types
|
||||
|
||||
| Type | Language | Required attribute |
|
||||
|---|---|---|
|
||||
| Custom widget | JSX (preferred) | `#widget` |
|
||||
| Regular script | JS frontend | `#run=frontendStartup` (optional) |
|
||||
| Render note | JSX | None (used via `~renderNote` relation) |
|
||||
|
||||
## Custom widgets (Preact JSX) — preferred
|
||||
|
||||
### Basic widget
|
||||
|
||||
```jsx
|
||||
import { defineWidget } from "trilium:preact";
|
||||
import { useState } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "center-pane",
|
||||
position: 10,
|
||||
render: () => {
|
||||
const [count, setCount] = useState(0);
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setCount(c => c + 1)}>
|
||||
Clicked {count} times
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Note context aware widget (reacts to active note)
|
||||
|
||||
```jsx
|
||||
import { defineWidget, useNoteContext, useNoteProperty } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "note-detail-pane",
|
||||
position: 10,
|
||||
render: () => {
|
||||
const { note } = useNoteContext();
|
||||
const title = useNoteProperty(note, "title");
|
||||
return <span>Current note: {title}</span>;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Right panel widget (sidebar)
|
||||
|
||||
```jsx
|
||||
import { defineWidget, RightPanelWidget, useState, useEffect } from "trilium:preact";
|
||||
|
||||
export default defineWidget({
|
||||
parent: "right-pane",
|
||||
position: 1,
|
||||
render() {
|
||||
const [time, setTime] = useState();
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTime(new Date().toLocaleString());
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
return (
|
||||
<RightPanelWidget id="my-clock" title="Clock">
|
||||
<p>The time is: {time}</p>
|
||||
</RightPanelWidget>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Widget locations (`parent` values)
|
||||
|
||||
| Value | Description | Notes |
|
||||
|---|---|---|
|
||||
| `left-pane` | Alongside the note tree | |
|
||||
| `center-pane` | Content area, spanning all splits | |
|
||||
| `note-detail-pane` | Inside a note, split-aware | Use `useNoteContext()` hook |
|
||||
| `right-pane` | Right sidebar section | Wrap in `<RightPanelWidget>` |
|
||||
|
||||
### Preact imports
|
||||
|
||||
```jsx
|
||||
// API methods
|
||||
import { showMessage, showError, getNote, searchForNotes, activateNote,
|
||||
runOnBackend, getActiveContextNote } from "trilium:api";
|
||||
|
||||
// Hooks and components
|
||||
import { defineWidget, defineLauncherWidget,
|
||||
useState, useEffect, useCallback, useMemo, useRef,
|
||||
useNoteContext, useActiveNoteContext, useNoteProperty,
|
||||
RightPanelWidget } from "trilium:preact";
|
||||
|
||||
// Built-in UI components
|
||||
import { ActionButton, Button, LinkButton, Modal,
|
||||
NoteAutocomplete, FormTextBox, FormToggle, FormCheckbox,
|
||||
FormDropdownList, FormGroup, FormText, FormTextArea,
|
||||
Icon, LoadingSpinner, Slider, Collapsible } from "trilium:preact";
|
||||
```
|
||||
|
||||
### Custom hooks
|
||||
|
||||
- `useNoteContext()` - returns `{ note }` for the current note context (use in `note-detail-pane`)
|
||||
- `useActiveNoteContext()` - returns `{ note, noteId }` for the active note (works from any widget location)
|
||||
- `useNoteProperty(note, propName)` - reactively watches a note property (e.g. "title", "type")
|
||||
|
||||
### Render notes (JSX)
|
||||
|
||||
For rendering custom content inside a note:
|
||||
1. Create a "render note" (type: Render Note) where you want the content to appear.
|
||||
2. Create a JSX code note **as a child** of the render note, exporting a default component.
|
||||
3. On the render note, add a `~renderNote` relation pointing to the child JSX note.
|
||||
|
||||
IMPORTANT: Always create the JSX code note as a child of the render note, not as a sibling or at the root. This keeps them organized together.
|
||||
|
||||
```jsx
|
||||
export default function MyRenderNote() {
|
||||
return (
|
||||
<>
|
||||
<h1>Custom rendered content</h1>
|
||||
<p>This appears inside the note.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Script API
|
||||
|
||||
In JSX, use `import { method } from "trilium:api"`. In JS frontend, use the `api` global.
|
||||
|
||||
### Navigation & tabs
|
||||
- `activateNote(notePath)` - navigate to a note
|
||||
- `activateNewNote(notePath)` - navigate and wait for sync
|
||||
- `openTabWithNote(notePath, activate?)` - open in new tab
|
||||
- `openSplitWithNote(notePath, activate?)` - open in new split
|
||||
- `getActiveContextNote()` - get currently active note
|
||||
- `getActiveContextNotePath()` - get path of active note
|
||||
- `setHoistedNoteId(noteId)` - hoist/unhoist note
|
||||
|
||||
### Note access & search
|
||||
- `getNote(noteId)` - get note by ID
|
||||
- `getNotes(noteIds)` - bulk fetch notes
|
||||
- `searchForNotes(searchString)` - search with full query syntax
|
||||
- `searchForNote(searchString)` - search returning first result
|
||||
|
||||
### Calendar/date notes
|
||||
- `getTodayNote()` - get/create today's note
|
||||
- `getDayNote(date)` / `getWeekNote(date)` / `getMonthNote(month)` / `getYearNote(year)`
|
||||
|
||||
### Editor access
|
||||
- `getActiveContextTextEditor()` - get CKEditor instance
|
||||
- `getActiveContextCodeEditor()` - get CodeMirror instance
|
||||
- `addTextToActiveContextEditor(text)` - insert text into active editor
|
||||
|
||||
### Dialogs & notifications
|
||||
- `showMessage(msg)` - info toast
|
||||
- `showError(msg)` - error toast
|
||||
- `showConfirmDialog(msg)` - confirm dialog (returns boolean)
|
||||
- `showPromptDialog(msg)` - prompt dialog (returns user input)
|
||||
|
||||
### Backend integration
|
||||
- `runOnBackend(func, params)` - execute a function on the backend
|
||||
|
||||
### UI interaction
|
||||
- `triggerCommand(name, data)` - trigger a command
|
||||
- `bindGlobalShortcut(shortcut, handler, namespace?)` - add keyboard shortcut
|
||||
|
||||
### Utilities
|
||||
- `formatDateISO(date)` - format as YYYY-MM-DD
|
||||
- `randomString(length)` - generate random string
|
||||
- `dayjs` - day.js library
|
||||
- `log(message)` - log to script log pane
|
||||
|
||||
## FNote object
|
||||
|
||||
Available via `getNote()`, `getActiveContextNote()`, `useNoteContext()`, etc.
|
||||
|
||||
### Properties
|
||||
- `note.noteId`, `note.title`, `note.type`, `note.mime`
|
||||
- `note.isProtected`, `note.isArchived`
|
||||
|
||||
### Content
|
||||
- `note.getContent()` - get note content
|
||||
- `note.getJsonContent()` - parse content as JSON
|
||||
|
||||
### Hierarchy
|
||||
- `note.getParentNotes()` / `note.getChildNotes()`
|
||||
- `note.hasChildren()`, `note.getSubtreeNoteIds()`
|
||||
|
||||
### Attributes
|
||||
- `note.getAttributes(type?, name?)` - all attributes (including inherited)
|
||||
- `note.getOwnedAttributes(type?, name?)` - only owned attributes
|
||||
- `note.hasAttribute(type, name)` - check for attribute
|
||||
|
||||
## Legacy jQuery widgets (avoid if possible)
|
||||
|
||||
Only use legacy widgets if you specifically need jQuery or cannot use JSX.
|
||||
|
||||
```javascript
|
||||
// Language: JS frontend, Label: #widget
|
||||
class MyWidget extends api.BasicWidget {
|
||||
get position() { return 1; }
|
||||
get parentWidget() { return "center-pane"; }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $("<div>");
|
||||
this.$widget.append($("<button>Click me</button>")
|
||||
.on("click", () => api.showMessage("Hello!")));
|
||||
return this.$widget;
|
||||
}
|
||||
}
|
||||
module.exports = new MyWidget();
|
||||
```
|
||||
|
||||
Key differences from Preact:
|
||||
- Use `api.` global instead of imports
|
||||
- `get parentWidget()` instead of `parent` field
|
||||
- `module.exports = new MyWidget()` (instance) for most widgets
|
||||
- `module.exports = MyWidget` (class, no `new`) for `note-detail-pane`
|
||||
- Right pane: extend `api.RightPanelWidget`, override `doRenderBody()` instead of `doRender()`
|
||||
|
||||
## Module system
|
||||
|
||||
For JSX, use `import`/`export` syntax between notes. For JS frontend, use `module.exports` and function parameters matching child note titles.
|
||||
50
apps/server/src/assets/llm/skills/search_syntax.md
Normal file
50
apps/server/src/assets/llm/skills/search_syntax.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Trilium Search Syntax
|
||||
|
||||
## Full-text search
|
||||
- `rings tolkien` — notes containing both words
|
||||
- `"The Lord of the Rings"` — exact phrase match
|
||||
|
||||
## Label filters
|
||||
- `#book` — notes with the "book" label
|
||||
- `#!book` — notes WITHOUT the "book" label
|
||||
- `#publicationYear = 1954` — exact value
|
||||
- `#genre *=* fan` — contains substring
|
||||
- `#title =* The` — starts with
|
||||
- `#title *= Rings` — ends with
|
||||
- `#publicationYear >= 1950` — numeric comparison (>, >=, <, <=)
|
||||
- `#dateNote >= TODAY-30` — date keywords: NOW+-seconds, TODAY+-days, MONTH+-months, YEAR+-years
|
||||
- `#phone %= '\d{3}-\d{4}'` — regex match
|
||||
- `#title ~= trilim` — fuzzy exact match (tolerates typos, min 3 chars)
|
||||
- `#content ~* progra` — fuzzy contains match
|
||||
|
||||
## Relation filters
|
||||
- `~author` — notes with an "author" relation
|
||||
- `~author.title *=* Tolkien` — relation target's title contains "Tolkien"
|
||||
- `~author.relations.son.title = 'Christopher Tolkien'` — deep relation traversal
|
||||
|
||||
## Note properties
|
||||
Access via `note.` prefix: noteId, title, type, mime, text, content, rawContent, dateCreated, dateModified, isProtected, isArchived, parentCount, childrenCount, attributeCount, labelCount, relationCount, contentSize, revisionCount.
|
||||
- `note.type = code AND note.mime = 'application/json'`
|
||||
- `note.content *=* searchTerm`
|
||||
|
||||
## Hierarchy
|
||||
- `note.parents.title = 'Books'` — parent named "Books"
|
||||
- `note.ancestors.title = 'Books'` — any ancestor named "Books"
|
||||
- `note.children.title = 'sub-note'` — child named "sub-note"
|
||||
|
||||
## Boolean logic
|
||||
- AND: `#book AND #fantasy` (implicit between adjacent expressions)
|
||||
- OR: `#book OR #author`
|
||||
- NOT: `not(note.ancestors.title = 'Tolkien')`
|
||||
- Parentheses: `(#genre = "fantasy" AND #year >= 1950) OR #award`
|
||||
|
||||
## Combining full-text and attributes
|
||||
- `towers #book` — full-text "towers" AND has #book label
|
||||
- `tolkien #book or #author` — full-text with OR on labels
|
||||
|
||||
## Ordering and limiting
|
||||
- `#author=Tolkien orderBy #publicationDate desc, note.title limit 10`
|
||||
|
||||
## Escaping
|
||||
- `\#hash` — literal # in full-text
|
||||
- Three quote types: single, double, backtick
|
||||
@@ -1,440 +1,445 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Ouvrir la boîte de dialogue \"Aller à la note\"",
|
||||
"search-in-subtree": "Rechercher des notes dans les sous-arbres de la note active",
|
||||
"expand-subtree": "Développer le sous-arbre de la note actuelle",
|
||||
"collapse-tree": "Réduire toute l'arborescence des notes",
|
||||
"collapse-subtree": "Réduire le sous-arbre de la note actuelle",
|
||||
"sort-child-notes": "Trier les notes enfants",
|
||||
"creating-and-moving-notes": "Créer et déplacer des notes",
|
||||
"create-note-into-inbox": "Créer une note dans l'emplacement par défaut (si défini) ou une note journalière",
|
||||
"delete-note": "Supprimer la note",
|
||||
"move-note-up": "Déplacer la note vers le haut",
|
||||
"move-note-down": "Déplacer la note vers le bas",
|
||||
"move-note-up-in-hierarchy": "Déplacer la note vers le haut dans la hiérarchie",
|
||||
"move-note-down-in-hierarchy": "Déplacer la note vers le bas dans la hiérarchie",
|
||||
"edit-note-title": "Passer de l'arborescence aux détails d'une note et éditer le titre",
|
||||
"edit-branch-prefix": "Afficher la fenêtre Éditer le préfixe de branche",
|
||||
"note-clipboard": "Note presse-papiers",
|
||||
"copy-notes-to-clipboard": "Copier les notes sélectionnées dans le presse-papiers",
|
||||
"paste-notes-from-clipboard": "Coller les notes depuis le presse-papiers dans la note active",
|
||||
"cut-notes-to-clipboard": "Couper les notes sélectionnées dans le presse-papiers",
|
||||
"select-all-notes-in-parent": "Sélectionner toutes les notes du niveau de la note active",
|
||||
"add-note-above-to-the-selection": "Ajouter la note au-dessus de la sélection",
|
||||
"add-note-below-to-selection": "Ajouter la note en dessous de la sélection",
|
||||
"duplicate-subtree": "Dupliquer le sous-arbre",
|
||||
"tabs-and-windows": "Onglets et fenêtres",
|
||||
"open-new-tab": "Ouvrir un nouvel onglet",
|
||||
"close-active-tab": "Fermer l'onglet actif",
|
||||
"reopen-last-tab": "Rouvrir le dernier onglet fermé",
|
||||
"activate-next-tab": "Basculer vers l'onglet à droite de l'onglet actif",
|
||||
"activate-previous-tab": "Basculer vers l'onglet à gauche de l'onglet actif",
|
||||
"open-new-window": "Ouvrir une nouvelle fenêtre vide",
|
||||
"toggle-tray": "Afficher/masquer l'application dans la barre des tâches",
|
||||
"first-tab": "Basculer vers le premier onglet dans la liste",
|
||||
"second-tab": "Basculer vers le deuxième onglet dans la liste",
|
||||
"third-tab": "Basculer vers le troisième onglet dans la liste",
|
||||
"fourth-tab": "Basculer vers le quatrième onglet dans la liste",
|
||||
"fifth-tab": "Basculer vers le cinquième onglet dans la liste",
|
||||
"sixth-tab": "Basculer vers le sixième onglet dans la liste",
|
||||
"seventh-tab": "Basculer vers le septième onglet dans la liste",
|
||||
"eight-tab": "Basculer vers le huitième onglet dans la liste",
|
||||
"ninth-tab": "Basculer vers le neuvième onglet dans la liste",
|
||||
"last-tab": "Basculer vers le dernier onglet dans la liste",
|
||||
"dialogs": "Boîtes de dialogue",
|
||||
"show-note-source": "Affiche la boîte de dialogue Source de la note",
|
||||
"show-options": "Afficher les Options",
|
||||
"show-revisions": "Afficher la boîte de dialogue Versions de la note",
|
||||
"show-recent-changes": "Afficher la boîte de dialogue Modifications récentes",
|
||||
"show-sql-console": "Afficher la boîte de dialogue Console SQL",
|
||||
"show-backend-log": "Afficher la boîte de dialogue Journal du backend",
|
||||
"text-note-operations": "Opérations sur les notes textuelles",
|
||||
"add-link-to-text": "Ouvrir la boîte de dialogue pour ajouter un lien dans le texte",
|
||||
"follow-link-under-cursor": "Suivre le lien sous le curseur",
|
||||
"insert-date-and-time-to-text": "Insérer la date et l'heure dans le texte",
|
||||
"paste-markdown-into-text": "Coller du texte au format Markdown dans la note depuis le presse-papiers",
|
||||
"cut-into-note": "Couper la sélection depuis la note actuelle et créer une sous-note avec le texte sélectionné",
|
||||
"add-include-note-to-text": "Ouvrir la boîte de dialogue pour Inclure une note",
|
||||
"edit-readonly-note": "Éditer une note en lecture seule",
|
||||
"attributes-labels-and-relations": "Attributs (labels et relations)",
|
||||
"add-new-label": "Créer un nouveau label",
|
||||
"create-new-relation": "Créer une nouvelle relation",
|
||||
"ribbon-tabs": "Onglets du ruban",
|
||||
"toggle-basic-properties": "Afficher/masquer les Propriétés de base de la note",
|
||||
"toggle-file-properties": "Afficher/masquer les Propriétés du fichier",
|
||||
"toggle-image-properties": "Afficher/masquer les Propriétés de l'image",
|
||||
"toggle-owned-attributes": "Afficher/masquer les Attributs propres",
|
||||
"toggle-inherited-attributes": "Afficher/masquer les Attributs hérités",
|
||||
"toggle-promoted-attributes": "Afficher/masquer les Attributs promus",
|
||||
"toggle-link-map": "Afficher/masquer la Carte de la note",
|
||||
"toggle-note-info": "Afficher/masquer les Informations de la note",
|
||||
"toggle-note-paths": "Afficher/masquer les Emplacements de la note",
|
||||
"toggle-similar-notes": "Afficher/masquer les Notes similaires",
|
||||
"other": "Autre",
|
||||
"toggle-right-pane": "Afficher/masquer le volet droit, qui inclut la Table des matières et les Accentuations",
|
||||
"print-active-note": "Imprimer la note active",
|
||||
"open-note-externally": "Ouvrir la note comme fichier avec l'application par défaut",
|
||||
"render-active-note": "Rendre (ou re-rendre) la note active",
|
||||
"run-active-note": "Exécuter le code JavaScript (frontend/backend) de la note active",
|
||||
"toggle-note-hoisting": "Activer le focus sur la note active",
|
||||
"unhoist": "Désactiver tout focus",
|
||||
"reload-frontend-app": "Recharger l'application",
|
||||
"open-dev-tools": "Ouvrir les outils de développement",
|
||||
"toggle-left-note-tree-panel": "Basculer le panneau gauche (arborescence des notes)",
|
||||
"toggle-full-screen": "Basculer en plein écran",
|
||||
"zoom-out": "Dézoomer",
|
||||
"zoom-in": "Zoomer",
|
||||
"note-navigation": "Navigation dans les notes",
|
||||
"reset-zoom-level": "Réinitialiser le niveau de zoom",
|
||||
"copy-without-formatting": "Copier le texte sélectionné sans mise en forme",
|
||||
"force-save-revision": "Forcer la création / sauvegarde d'une nouvelle version de la note active",
|
||||
"show-help": "Affiche le guide de l'utilisateur intégré",
|
||||
"toggle-book-properties": "Afficher/masquer les Propriétés du Livre",
|
||||
"toggle-classic-editor-toolbar": "Activer/désactiver l'onglet Mise en forme de l'éditeur avec la barre d'outils fixe",
|
||||
"export-as-pdf": "Exporte la note actuelle en PDF",
|
||||
"show-cheatsheet": "Affiche une fenêtre modale avec des opérations de clavier courantes",
|
||||
"toggle-zen-mode": "Active/désactive le mode zen (interface réduite pour favoriser la concentration)",
|
||||
"back-in-note-history": "Naviguer à la note précédente dans l'historique",
|
||||
"forward-in-note-history": "Naviguer a la note suivante dans l'historique",
|
||||
"open-command-palette": "Ouvrir la palette de commandes",
|
||||
"clone-notes-to": "Cloner les nœuds sélectionnés",
|
||||
"move-notes-to": "Déplacer les nœuds sélectionnés",
|
||||
"scroll-to-active-note": "Faire défiler l’arborescence des notes jusqu’à la note active",
|
||||
"quick-search": "Activer la barre de recherche rapide",
|
||||
"create-note-after": "Créer une note après la note active",
|
||||
"create-note-into": "Créer une note enfant de la note active",
|
||||
"find-in-text": "Afficher/Masquer le panneau de recherche"
|
||||
},
|
||||
"login": {
|
||||
"title": "Connexion",
|
||||
"heading": "Connexion à Trilium",
|
||||
"incorrect-password": "Le mot de passe est incorrect. Veuillez réessayer.",
|
||||
"password": "Mot de passe",
|
||||
"remember-me": "Se souvenir de moi",
|
||||
"button": "Connexion",
|
||||
"sign_in_with_sso": "Se connecter avec {{ ssoIssuerName }}",
|
||||
"incorrect-totp": "TOTP incorrect. Veuillez réessayer."
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Définir un mot de passe",
|
||||
"heading": "Définir un mot de passe",
|
||||
"description": "Avant de pouvoir commencer à utiliser Trilium depuis le web, vous devez d'abord définir un mot de passe. Vous utiliserez ensuite ce mot de passe pour vous connecter.",
|
||||
"password": "Mot de passe",
|
||||
"password-confirmation": "Confirmation du mot de passe",
|
||||
"button": "Définir le mot de passe"
|
||||
},
|
||||
"setup": {
|
||||
"heading": "Configuration de Trilium Notes",
|
||||
"new-document": "Je suis un nouvel utilisateur et je souhaite créer un nouveau document Trilium pour mes notes",
|
||||
"sync-from-desktop": "J'ai déjà l'application de bureau et je souhaite configurer la synchronisation avec celle-ci",
|
||||
"sync-from-server": "J'ai déjà un serveur et je souhaite configurer la synchronisation avec celui-ci",
|
||||
"next": "Suivant",
|
||||
"init-in-progress": "Initialisation du document en cours",
|
||||
"redirecting": "Vous serez bientôt redirigé vers l'application.",
|
||||
"title": "Configuration"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Synchroniser depuis une application de bureau",
|
||||
"description": "Cette procédure doit être réalisée depuis l'application de bureau :",
|
||||
"step1": "Ouvrez l'application Trilium Notes.",
|
||||
"step2": "Dans le menu Trilium, cliquez sur Options.",
|
||||
"step3": "Cliquez sur la catégorie Synchroniser.",
|
||||
"step4": "Remplacez l'adresse de l'instance de serveur par : {{- host}} et cliquez sur Enregistrer.",
|
||||
"step5": "Cliquez sur le bouton 'Tester la synchronisation' pour vérifier que la connexion fonctionne.",
|
||||
"step6": "Une fois que vous avez terminé ces étapes, cliquez sur {{- link}}.",
|
||||
"step6-here": "ici"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Synchroniser depuis le serveur",
|
||||
"instructions": "Veuillez saisir l'adresse du serveur Trilium et les informations d'identification ci-dessous. Cela téléchargera l'intégralité du document Trilium à partir du serveur et configurera la synchronisation avec celui-ci. En fonction de la taille du document et de votre vitesse de connexion, cela peut prendre un plusieurs minutes.",
|
||||
"server-host": "Adresse du serveur Trilium",
|
||||
"server-host-placeholder": "https://<nom d'hôte>:<port>",
|
||||
"proxy-server": "Serveur proxy (facultatif)",
|
||||
"proxy-server-placeholder": "https://<nom d'hôte>:<port>",
|
||||
"note": "Note :",
|
||||
"proxy-instruction": "Si vous laissez le paramètre de proxy vide, le proxy du système sera utilisé (s'applique uniquement à l'application de bureau)",
|
||||
"password": "Mot de passe",
|
||||
"password-placeholder": "Mot de passe",
|
||||
"back": "Retour",
|
||||
"finish-setup": "Terminer"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Synchronisation en cours",
|
||||
"successful": "La synchronisation a été correctement configurée. La synchronisation initiale prendra un certain temps. Une fois terminée, vous serez redirigé vers la page de connexion.",
|
||||
"outstanding-items": "Éléments de synchronisation exceptionnels :",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Page non trouvée",
|
||||
"heading": "Page non trouvée"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "parent :",
|
||||
"clipped-from": "Cette note a été initialement extraite de {{- url}}",
|
||||
"child-notes": "Notes enfants :",
|
||||
"no-content": "Cette note n'a aucun contenu."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Lundi",
|
||||
"tuesday": "Mardi",
|
||||
"wednesday": "Mercredi",
|
||||
"thursday": "Jeudi",
|
||||
"friday": "Vendredi",
|
||||
"saturday": "Samedi",
|
||||
"sunday": "Dimanche"
|
||||
},
|
||||
"months": {
|
||||
"january": "Janvier",
|
||||
"february": "Février",
|
||||
"march": "Mars",
|
||||
"april": "Avril",
|
||||
"may": "Mai",
|
||||
"june": "Juin",
|
||||
"july": "Juillet",
|
||||
"august": "Août",
|
||||
"september": "Septembre",
|
||||
"october": "Octobre",
|
||||
"november": "Novembre",
|
||||
"december": "Décembre"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Recherche :"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "L'hôte du serveur de synchronisation n'est pas configuré. Veuillez d'abord configurer la synchronisation.",
|
||||
"successful": "L'établissement de liaison du serveur de synchronisation a été réussi, la synchronisation a été démarrée."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Notes cachées",
|
||||
"search-history-title": "Historique de recherche",
|
||||
"note-map-title": "Carte de la Note",
|
||||
"sql-console-history-title": "Historique de la console SQL",
|
||||
"shared-notes-title": "Notes partagées",
|
||||
"bulk-action-title": "Action groupée",
|
||||
"backend-log-title": "Journal Backend",
|
||||
"user-hidden-title": "Utilisateur masqué",
|
||||
"launch-bar-templates-title": "Modèles de barre de raccourcis",
|
||||
"base-abstract-launcher-title": "Raccourci Base abstraite",
|
||||
"command-launcher-title": "Raccourci Commande",
|
||||
"note-launcher-title": "Raccourci Note",
|
||||
"script-launcher-title": "Raccourci Script",
|
||||
"built-in-widget-title": "Widget intégré",
|
||||
"spacer-title": "Séparateur",
|
||||
"custom-widget-title": "Widget personnalisé",
|
||||
"launch-bar-title": "Barre de lancement",
|
||||
"available-launchers-title": "Raccourcis disponibles",
|
||||
"go-to-previous-note-title": "Aller à la note précédente",
|
||||
"go-to-next-note-title": "Aller à la note suivante",
|
||||
"new-note-title": "Nouvelle note",
|
||||
"search-notes-title": "Rechercher des notes",
|
||||
"calendar-title": "Calendrier",
|
||||
"recent-changes-title": "Modifications récentes",
|
||||
"bookmarks-title": "Signets",
|
||||
"open-today-journal-note-title": "Ouvrir la note du journal du jour",
|
||||
"quick-search-title": "Recherche rapide",
|
||||
"protected-session-title": "Session protégée",
|
||||
"sync-status-title": "État de la synchronisation",
|
||||
"settings-title": "Réglages",
|
||||
"options-title": "Options",
|
||||
"appearance-title": "Apparence",
|
||||
"shortcuts-title": "Raccourcis",
|
||||
"text-notes": "Notes de texte",
|
||||
"code-notes-title": "Notes de code",
|
||||
"images-title": "Images",
|
||||
"spellcheck-title": "Correcteur orthographique",
|
||||
"password-title": "Mot de passe",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Sauvegarde",
|
||||
"sync-title": "Synchronisation",
|
||||
"other": "Autre",
|
||||
"advanced-title": "Avancé",
|
||||
"visible-launchers-title": "Raccourcis visibles",
|
||||
"user-guide": "Guide de l'utilisateur",
|
||||
"jump-to-note-title": "Aller à...",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"localization": "Langue et région",
|
||||
"inbox-title": "Boîte de réception",
|
||||
"command-palette": "Ouvrir la palette de commandes",
|
||||
"zen-mode": "Mode Zen"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nouvelle note",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Le fichier journal '{{ fileName }}' n'existe pas (encore).",
|
||||
"reading-log-failed": "La lecture du fichier journal d'administration '{{ fileName }}' a échoué."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Ce type de note ne peut pas être affiché."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Document PDF (*.pdf)",
|
||||
"unable-to-export-message": "La note actuelle n'a pas pu être exportée en format PDF.",
|
||||
"unable-to-export-title": "Impossible d'exporter au format PDF",
|
||||
"unable-to-save-message": "Le fichier sélectionné n'a pas pu être écrit. Réessayez ou sélectionnez une autre destination.",
|
||||
"unable-to-print": "Impossible d'imprimer la note"
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Quitter Trilium",
|
||||
"recents": "Notes récentes",
|
||||
"bookmarks": "Signets",
|
||||
"today": "Ouvrir la note du journal du jour",
|
||||
"new-note": "Nouvelle note",
|
||||
"show-windows": "Afficher les fenêtres",
|
||||
"open_new_window": "Ouvrir une nouvelle fenêtre"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "La migration directe à partir de votre version actuelle n'est pas prise en charge. Veuillez d'abord mettre à jour vers la version v0.60.4, puis vers cette nouvelle version.",
|
||||
"error_message": "Erreur lors de la migration vers la version {{version}}: {{stack}}",
|
||||
"wrong_db_version": "La version de la base de données ({{version}}) est plus récente que ce que l'application supporte actuellement ({{targetVersion}}), ce qui signifie qu'elle a été créée par une version plus récente et incompatible de Trilium. Mettez à jour vers la dernière version de Trilium pour résoudre ce problème."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Erreur"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"command-palette": "Palette de commandes",
|
||||
"quick-search": "Recherche rapide",
|
||||
"back-in-note-history": "Revenir dans l’historique des notes",
|
||||
"forward-in-note-history": "Suivant dans l’historique des notes",
|
||||
"jump-to-note": "Aller à…",
|
||||
"scroll-to-active-note": "Faire défiler jusqu’à la note active",
|
||||
"search-in-subtree": "Rechercher dans la sous-arborescence",
|
||||
"expand-subtree": "Développer la sous-arborescence",
|
||||
"collapse-tree": "Réduire l’arborescence",
|
||||
"collapse-subtree": "Réduire la sous-arborescence",
|
||||
"sort-child-notes": "Trier les notes enfants",
|
||||
"create-note-after": "Créer une note après",
|
||||
"create-note-into": "Créer une note dans",
|
||||
"create-note-into-inbox": "Créer une note dans Inbox",
|
||||
"delete-notes": "Supprimer les notes",
|
||||
"move-note-up": "Remonter la note",
|
||||
"move-note-down": "Descendre la note",
|
||||
"move-note-up-in-hierarchy": "Monter la note dans la hiérarchie",
|
||||
"move-note-down-in-hierarchy": "Descendre la note dans la hiérarchie",
|
||||
"edit-note-title": "Modifier le titre de la note",
|
||||
"edit-branch-prefix": "Modifier le préfixe de la branche",
|
||||
"clone-notes-to": "Cloner les notes vers",
|
||||
"move-notes-to": "Déplacer les notes vers",
|
||||
"copy-notes-to-clipboard": "Copier les notes dans le presse-papiers",
|
||||
"paste-notes-from-clipboard": "Coller les notes depuis le presse-papiers",
|
||||
"cut-notes-to-clipboard": "Couper les notes vers le presse-papier",
|
||||
"select-all-notes-in-parent": "Selectionner toutes les notes dans le parent",
|
||||
"add-note-above-to-selection": "Ajouter la note au-dessus à la selection",
|
||||
"add-note-below-to-selection": "Ajouter la note dessous à la selection",
|
||||
"duplicate-subtree": "Dupliquer la sous-arborescence",
|
||||
"open-new-tab": "Ouvrir un nouvel onglet",
|
||||
"close-active-tab": "Fermer l'onglet actif",
|
||||
"reopen-last-tab": "Réouvrir le dernier onglet",
|
||||
"activate-next-tab": "Activer l'onglet suivant",
|
||||
"activate-previous-tab": "Activer l'onglet précédent",
|
||||
"open-new-window": "Ouvrir une nouvelle fenêtre",
|
||||
"toggle-system-tray-icon": "Activer/Désactiver l'icone de la barre d'état",
|
||||
"toggle-zen-mode": "Activer/Désactiver le mode Zen",
|
||||
"switch-to-first-tab": "Aller au premier onglet",
|
||||
"switch-to-second-tab": "Aller au second onglet",
|
||||
"switch-to-third-tab": "Aller au troisième onglet",
|
||||
"switch-to-fourth-tab": "Aller au quatrième onglet",
|
||||
"switch-to-fifth-tab": "Aller au cinquième onglet",
|
||||
"switch-to-sixth-tab": "Aller au sixième onglet",
|
||||
"switch-to-seventh-tab": "Aller au septième onglet",
|
||||
"switch-to-eighth-tab": "Aller au huitième onglet",
|
||||
"switch-to-ninth-tab": "Aller au neuvième onglet",
|
||||
"switch-to-last-tab": "Aller au dernier onglet",
|
||||
"show-note-source": "Afficher la source de la note",
|
||||
"show-options": "Afficher les options",
|
||||
"show-revisions": "Afficher les révisions",
|
||||
"show-recent-changes": "Afficher les changements récents",
|
||||
"show-sql-console": "Afficher la console SQL",
|
||||
"show-backend-log": "Afficher le journal du backend",
|
||||
"show-help": "Afficher l'aide",
|
||||
"show-cheatsheet": "Afficher la fiche de triche",
|
||||
"add-link-to-text": "Ajouter un lien au texte",
|
||||
"follow-link-under-cursor": "Suivre le lien en dessous du curseur",
|
||||
"insert-date-and-time-to-text": "Insérer la date et l'heure dans le texte",
|
||||
"paste-markdown-into-text": "Coller du Markdown dans le texte",
|
||||
"cut-into-note": "Couper dans une note",
|
||||
"add-include-note-to-text": "Ajouter une note inclusion au texte",
|
||||
"edit-read-only-note": "Modifier une note en lecture seule",
|
||||
"add-new-label": "Ajouter une nouvelle étiquette",
|
||||
"add-new-relation": "Ajouter une nouvelle relation",
|
||||
"toggle-ribbon-tab-classic-editor": "Basculer l'onglet Mise en forme de l'éditeur avec la barre d'outils fixe",
|
||||
"toggle-ribbon-tab-basic-properties": "Afficher/masquer les Propriétés de base de la note",
|
||||
"toggle-ribbon-tab-book-properties": "Afficher/masquer les Propriétés du Livre",
|
||||
"toggle-ribbon-tab-file-properties": "Afficher/masquer les Propriétés du fichier",
|
||||
"toggle-ribbon-tab-image-properties": "Afficher/masquer les Propriétés de l'image",
|
||||
"toggle-ribbon-tab-owned-attributes": "Afficher/masquer les Attributs propres",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Afficher/masquer les Attributs hérités",
|
||||
"toggle-right-pane": "Afficher le panneau de droite",
|
||||
"print-active-note": "Imprimer la note active",
|
||||
"export-active-note-as-pdf": "Exporter la note active en PDF",
|
||||
"open-note-externally": "Ouvrir la note à l'extérieur",
|
||||
"render-active-note": "Faire un rendu de la note active",
|
||||
"run-active-note": "Lancer la note active",
|
||||
"reload-frontend-app": "Recharger l'application Frontend",
|
||||
"open-developer-tools": "Ouvrir les outils développeur",
|
||||
"find-in-text": "Chercher un texte",
|
||||
"toggle-left-pane": "Afficher le panneau de gauche",
|
||||
"toggle-full-screen": "Passer en mode plein écran",
|
||||
"zoom-out": "Dézoomer",
|
||||
"zoom-in": "Zoomer",
|
||||
"reset-zoom-level": "Réinitilaliser le zoom",
|
||||
"copy-without-formatting": "Copier sans mise en forme",
|
||||
"force-save-revision": "Forcer la sauvegarde de la révision",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Basculer les attributs promus de l'onglet du ruban",
|
||||
"toggle-ribbon-tab-note-map": "Basculer l'onglet du ruban Note Map",
|
||||
"toggle-ribbon-tab-note-info": "Basculer l'onglet du ruban Note Info",
|
||||
"toggle-ribbon-tab-note-paths": "Basculer les chemins de notes de l'onglet du ruban",
|
||||
"toggle-ribbon-tab-similar-notes": "Basculer l'onglet du ruban Notes similaires",
|
||||
"toggle-note-hoisting": "Activer la focalisation sur la note",
|
||||
"unhoist-note": "Désactiver la focalisation sur la note"
|
||||
},
|
||||
"sql_init": {
|
||||
"db_not_initialized_desktop": "Base de données non initialisée, merci de suivre les instructions à l'écran.",
|
||||
"db_not_initialized_server": "Base de données non initialisée, veuillez visitez - http://[your-server-host]:{{port}} pour consulter les instructions d'initialisation de Trilium."
|
||||
},
|
||||
"desktop": {
|
||||
"instance_already_running": "Une instance est déjà en cours d'execution, ouverture de cette instance à la place."
|
||||
},
|
||||
"weekdayNumber": "Semaine {weekNumber}",
|
||||
"quarterNumber": "Trimestre {quarterNumber}",
|
||||
"share_theme": {
|
||||
"site-theme": "Thème du site",
|
||||
"search_placeholder": "Recherche...",
|
||||
"image_alt": "Image de l'article",
|
||||
"last-updated": "Dernière mise à jour le {{- date}}",
|
||||
"subpages": "Sous-pages:",
|
||||
"on-this-page": "Sur cette page",
|
||||
"expand": "Développer"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"text-snippet": "Extrait de texte",
|
||||
"description": "Description",
|
||||
"list-view": "Vue en liste",
|
||||
"grid-view": "Vue en grille",
|
||||
"calendar": "Calendrier",
|
||||
"table": "Tableau",
|
||||
"geo-map": "Carte géographique",
|
||||
"start-date": "Date de début",
|
||||
"end-date": "Date de fin",
|
||||
"start-time": "Heure de début",
|
||||
"end-time": "Heure de fin",
|
||||
"geolocation": "Géolocalisation",
|
||||
"built-in-templates": "Modèles intégrés",
|
||||
"board": "Tableau de bord",
|
||||
"status": "État",
|
||||
"board_note_first": "Première note",
|
||||
"board_note_second": "Deuxième note",
|
||||
"board_note_third": "Troisième note",
|
||||
"board_status_todo": "A faire",
|
||||
"board_status_progress": "En cours",
|
||||
"board_status_done": "Terminé",
|
||||
"presentation": "Présentation",
|
||||
"presentation_slide": "Diapositive de présentation",
|
||||
"presentation_slide_first": "Première diapositive",
|
||||
"presentation_slide_second": "Deuxième diapositive",
|
||||
"background": "Arrière-plan"
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Ouvrir la boîte de dialogue \"Aller à la note\"",
|
||||
"search-in-subtree": "Rechercher des notes dans les sous-arbres de la note active",
|
||||
"expand-subtree": "Développer le sous-arbre de la note actuelle",
|
||||
"collapse-tree": "Réduire toute l'arborescence des notes",
|
||||
"collapse-subtree": "Réduire le sous-arbre de la note actuelle",
|
||||
"sort-child-notes": "Trier les notes enfants",
|
||||
"creating-and-moving-notes": "Créer et déplacer des notes",
|
||||
"create-note-into-inbox": "Créer une note dans l'emplacement par défaut (si défini) ou une note journalière",
|
||||
"delete-note": "Supprimer la note",
|
||||
"move-note-up": "Déplacer la note vers le haut",
|
||||
"move-note-down": "Déplacer la note vers le bas",
|
||||
"move-note-up-in-hierarchy": "Déplacer la note vers le haut dans la hiérarchie",
|
||||
"move-note-down-in-hierarchy": "Déplacer la note vers le bas dans la hiérarchie",
|
||||
"edit-note-title": "Passer de l'arborescence aux détails d'une note et éditer le titre",
|
||||
"edit-branch-prefix": "Afficher la fenêtre Éditer le préfixe de branche",
|
||||
"note-clipboard": "Note presse-papiers",
|
||||
"copy-notes-to-clipboard": "Copier les notes sélectionnées dans le presse-papiers",
|
||||
"paste-notes-from-clipboard": "Coller les notes depuis le presse-papiers dans la note active",
|
||||
"cut-notes-to-clipboard": "Couper les notes sélectionnées dans le presse-papiers",
|
||||
"select-all-notes-in-parent": "Sélectionner toutes les notes du niveau de la note active",
|
||||
"add-note-above-to-the-selection": "Ajouter la note au-dessus de la sélection",
|
||||
"add-note-below-to-selection": "Ajouter la note en dessous de la sélection",
|
||||
"duplicate-subtree": "Dupliquer le sous-arbre",
|
||||
"tabs-and-windows": "Onglets et fenêtres",
|
||||
"open-new-tab": "Ouvrir un nouvel onglet",
|
||||
"close-active-tab": "Fermer l'onglet actif",
|
||||
"reopen-last-tab": "Rouvrir le dernier onglet fermé",
|
||||
"activate-next-tab": "Basculer vers l'onglet à droite de l'onglet actif",
|
||||
"activate-previous-tab": "Basculer vers l'onglet à gauche de l'onglet actif",
|
||||
"open-new-window": "Ouvrir une nouvelle fenêtre vide",
|
||||
"toggle-tray": "Afficher/masquer l'application dans la barre des tâches",
|
||||
"first-tab": "Basculer vers le premier onglet dans la liste",
|
||||
"second-tab": "Basculer vers le deuxième onglet dans la liste",
|
||||
"third-tab": "Basculer vers le troisième onglet dans la liste",
|
||||
"fourth-tab": "Basculer vers le quatrième onglet dans la liste",
|
||||
"fifth-tab": "Basculer vers le cinquième onglet dans la liste",
|
||||
"sixth-tab": "Basculer vers le sixième onglet dans la liste",
|
||||
"seventh-tab": "Basculer vers le septième onglet dans la liste",
|
||||
"eight-tab": "Basculer vers le huitième onglet dans la liste",
|
||||
"ninth-tab": "Basculer vers le neuvième onglet dans la liste",
|
||||
"last-tab": "Basculer vers le dernier onglet dans la liste",
|
||||
"dialogs": "Boîtes de dialogue",
|
||||
"show-note-source": "Affiche la boîte de dialogue Source de la note",
|
||||
"show-options": "Afficher les Options",
|
||||
"show-revisions": "Afficher la boîte de dialogue Versions de la note",
|
||||
"show-recent-changes": "Afficher la boîte de dialogue Modifications récentes",
|
||||
"show-sql-console": "Afficher la boîte de dialogue Console SQL",
|
||||
"show-backend-log": "Afficher la boîte de dialogue Journal du backend",
|
||||
"text-note-operations": "Opérations sur les notes textuelles",
|
||||
"add-link-to-text": "Ouvrir la boîte de dialogue pour ajouter un lien dans le texte",
|
||||
"follow-link-under-cursor": "Suivre le lien sous le curseur",
|
||||
"insert-date-and-time-to-text": "Insérer la date et l'heure dans le texte",
|
||||
"paste-markdown-into-text": "Coller du texte au format Markdown dans la note depuis le presse-papiers",
|
||||
"cut-into-note": "Couper la sélection depuis la note actuelle et créer une sous-note avec le texte sélectionné",
|
||||
"add-include-note-to-text": "Ouvrir la boîte de dialogue pour Inclure une note",
|
||||
"edit-readonly-note": "Éditer une note en lecture seule",
|
||||
"attributes-labels-and-relations": "Attributs (labels et relations)",
|
||||
"add-new-label": "Créer un nouveau label",
|
||||
"create-new-relation": "Créer une nouvelle relation",
|
||||
"ribbon-tabs": "Onglets du ruban",
|
||||
"toggle-basic-properties": "Afficher/masquer les Propriétés de base de la note",
|
||||
"toggle-file-properties": "Afficher/masquer les Propriétés du fichier",
|
||||
"toggle-image-properties": "Afficher/masquer les Propriétés de l'image",
|
||||
"toggle-owned-attributes": "Afficher/masquer les Attributs propres",
|
||||
"toggle-inherited-attributes": "Afficher/masquer les Attributs hérités",
|
||||
"toggle-promoted-attributes": "Afficher/masquer les Attributs promus",
|
||||
"toggle-link-map": "Afficher/masquer la Carte de la note",
|
||||
"toggle-note-info": "Afficher/masquer les Informations de la note",
|
||||
"toggle-note-paths": "Afficher/masquer les Emplacements de la note",
|
||||
"toggle-similar-notes": "Afficher/masquer les Notes similaires",
|
||||
"other": "Autre",
|
||||
"toggle-right-pane": "Afficher/masquer le volet droit, qui inclut la Table des matières et les Accentuations",
|
||||
"print-active-note": "Imprimer la note active",
|
||||
"open-note-externally": "Ouvrir la note comme fichier avec l'application par défaut",
|
||||
"render-active-note": "Rendre (ou re-rendre) la note active",
|
||||
"run-active-note": "Exécuter le code JavaScript (frontend/backend) de la note active",
|
||||
"toggle-note-hoisting": "Activer le focus sur la note active",
|
||||
"unhoist": "Désactiver tout focus",
|
||||
"reload-frontend-app": "Recharger l'application",
|
||||
"open-dev-tools": "Ouvrir les outils de développement",
|
||||
"toggle-left-note-tree-panel": "Basculer le panneau gauche (arborescence des notes)",
|
||||
"toggle-full-screen": "Basculer en plein écran",
|
||||
"zoom-out": "Dézoomer",
|
||||
"zoom-in": "Zoomer",
|
||||
"note-navigation": "Navigation dans les notes",
|
||||
"reset-zoom-level": "Réinitialiser le niveau de zoom",
|
||||
"copy-without-formatting": "Copier le texte sélectionné sans mise en forme",
|
||||
"force-save-revision": "Forcer la création / sauvegarde d'une nouvelle version de la note active",
|
||||
"show-help": "Affiche le guide de l'utilisateur intégré",
|
||||
"toggle-book-properties": "Afficher/masquer les Propriétés du Livre",
|
||||
"toggle-classic-editor-toolbar": "Activer/désactiver l'onglet Mise en forme de l'éditeur avec la barre d'outils fixe",
|
||||
"export-as-pdf": "Exporte la note actuelle en PDF",
|
||||
"show-cheatsheet": "Affiche une fenêtre modale avec des opérations de clavier courantes",
|
||||
"toggle-zen-mode": "Active/désactive le mode zen (interface réduite pour favoriser la concentration)",
|
||||
"back-in-note-history": "Naviguer à la note précédente dans l'historique",
|
||||
"forward-in-note-history": "Naviguer a la note suivante dans l'historique",
|
||||
"open-command-palette": "Ouvrir la palette de commandes",
|
||||
"clone-notes-to": "Cloner les nœuds sélectionnés",
|
||||
"move-notes-to": "Déplacer les nœuds sélectionnés",
|
||||
"scroll-to-active-note": "Faire défiler l’arborescence des notes jusqu’à la note active",
|
||||
"quick-search": "Activer la barre de recherche rapide",
|
||||
"create-note-after": "Créer une note après la note active",
|
||||
"create-note-into": "Créer une note enfant de la note active",
|
||||
"find-in-text": "Afficher/Masquer le panneau de recherche"
|
||||
},
|
||||
"login": {
|
||||
"title": "Connexion",
|
||||
"heading": "Connexion à Trilium",
|
||||
"incorrect-password": "Le mot de passe est incorrect. Veuillez réessayer.",
|
||||
"password": "Mot de passe",
|
||||
"remember-me": "Se souvenir de moi",
|
||||
"button": "Connexion",
|
||||
"sign_in_with_sso": "Se connecter avec {{ ssoIssuerName }}",
|
||||
"incorrect-totp": "TOTP incorrect. Veuillez réessayer."
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Définir un mot de passe",
|
||||
"heading": "Définir un mot de passe",
|
||||
"description": "Avant de pouvoir commencer à utiliser Trilium depuis le web, vous devez d'abord définir un mot de passe. Vous utiliserez ensuite ce mot de passe pour vous connecter.",
|
||||
"password": "Mot de passe",
|
||||
"password-confirmation": "Confirmation du mot de passe",
|
||||
"button": "Définir le mot de passe"
|
||||
},
|
||||
"setup": {
|
||||
"heading": "Configuration de Trilium Notes",
|
||||
"new-document": "Je suis un nouvel utilisateur et je souhaite créer un nouveau document Trilium pour mes notes",
|
||||
"sync-from-desktop": "J'ai déjà l'application de bureau et je souhaite configurer la synchronisation avec celle-ci",
|
||||
"sync-from-server": "J'ai déjà un serveur et je souhaite configurer la synchronisation avec celui-ci",
|
||||
"next": "Suivant",
|
||||
"init-in-progress": "Initialisation du document en cours",
|
||||
"redirecting": "Vous serez bientôt redirigé vers l'application.",
|
||||
"title": "Configuration"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Synchroniser depuis une application de bureau",
|
||||
"description": "Cette procédure doit être réalisée depuis l'application de bureau :",
|
||||
"step1": "Ouvrez l'application Trilium Notes.",
|
||||
"step2": "Dans le menu Trilium, cliquez sur Options.",
|
||||
"step3": "Cliquez sur la catégorie Synchroniser.",
|
||||
"step4": "Remplacez l'adresse de l'instance de serveur par : {{- host}} et cliquez sur Enregistrer.",
|
||||
"step5": "Cliquez sur le bouton 'Tester la synchronisation' pour vérifier que la connexion fonctionne.",
|
||||
"step6": "Une fois que vous avez terminé ces étapes, cliquez sur {{- link}}.",
|
||||
"step6-here": "ici"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Synchroniser depuis le serveur",
|
||||
"instructions": "Veuillez saisir l'adresse du serveur Trilium et les informations d'identification ci-dessous. Cela téléchargera l'intégralité du document Trilium à partir du serveur et configurera la synchronisation avec celui-ci. En fonction de la taille du document et de votre vitesse de connexion, cela peut prendre un plusieurs minutes.",
|
||||
"server-host": "Adresse du serveur Trilium",
|
||||
"server-host-placeholder": "https://<nom d'hôte>:<port>",
|
||||
"proxy-server": "Serveur proxy (facultatif)",
|
||||
"proxy-server-placeholder": "https://<nom d'hôte>:<port>",
|
||||
"note": "Note :",
|
||||
"proxy-instruction": "Si vous laissez le paramètre de proxy vide, le proxy du système sera utilisé (s'applique uniquement à l'application de bureau)",
|
||||
"password": "Mot de passe",
|
||||
"password-placeholder": "Mot de passe",
|
||||
"back": "Retour",
|
||||
"finish-setup": "Terminer"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Synchronisation en cours",
|
||||
"successful": "La synchronisation a été correctement configurée. La synchronisation initiale prendra un certain temps. Une fois terminée, vous serez redirigé vers la page de connexion.",
|
||||
"outstanding-items": "Éléments de synchronisation exceptionnels :",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Page non trouvée",
|
||||
"heading": "Page non trouvée"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "parent :",
|
||||
"clipped-from": "Cette note a été initialement extraite de {{- url}}",
|
||||
"child-notes": "Notes enfants :",
|
||||
"no-content": "Cette note n'a aucun contenu."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Lundi",
|
||||
"tuesday": "Mardi",
|
||||
"wednesday": "Mercredi",
|
||||
"thursday": "Jeudi",
|
||||
"friday": "Vendredi",
|
||||
"saturday": "Samedi",
|
||||
"sunday": "Dimanche"
|
||||
},
|
||||
"months": {
|
||||
"january": "Janvier",
|
||||
"february": "Février",
|
||||
"march": "Mars",
|
||||
"april": "Avril",
|
||||
"may": "Mai",
|
||||
"june": "Juin",
|
||||
"july": "Juillet",
|
||||
"august": "Août",
|
||||
"september": "Septembre",
|
||||
"october": "Octobre",
|
||||
"november": "Novembre",
|
||||
"december": "Décembre"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Recherche :",
|
||||
"llm_chat_prefix": "Chat:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "L'hôte du serveur de synchronisation n'est pas configuré. Veuillez d'abord configurer la synchronisation.",
|
||||
"successful": "L'établissement de liaison du serveur de synchronisation a été réussi, la synchronisation a été démarrée."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Notes cachées",
|
||||
"search-history-title": "Historique de recherche",
|
||||
"note-map-title": "Carte de la Note",
|
||||
"sql-console-history-title": "Historique de la console SQL",
|
||||
"shared-notes-title": "Notes partagées",
|
||||
"bulk-action-title": "Action groupée",
|
||||
"backend-log-title": "Journal Backend",
|
||||
"user-hidden-title": "Utilisateur masqué",
|
||||
"launch-bar-templates-title": "Modèles de barre de raccourcis",
|
||||
"base-abstract-launcher-title": "Raccourci Base abstraite",
|
||||
"command-launcher-title": "Raccourci Commande",
|
||||
"note-launcher-title": "Raccourci Note",
|
||||
"script-launcher-title": "Raccourci Script",
|
||||
"built-in-widget-title": "Widget intégré",
|
||||
"spacer-title": "Séparateur",
|
||||
"custom-widget-title": "Widget personnalisé",
|
||||
"launch-bar-title": "Barre de lancement",
|
||||
"available-launchers-title": "Raccourcis disponibles",
|
||||
"go-to-previous-note-title": "Aller à la note précédente",
|
||||
"go-to-next-note-title": "Aller à la note suivante",
|
||||
"new-note-title": "Nouvelle note",
|
||||
"search-notes-title": "Rechercher des notes",
|
||||
"calendar-title": "Calendrier",
|
||||
"recent-changes-title": "Modifications récentes",
|
||||
"bookmarks-title": "Signets",
|
||||
"open-today-journal-note-title": "Ouvrir la note du journal du jour",
|
||||
"quick-search-title": "Recherche rapide",
|
||||
"protected-session-title": "Session protégée",
|
||||
"sync-status-title": "État de la synchronisation",
|
||||
"settings-title": "Réglages",
|
||||
"options-title": "Options",
|
||||
"appearance-title": "Apparence",
|
||||
"shortcuts-title": "Raccourcis",
|
||||
"text-notes": "Notes de texte",
|
||||
"code-notes-title": "Notes de code",
|
||||
"images-title": "Images",
|
||||
"spellcheck-title": "Correcteur orthographique",
|
||||
"password-title": "Mot de passe",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Sauvegarde",
|
||||
"sync-title": "Synchronisation",
|
||||
"other": "Autre",
|
||||
"advanced-title": "Avancé",
|
||||
"visible-launchers-title": "Raccourcis visibles",
|
||||
"user-guide": "Guide de l'utilisateur",
|
||||
"jump-to-note-title": "Aller à...",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"localization": "Langue et région",
|
||||
"inbox-title": "Boîte de réception",
|
||||
"command-palette": "Ouvrir la palette de commandes",
|
||||
"zen-mode": "Mode Zen",
|
||||
"llm-chat-history-title": "Historique du chat",
|
||||
"llm-title": "AI / LLM",
|
||||
"tab-switcher-title": "Commutateur d'onglets",
|
||||
"sidebar-chat-title": "AI Chat"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nouvelle note",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Le fichier journal '{{ fileName }}' n'existe pas (encore).",
|
||||
"reading-log-failed": "La lecture du fichier journal d'administration '{{ fileName }}' a échoué."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Ce type de note ne peut pas être affiché."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Document PDF (*.pdf)",
|
||||
"unable-to-export-message": "La note actuelle n'a pas pu être exportée en format PDF.",
|
||||
"unable-to-export-title": "Impossible d'exporter au format PDF",
|
||||
"unable-to-save-message": "Le fichier sélectionné n'a pas pu être écrit. Réessayez ou sélectionnez une autre destination.",
|
||||
"unable-to-print": "Impossible d'imprimer la note"
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Quitter Trilium",
|
||||
"recents": "Notes récentes",
|
||||
"bookmarks": "Signets",
|
||||
"today": "Ouvrir la note du journal du jour",
|
||||
"new-note": "Nouvelle note",
|
||||
"show-windows": "Afficher les fenêtres",
|
||||
"open_new_window": "Ouvrir une nouvelle fenêtre"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "La migration directe à partir de votre version actuelle n'est pas prise en charge. Veuillez d'abord mettre à jour vers la version v0.60.4, puis vers cette nouvelle version.",
|
||||
"error_message": "Erreur lors de la migration vers la version {{version}}: {{stack}}",
|
||||
"wrong_db_version": "La version de la base de données ({{version}}) est plus récente que ce que l'application supporte actuellement ({{targetVersion}}), ce qui signifie qu'elle a été créée par une version plus récente et incompatible de Trilium. Mettez à jour vers la dernière version de Trilium pour résoudre ce problème."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Erreur"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"command-palette": "Palette de commandes",
|
||||
"quick-search": "Recherche rapide",
|
||||
"back-in-note-history": "Revenir dans l’historique des notes",
|
||||
"forward-in-note-history": "Suivant dans l’historique des notes",
|
||||
"jump-to-note": "Aller à…",
|
||||
"scroll-to-active-note": "Faire défiler jusqu’à la note active",
|
||||
"search-in-subtree": "Rechercher dans la sous-arborescence",
|
||||
"expand-subtree": "Développer la sous-arborescence",
|
||||
"collapse-tree": "Réduire l’arborescence",
|
||||
"collapse-subtree": "Réduire la sous-arborescence",
|
||||
"sort-child-notes": "Trier les notes enfants",
|
||||
"create-note-after": "Créer une note après",
|
||||
"create-note-into": "Créer une note dans",
|
||||
"create-note-into-inbox": "Créer une note dans Inbox",
|
||||
"delete-notes": "Supprimer les notes",
|
||||
"move-note-up": "Remonter la note",
|
||||
"move-note-down": "Descendre la note",
|
||||
"move-note-up-in-hierarchy": "Monter la note dans la hiérarchie",
|
||||
"move-note-down-in-hierarchy": "Descendre la note dans la hiérarchie",
|
||||
"edit-note-title": "Modifier le titre de la note",
|
||||
"edit-branch-prefix": "Modifier le préfixe de la branche",
|
||||
"clone-notes-to": "Cloner les notes vers",
|
||||
"move-notes-to": "Déplacer les notes vers",
|
||||
"copy-notes-to-clipboard": "Copier les notes dans le presse-papiers",
|
||||
"paste-notes-from-clipboard": "Coller les notes depuis le presse-papiers",
|
||||
"cut-notes-to-clipboard": "Couper les notes vers le presse-papier",
|
||||
"select-all-notes-in-parent": "Selectionner toutes les notes dans le parent",
|
||||
"add-note-above-to-selection": "Ajouter la note au-dessus à la selection",
|
||||
"add-note-below-to-selection": "Ajouter la note dessous à la selection",
|
||||
"duplicate-subtree": "Dupliquer la sous-arborescence",
|
||||
"open-new-tab": "Ouvrir un nouvel onglet",
|
||||
"close-active-tab": "Fermer l'onglet actif",
|
||||
"reopen-last-tab": "Réouvrir le dernier onglet",
|
||||
"activate-next-tab": "Activer l'onglet suivant",
|
||||
"activate-previous-tab": "Activer l'onglet précédent",
|
||||
"open-new-window": "Ouvrir une nouvelle fenêtre",
|
||||
"toggle-system-tray-icon": "Activer/Désactiver l'icone de la barre d'état",
|
||||
"toggle-zen-mode": "Activer/Désactiver le mode Zen",
|
||||
"switch-to-first-tab": "Aller au premier onglet",
|
||||
"switch-to-second-tab": "Aller au second onglet",
|
||||
"switch-to-third-tab": "Aller au troisième onglet",
|
||||
"switch-to-fourth-tab": "Aller au quatrième onglet",
|
||||
"switch-to-fifth-tab": "Aller au cinquième onglet",
|
||||
"switch-to-sixth-tab": "Aller au sixième onglet",
|
||||
"switch-to-seventh-tab": "Aller au septième onglet",
|
||||
"switch-to-eighth-tab": "Aller au huitième onglet",
|
||||
"switch-to-ninth-tab": "Aller au neuvième onglet",
|
||||
"switch-to-last-tab": "Aller au dernier onglet",
|
||||
"show-note-source": "Afficher la source de la note",
|
||||
"show-options": "Afficher les options",
|
||||
"show-revisions": "Afficher les révisions",
|
||||
"show-recent-changes": "Afficher les changements récents",
|
||||
"show-sql-console": "Afficher la console SQL",
|
||||
"show-backend-log": "Afficher le journal du backend",
|
||||
"show-help": "Afficher l'aide",
|
||||
"show-cheatsheet": "Afficher la fiche de triche",
|
||||
"add-link-to-text": "Ajouter un lien au texte",
|
||||
"follow-link-under-cursor": "Suivre le lien en dessous du curseur",
|
||||
"insert-date-and-time-to-text": "Insérer la date et l'heure dans le texte",
|
||||
"paste-markdown-into-text": "Coller du Markdown dans le texte",
|
||||
"cut-into-note": "Couper dans une note",
|
||||
"add-include-note-to-text": "Ajouter une note inclusion au texte",
|
||||
"edit-read-only-note": "Modifier une note en lecture seule",
|
||||
"add-new-label": "Ajouter une nouvelle étiquette",
|
||||
"add-new-relation": "Ajouter une nouvelle relation",
|
||||
"toggle-ribbon-tab-classic-editor": "Basculer l'onglet Mise en forme de l'éditeur avec la barre d'outils fixe",
|
||||
"toggle-ribbon-tab-basic-properties": "Afficher/masquer les Propriétés de base de la note",
|
||||
"toggle-ribbon-tab-book-properties": "Afficher/masquer les Propriétés du Livre",
|
||||
"toggle-ribbon-tab-file-properties": "Afficher/masquer les Propriétés du fichier",
|
||||
"toggle-ribbon-tab-image-properties": "Afficher/masquer les Propriétés de l'image",
|
||||
"toggle-ribbon-tab-owned-attributes": "Afficher/masquer les Attributs propres",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Afficher/masquer les Attributs hérités",
|
||||
"toggle-right-pane": "Afficher le panneau de droite",
|
||||
"print-active-note": "Imprimer la note active",
|
||||
"export-active-note-as-pdf": "Exporter la note active en PDF",
|
||||
"open-note-externally": "Ouvrir la note à l'extérieur",
|
||||
"render-active-note": "Faire un rendu de la note active",
|
||||
"run-active-note": "Lancer la note active",
|
||||
"reload-frontend-app": "Recharger l'application Frontend",
|
||||
"open-developer-tools": "Ouvrir les outils développeur",
|
||||
"find-in-text": "Chercher un texte",
|
||||
"toggle-left-pane": "Afficher le panneau de gauche",
|
||||
"toggle-full-screen": "Passer en mode plein écran",
|
||||
"zoom-out": "Dézoomer",
|
||||
"zoom-in": "Zoomer",
|
||||
"reset-zoom-level": "Réinitilaliser le zoom",
|
||||
"copy-without-formatting": "Copier sans mise en forme",
|
||||
"force-save-revision": "Forcer la sauvegarde de la révision",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Basculer les attributs promus de l'onglet du ruban",
|
||||
"toggle-ribbon-tab-note-map": "Basculer l'onglet du ruban Note Map",
|
||||
"toggle-ribbon-tab-note-info": "Basculer l'onglet du ruban Note Info",
|
||||
"toggle-ribbon-tab-note-paths": "Basculer les chemins de notes de l'onglet du ruban",
|
||||
"toggle-ribbon-tab-similar-notes": "Basculer l'onglet du ruban Notes similaires",
|
||||
"toggle-note-hoisting": "Activer la focalisation sur la note",
|
||||
"unhoist-note": "Désactiver la focalisation sur la note"
|
||||
},
|
||||
"sql_init": {
|
||||
"db_not_initialized_desktop": "Base de données non initialisée, merci de suivre les instructions à l'écran.",
|
||||
"db_not_initialized_server": "Base de données non initialisée, veuillez visitez - http://[your-server-host]:{{port}} pour consulter les instructions d'initialisation de Trilium."
|
||||
},
|
||||
"desktop": {
|
||||
"instance_already_running": "Une instance est déjà en cours d'execution, ouverture de cette instance à la place."
|
||||
},
|
||||
"weekdayNumber": "Semaine {weekNumber}",
|
||||
"quarterNumber": "Trimestre {quarterNumber}",
|
||||
"share_theme": {
|
||||
"site-theme": "Thème du site",
|
||||
"search_placeholder": "Recherche...",
|
||||
"image_alt": "Image de l'article",
|
||||
"last-updated": "Dernière mise à jour le {{- date}}",
|
||||
"subpages": "Sous-pages:",
|
||||
"on-this-page": "Sur cette page",
|
||||
"expand": "Développer"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"text-snippet": "Extrait de texte",
|
||||
"description": "Description",
|
||||
"list-view": "Vue en liste",
|
||||
"grid-view": "Vue en grille",
|
||||
"calendar": "Calendrier",
|
||||
"table": "Tableau",
|
||||
"geo-map": "Carte géographique",
|
||||
"start-date": "Date de début",
|
||||
"end-date": "Date de fin",
|
||||
"start-time": "Heure de début",
|
||||
"end-time": "Heure de fin",
|
||||
"geolocation": "Géolocalisation",
|
||||
"built-in-templates": "Modèles intégrés",
|
||||
"board": "Tableau Kanban",
|
||||
"status": "État",
|
||||
"board_note_first": "Première note",
|
||||
"board_note_second": "Deuxième note",
|
||||
"board_note_third": "Troisième note",
|
||||
"board_status_todo": "A faire",
|
||||
"board_status_progress": "En cours",
|
||||
"board_status_done": "Terminé",
|
||||
"presentation": "Présentation",
|
||||
"presentation_slide": "Diapositive de présentation",
|
||||
"presentation_slide_first": "Première diapositive",
|
||||
"presentation_slide_second": "Deuxième diapositive",
|
||||
"background": "Arrière-plan"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,10 @@
|
||||
"script-launcher-title": "Scorciatoie degli script",
|
||||
"command-palette": "Apri tavolozza comandi",
|
||||
"zen-mode": "Modalità Zen",
|
||||
"tab-switcher-title": "Selettore scheda"
|
||||
"tab-switcher-title": "Selettore scheda",
|
||||
"llm-chat-history-title": "Cronologia chat IA",
|
||||
"llm-title": "AI / LLM",
|
||||
"sidebar-chat-title": "Chat con IA"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nuova nota",
|
||||
@@ -400,7 +403,8 @@
|
||||
},
|
||||
"quarterNumber": "Quadrimestre n. {quarterNumber}",
|
||||
"special_notes": {
|
||||
"search_prefix": "Ricerca:"
|
||||
"search_prefix": "Ricerca:",
|
||||
"llm_chat_prefix": "Chat:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "L'host del server di sincronizzazione non è impostato. Configurare prima la sincronizzazione.",
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { LlmMessage } from "@triliumnext/commons";
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
import { generateChatTitle } from "../../services/llm/chat_title.js";
|
||||
import { getProviderByType, hasConfiguredProviders, type LlmProviderConfig } from "../../services/llm/index.js";
|
||||
import { getAllModels, getProviderByType, hasConfiguredProviders, type LlmProviderConfig } from "../../services/llm/index.js";
|
||||
import { streamToChunks } from "../../services/llm/stream.js";
|
||||
import log from "../../services/log.js";
|
||||
import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
|
||||
@@ -88,19 +88,14 @@ async function streamChat(req: Request, res: Response) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available models for a provider.
|
||||
* Get available models from all configured providers.
|
||||
*/
|
||||
function getModels(req: Request, _res: Response) {
|
||||
const providerType = req.query.provider as string || "anthropic";
|
||||
|
||||
// Return empty array when no providers configured - client handles this gracefully
|
||||
function getModels(_req: Request, _res: Response) {
|
||||
if (!hasConfiguredProviders()) {
|
||||
return { models: [] };
|
||||
}
|
||||
|
||||
const llmProvider = getProviderByType(providerType);
|
||||
const models = llmProvider.getAvailableModels();
|
||||
return { models };
|
||||
return { models: getAllModels() };
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { LlmProvider } from "./types.js";
|
||||
import type { LlmProvider, ModelInfo } from "./types.js";
|
||||
import { AnthropicProvider } from "./providers/anthropic.js";
|
||||
import { GoogleProvider } from "./providers/google.js";
|
||||
import { OpenAiProvider } from "./providers/openai.js";
|
||||
import optionService from "../options.js";
|
||||
import log from "../log.js";
|
||||
|
||||
@@ -16,7 +18,9 @@ export interface LlmProviderSetup {
|
||||
|
||||
/** Factory functions for creating provider instances */
|
||||
const providerFactories: Record<string, (apiKey: string) => LlmProvider> = {
|
||||
anthropic: (apiKey) => new AnthropicProvider(apiKey)
|
||||
anthropic: (apiKey) => new AnthropicProvider(apiKey),
|
||||
openai: (apiKey) => new OpenAiProvider(apiKey),
|
||||
google: (apiKey) => new GoogleProvider(apiKey)
|
||||
};
|
||||
|
||||
/** Cache of instantiated providers by their config ID */
|
||||
@@ -95,6 +99,35 @@ export function hasConfiguredProviders(): boolean {
|
||||
return getConfiguredProviders().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all models from all configured providers, tagged with their provider type.
|
||||
*/
|
||||
export function getAllModels(): ModelInfo[] {
|
||||
const configs = getConfiguredProviders();
|
||||
const seenProviderTypes = new Set<string>();
|
||||
const allModels: ModelInfo[] = [];
|
||||
|
||||
for (const config of configs) {
|
||||
// Only include models once per provider type (not per config instance)
|
||||
if (seenProviderTypes.has(config.provider)) {
|
||||
continue;
|
||||
}
|
||||
seenProviderTypes.add(config.provider);
|
||||
|
||||
try {
|
||||
const provider = getProvider(config.id);
|
||||
const models = provider.getAvailableModels();
|
||||
for (const model of models) {
|
||||
allModels.push({ ...model, provider: config.provider });
|
||||
}
|
||||
} catch (e) {
|
||||
log.error(`Failed to get models from provider ${config.provider}: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
return allModels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the provider cache. Call this when provider configurations change.
|
||||
*/
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
import { createAnthropic, type AnthropicProvider as AnthropicSDKProvider } from "@ai-sdk/anthropic";
|
||||
import { generateText, streamText, stepCountIs, type CoreMessage, type ToolSet } from "ai";
|
||||
import { stepCountIs, streamText, type ModelMessage, type ToolSet } from "ai";
|
||||
import type { LlmMessage } from "@triliumnext/commons";
|
||||
|
||||
import becca from "../../../becca/becca.js";
|
||||
import { noteTools, attributeTools, currentNoteTools } from "../tools/index.js";
|
||||
import type { LlmProvider, LlmProviderConfig, ModelInfo, ModelPricing, StreamResult } from "../types.js";
|
||||
|
||||
const DEFAULT_MODEL = "claude-sonnet-4-6";
|
||||
const DEFAULT_MAX_TOKENS = 8096;
|
||||
const TITLE_MODEL = "claude-haiku-4-5-20251001";
|
||||
const TITLE_MAX_TOKENS = 30;
|
||||
|
||||
/**
|
||||
* Calculate effective cost for comparison (weighted average: 1 input + 3 output).
|
||||
* Output is weighted more heavily as it's typically the dominant cost factor.
|
||||
*/
|
||||
function effectiveCost(pricing: ModelPricing): number {
|
||||
return (pricing.input + 3 * pricing.output) / 4;
|
||||
}
|
||||
import type { LlmProviderConfig, StreamResult } from "../types.js";
|
||||
import { BaseProvider, buildModelList } from "./base_provider.js";
|
||||
|
||||
/**
|
||||
* Available Anthropic models with pricing (USD per million tokens).
|
||||
* Source: https://docs.anthropic.com/en/docs/about-claude/models
|
||||
*/
|
||||
const BASE_MODELS: Omit<ModelInfo, "costMultiplier">[] = [
|
||||
const { models: AVAILABLE_MODELS, pricing: MODEL_PRICING } = buildModelList([
|
||||
// ===== Current Models =====
|
||||
{
|
||||
id: "claude-sonnet-4-6",
|
||||
@@ -49,7 +35,7 @@ const BASE_MODELS: Omit<ModelInfo, "costMultiplier">[] = [
|
||||
id: "claude-sonnet-4-5-20250929",
|
||||
name: "Claude Sonnet 4.5",
|
||||
pricing: { input: 3, output: 15 },
|
||||
contextWindow: 200000, // 1M available with beta header
|
||||
contextWindow: 200000,
|
||||
isLegacy: true
|
||||
},
|
||||
{
|
||||
@@ -70,7 +56,7 @@ const BASE_MODELS: Omit<ModelInfo, "costMultiplier">[] = [
|
||||
id: "claude-sonnet-4-20250514",
|
||||
name: "Claude Sonnet 4.0",
|
||||
pricing: { input: 3, output: 15 },
|
||||
contextWindow: 200000, // 1M available with beta header
|
||||
contextWindow: 200000,
|
||||
isLegacy: true
|
||||
},
|
||||
{
|
||||
@@ -80,69 +66,42 @@ const BASE_MODELS: Omit<ModelInfo, "costMultiplier">[] = [
|
||||
contextWindow: 200000,
|
||||
isLegacy: true
|
||||
}
|
||||
];
|
||||
]);
|
||||
|
||||
// Use default model (Sonnet) as baseline for cost multiplier
|
||||
const baselineModel = BASE_MODELS.find(m => m.isDefault) || BASE_MODELS[0];
|
||||
const baselineCost = effectiveCost(baselineModel.pricing);
|
||||
|
||||
// Build models with cost multipliers
|
||||
const AVAILABLE_MODELS: ModelInfo[] = BASE_MODELS.map(m => ({
|
||||
...m,
|
||||
costMultiplier: Math.round((effectiveCost(m.pricing) / baselineCost) * 10) / 10
|
||||
}));
|
||||
|
||||
// Build pricing lookup from available models
|
||||
const MODEL_PRICING: Record<string, ModelPricing> = Object.fromEntries(
|
||||
AVAILABLE_MODELS.map(m => [m.id, m.pricing])
|
||||
);
|
||||
|
||||
/**
|
||||
* Build a lightweight context hint about the current note (title + type only, no content).
|
||||
* The full content is available via the get_current_note tool.
|
||||
*/
|
||||
function buildNoteHint(noteId: string): string | null {
|
||||
const note = becca.getNote(noteId);
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `The user is currently viewing a ${note.type} note titled "${note.title}". Use the get_current_note tool to read its content if needed.`;
|
||||
}
|
||||
|
||||
export class AnthropicProvider implements LlmProvider {
|
||||
export class AnthropicProvider extends BaseProvider {
|
||||
name = "anthropic";
|
||||
protected defaultModel = "claude-sonnet-4-6";
|
||||
protected titleModel = "claude-haiku-4-5-20251001";
|
||||
protected availableModels = AVAILABLE_MODELS;
|
||||
protected modelPricing = MODEL_PRICING;
|
||||
|
||||
private anthropic: AnthropicSDKProvider;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
super();
|
||||
if (!apiKey) {
|
||||
throw new Error("API key is required for Anthropic provider");
|
||||
}
|
||||
this.anthropic = createAnthropic({ apiKey });
|
||||
}
|
||||
|
||||
chat(messages: LlmMessage[], config: LlmProviderConfig): StreamResult {
|
||||
let systemPrompt = config.systemPrompt || messages.find(m => m.role === "system")?.content;
|
||||
const chatMessages = messages.filter(m => m.role !== "system");
|
||||
protected createModel(modelId: string) {
|
||||
return this.anthropic(modelId);
|
||||
}
|
||||
|
||||
// Add a lightweight hint about the current note (content available via tool)
|
||||
if (config.contextNoteId) {
|
||||
const noteHint = buildNoteHint(config.contextNoteId);
|
||||
if (noteHint) {
|
||||
systemPrompt = systemPrompt
|
||||
? `${systemPrompt}\n\n${noteHint}`
|
||||
: noteHint;
|
||||
}
|
||||
}
|
||||
protected override addWebSearchTool(tools: ToolSet): void {
|
||||
tools.web_search = this.anthropic.tools.webSearch_20250305({
|
||||
maxUses: 5
|
||||
});
|
||||
}
|
||||
|
||||
// Convert to AI SDK message format with cache control breakpoints.
|
||||
// The system prompt and conversation history (all but the last user message)
|
||||
// are stable across turns, so we mark them for caching to reduce costs.
|
||||
/**
|
||||
* Override buildMessages to add Anthropic-specific cache control breakpoints.
|
||||
*/
|
||||
protected override buildMessages(chatMessages: LlmMessage[], systemPrompt: string | undefined): ModelMessage[] {
|
||||
const CACHE_CONTROL = { anthropic: { cacheControl: { type: "ephemeral" as const } } };
|
||||
const coreMessages: ModelMessage[] = [];
|
||||
|
||||
const coreMessages: CoreMessage[] = [];
|
||||
|
||||
// System prompt as a cacheable message
|
||||
if (systemPrompt) {
|
||||
coreMessages.push({
|
||||
role: "system",
|
||||
@@ -151,94 +110,59 @@ export class AnthropicProvider implements LlmProvider {
|
||||
});
|
||||
}
|
||||
|
||||
// Conversation messages
|
||||
for (let i = 0; i < chatMessages.length; i++) {
|
||||
const m = chatMessages[i];
|
||||
const isLastBeforeNewTurn = i === chatMessages.length - 2;
|
||||
// Anthropic rejects empty text content blocks. Replace empty
|
||||
// content (e.g. tool-only assistant turns) with a placeholder
|
||||
// to preserve conversation flow.
|
||||
const content = m.content || "(tool use)";
|
||||
coreMessages.push({
|
||||
role: m.role as "user" | "assistant",
|
||||
content: m.content,
|
||||
// Cache breakpoint on the second-to-last message:
|
||||
// everything up to here is identical across consecutive turns.
|
||||
content,
|
||||
...(isLastBeforeNewTurn && { providerOptions: CACHE_CONTROL })
|
||||
});
|
||||
}
|
||||
|
||||
const model = this.anthropic(config.model || DEFAULT_MODEL);
|
||||
return coreMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override chat to add Anthropic-specific extended thinking support.
|
||||
*/
|
||||
override chat(messages: LlmMessage[], config: LlmProviderConfig): StreamResult {
|
||||
if (!config.enableExtendedThinking) {
|
||||
return super.chat(messages, config);
|
||||
}
|
||||
|
||||
const systemPrompt = this.buildSystemPrompt(messages, config);
|
||||
const chatMessages = messages.filter(m => m.role !== "system");
|
||||
const coreMessages = this.buildMessages(chatMessages, systemPrompt);
|
||||
|
||||
const thinkingBudget = config.thinkingBudget || 10000;
|
||||
const maxTokens = Math.max(config.maxTokens || 8096, thinkingBudget + 4000);
|
||||
|
||||
// Build options for streamText
|
||||
const streamOptions: Parameters<typeof streamText>[0] = {
|
||||
model,
|
||||
model: this.createModel(config.model || this.defaultModel),
|
||||
messages: coreMessages,
|
||||
maxOutputTokens: config.maxTokens || DEFAULT_MAX_TOKENS
|
||||
};
|
||||
|
||||
// Enable extended thinking for deeper reasoning
|
||||
if (config.enableExtendedThinking) {
|
||||
const thinkingBudget = config.thinkingBudget || 10000;
|
||||
streamOptions.providerOptions = {
|
||||
maxOutputTokens: maxTokens,
|
||||
providerOptions: {
|
||||
anthropic: {
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budgetTokens: thinkingBudget
|
||||
}
|
||||
}
|
||||
};
|
||||
streamOptions.maxOutputTokens = Math.max(
|
||||
streamOptions.maxOutputTokens || DEFAULT_MAX_TOKENS,
|
||||
thinkingBudget + 4000
|
||||
);
|
||||
}
|
||||
|
||||
// Build tools object
|
||||
const tools: ToolSet = {};
|
||||
|
||||
if (config.enableWebSearch) {
|
||||
tools.web_search = this.anthropic.tools.webSearch_20250305({
|
||||
maxUses: 5
|
||||
});
|
||||
}
|
||||
|
||||
if (config.contextNoteId) {
|
||||
Object.assign(tools, currentNoteTools(config.contextNoteId));
|
||||
}
|
||||
|
||||
if (config.enableNoteTools) {
|
||||
Object.assign(tools, noteTools);
|
||||
Object.assign(tools, attributeTools);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tools = this.buildTools(config);
|
||||
if (Object.keys(tools).length > 0) {
|
||||
streamOptions.tools = tools;
|
||||
// Allow multiple tool use cycles before final response
|
||||
streamOptions.stopWhen = stepCountIs(5);
|
||||
// Let model decide when to use tools vs respond with text
|
||||
streamOptions.toolChoice = "auto";
|
||||
}
|
||||
|
||||
return streamText(streamOptions);
|
||||
}
|
||||
|
||||
getModelPricing(model: string): ModelPricing | undefined {
|
||||
return MODEL_PRICING[model];
|
||||
}
|
||||
|
||||
getAvailableModels(): ModelInfo[] {
|
||||
return AVAILABLE_MODELS;
|
||||
}
|
||||
|
||||
async generateTitle(firstMessage: string): Promise<string> {
|
||||
const { text } = await generateText({
|
||||
model: this.anthropic(TITLE_MODEL),
|
||||
maxOutputTokens: TITLE_MAX_TOKENS,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: `Summarize the following message as a very short chat title (max 6 words). Reply with ONLY the title, no quotes or punctuation at the end.\n\nMessage: ${firstMessage}`
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return text.trim();
|
||||
}
|
||||
}
|
||||
|
||||
188
apps/server/src/services/llm/providers/base_provider.ts
Normal file
188
apps/server/src/services/llm/providers/base_provider.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Base class for LLM providers. Handles shared logic for system prompt building,
|
||||
* tool assembly, model pricing, and title generation.
|
||||
*/
|
||||
|
||||
import { generateText, streamText, stepCountIs, type ModelMessage, type ToolSet } from "ai";
|
||||
import type { LanguageModel } from "ai";
|
||||
import type { LlmMessage } from "@triliumnext/commons";
|
||||
|
||||
import becca from "../../../becca/becca.js";
|
||||
import { getSkillsSummary } from "../skills/index.js";
|
||||
import { noteTools, attributeTools, hierarchyTools, skillTools, currentNoteTools } from "../tools/index.js";
|
||||
import type { LlmProvider, LlmProviderConfig, ModelInfo, ModelPricing, StreamResult } from "../types.js";
|
||||
|
||||
const DEFAULT_MAX_TOKENS = 8096;
|
||||
const TITLE_MAX_TOKENS = 30;
|
||||
|
||||
/**
|
||||
* Calculate effective cost for comparison (weighted average: 1 input + 3 output).
|
||||
* Output is weighted more heavily as it's typically the dominant cost factor.
|
||||
*/
|
||||
function effectiveCost(pricing: ModelPricing): number {
|
||||
return (pricing.input + 3 * pricing.output) / 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lightweight context hint about the current note (title + type only, no content).
|
||||
*/
|
||||
function buildNoteHint(noteId: string): string | null {
|
||||
const note = becca.getNote(noteId);
|
||||
if (!note) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `The user is currently viewing a ${note.type} note titled "${note.title}". Use the get_current_note tool to read its content if needed.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the model list with cost multipliers from a base model definition array.
|
||||
*/
|
||||
export function buildModelList(baseModels: Omit<ModelInfo, "costMultiplier">[]): {
|
||||
models: ModelInfo[];
|
||||
pricing: Record<string, ModelPricing>;
|
||||
} {
|
||||
const baselineModel = baseModels.find(m => m.isDefault) || baseModels[0];
|
||||
const baselineCost = effectiveCost(baselineModel.pricing);
|
||||
|
||||
const models = baseModels.map(m => ({
|
||||
...m,
|
||||
costMultiplier: Math.round((effectiveCost(m.pricing) / baselineCost) * 10) / 10
|
||||
}));
|
||||
|
||||
const pricing = Object.fromEntries(
|
||||
models.map(m => [m.id, m.pricing])
|
||||
);
|
||||
|
||||
return { models, pricing };
|
||||
}
|
||||
|
||||
export abstract class BaseProvider implements LlmProvider {
|
||||
abstract name: string;
|
||||
|
||||
protected abstract defaultModel: string;
|
||||
protected abstract titleModel: string;
|
||||
protected abstract availableModels: ModelInfo[];
|
||||
protected abstract modelPricing: Record<string, ModelPricing>;
|
||||
|
||||
/** Create a language model instance for the given model ID. */
|
||||
protected abstract createModel(modelId: string): LanguageModel;
|
||||
|
||||
/**
|
||||
* Build the system prompt with note hints and skills summary.
|
||||
*/
|
||||
protected buildSystemPrompt(messages: LlmMessage[], config: LlmProviderConfig): string | undefined {
|
||||
let systemPrompt = config.systemPrompt || messages.find(m => m.role === "system")?.content;
|
||||
|
||||
if (config.contextNoteId) {
|
||||
const noteHint = buildNoteHint(config.contextNoteId);
|
||||
if (noteHint) {
|
||||
systemPrompt = systemPrompt
|
||||
? `${systemPrompt}\n\n${noteHint}`
|
||||
: noteHint;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enableNoteTools) {
|
||||
const skillsHint = `You have access to skills that provide specialized instructions. Load a skill with the load_skill tool before performing complex operations.\n\nAvailable skills:\n${getSkillsSummary()}`;
|
||||
systemPrompt = systemPrompt
|
||||
? `${systemPrompt}\n\n${skillsHint}`
|
||||
: skillsHint;
|
||||
}
|
||||
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the ModelMessage array from LlmMessages (no provider-specific options).
|
||||
*/
|
||||
protected buildMessages(chatMessages: LlmMessage[], systemPrompt: string | undefined): ModelMessage[] {
|
||||
const coreMessages: ModelMessage[] = [];
|
||||
|
||||
if (systemPrompt) {
|
||||
coreMessages.push({ role: "system", content: systemPrompt });
|
||||
}
|
||||
|
||||
for (const m of chatMessages) {
|
||||
coreMessages.push({
|
||||
role: m.role as "user" | "assistant",
|
||||
content: m.content
|
||||
});
|
||||
}
|
||||
|
||||
return coreMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add provider-specific web search tool. Override in subclasses that support it.
|
||||
*/
|
||||
protected addWebSearchTool(_tools: ToolSet): void {}
|
||||
|
||||
/**
|
||||
* Build the tool set based on config.
|
||||
*/
|
||||
protected buildTools(config: LlmProviderConfig): ToolSet {
|
||||
const tools: ToolSet = {};
|
||||
|
||||
if (config.enableWebSearch) {
|
||||
this.addWebSearchTool(tools);
|
||||
}
|
||||
|
||||
if (config.contextNoteId) {
|
||||
Object.assign(tools, currentNoteTools(config.contextNoteId));
|
||||
}
|
||||
|
||||
if (config.enableNoteTools) {
|
||||
Object.assign(tools, noteTools);
|
||||
Object.assign(tools, attributeTools);
|
||||
Object.assign(tools, hierarchyTools);
|
||||
Object.assign(tools, skillTools);
|
||||
}
|
||||
|
||||
return tools;
|
||||
}
|
||||
|
||||
chat(messages: LlmMessage[], config: LlmProviderConfig): StreamResult {
|
||||
const systemPrompt = this.buildSystemPrompt(messages, config);
|
||||
const chatMessages = messages.filter(m => m.role !== "system");
|
||||
const coreMessages = this.buildMessages(chatMessages, systemPrompt);
|
||||
|
||||
const streamOptions: Parameters<typeof streamText>[0] = {
|
||||
model: this.createModel(config.model || this.defaultModel),
|
||||
messages: coreMessages,
|
||||
maxOutputTokens: config.maxTokens || DEFAULT_MAX_TOKENS
|
||||
};
|
||||
|
||||
const tools = this.buildTools(config);
|
||||
if (Object.keys(tools).length > 0) {
|
||||
streamOptions.tools = tools;
|
||||
streamOptions.stopWhen = stepCountIs(5);
|
||||
streamOptions.toolChoice = "auto";
|
||||
}
|
||||
|
||||
return streamText(streamOptions);
|
||||
}
|
||||
|
||||
getModelPricing(model: string): ModelPricing | undefined {
|
||||
return this.modelPricing[model];
|
||||
}
|
||||
|
||||
getAvailableModels(): ModelInfo[] {
|
||||
return this.availableModels;
|
||||
}
|
||||
|
||||
async generateTitle(firstMessage: string): Promise<string> {
|
||||
const { text } = await generateText({
|
||||
model: this.createModel(this.titleModel),
|
||||
maxOutputTokens: TITLE_MAX_TOKENS,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: `Summarize the following message as a very short chat title (max 6 words). Reply with ONLY the title, no quotes or punctuation at the end.\n\nMessage: ${firstMessage}`
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return text.trim();
|
||||
}
|
||||
}
|
||||
102
apps/server/src/services/llm/providers/google.ts
Normal file
102
apps/server/src/services/llm/providers/google.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { createGoogleGenerativeAI, type GoogleGenerativeAIProvider } from "@ai-sdk/google";
|
||||
import { streamText, stepCountIs, type ToolSet } from "ai";
|
||||
import type { LlmMessage } from "@triliumnext/commons";
|
||||
|
||||
import type { LlmProviderConfig, StreamResult } from "../types.js";
|
||||
import { BaseProvider, buildModelList } from "./base_provider.js";
|
||||
|
||||
/**
|
||||
* Available Google Gemini models with pricing (USD per million tokens).
|
||||
* Source: https://ai.google.dev/gemini-api/docs/pricing
|
||||
*/
|
||||
const { models: AVAILABLE_MODELS, pricing: MODEL_PRICING } = buildModelList([
|
||||
// ===== Current Models =====
|
||||
{
|
||||
id: "gemini-2.5-pro",
|
||||
name: "Gemini 2.5 Pro",
|
||||
pricing: { input: 1.25, output: 10 },
|
||||
contextWindow: 1048576
|
||||
},
|
||||
{
|
||||
id: "gemini-2.5-flash",
|
||||
name: "Gemini 2.5 Flash",
|
||||
pricing: { input: 0.3, output: 2.5 },
|
||||
contextWindow: 1048576,
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
id: "gemini-2.5-flash-lite",
|
||||
name: "Gemini 2.5 Flash-Lite",
|
||||
pricing: { input: 0.1, output: 0.4 },
|
||||
contextWindow: 1048576
|
||||
},
|
||||
{
|
||||
id: "gemini-2.0-flash",
|
||||
name: "Gemini 2.0 Flash",
|
||||
pricing: { input: 0.1, output: 0.4 },
|
||||
contextWindow: 1048576,
|
||||
isLegacy: true
|
||||
}
|
||||
]);
|
||||
|
||||
export class GoogleProvider extends BaseProvider {
|
||||
name = "google";
|
||||
protected defaultModel = "gemini-2.5-flash";
|
||||
protected titleModel = "gemini-2.5-flash-lite";
|
||||
protected availableModels = AVAILABLE_MODELS;
|
||||
protected modelPricing = MODEL_PRICING;
|
||||
|
||||
private google: GoogleGenerativeAIProvider;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
super();
|
||||
if (!apiKey) {
|
||||
throw new Error("API key is required for Google provider");
|
||||
}
|
||||
this.google = createGoogleGenerativeAI({ apiKey });
|
||||
}
|
||||
|
||||
protected createModel(modelId: string) {
|
||||
return this.google(modelId);
|
||||
}
|
||||
|
||||
protected override addWebSearchTool(tools: ToolSet): void {
|
||||
tools.google_search = this.google.tools.googleSearch({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Override chat to add Google-specific extended thinking support.
|
||||
* Gemini 2.5 uses thinkingBudget, Gemini 3.x uses thinkingLevel.
|
||||
*/
|
||||
override chat(messages: LlmMessage[], config: LlmProviderConfig): StreamResult {
|
||||
if (!config.enableExtendedThinking) {
|
||||
return super.chat(messages, config);
|
||||
}
|
||||
|
||||
const systemPrompt = this.buildSystemPrompt(messages, config);
|
||||
const chatMessages = messages.filter(m => m.role !== "system");
|
||||
const coreMessages = this.buildMessages(chatMessages, systemPrompt);
|
||||
|
||||
const streamOptions: Parameters<typeof streamText>[0] = {
|
||||
model: this.createModel(config.model || this.defaultModel),
|
||||
messages: coreMessages,
|
||||
maxOutputTokens: config.maxTokens || 8096,
|
||||
providerOptions: {
|
||||
google: {
|
||||
thinkingConfig: {
|
||||
thinkingBudget: config.thinkingBudget || 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tools = this.buildTools(config);
|
||||
if (Object.keys(tools).length > 0) {
|
||||
streamOptions.tools = tools;
|
||||
streamOptions.stopWhen = stepCountIs(5);
|
||||
streamOptions.toolChoice = "auto";
|
||||
}
|
||||
|
||||
return streamText(streamOptions);
|
||||
}
|
||||
}
|
||||
84
apps/server/src/services/llm/providers/openai.ts
Normal file
84
apps/server/src/services/llm/providers/openai.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { createOpenAI, type OpenAIProvider as OpenAISDKProvider } from "@ai-sdk/openai";
|
||||
import type { ToolSet } from "ai";
|
||||
|
||||
import { BaseProvider, buildModelList } from "./base_provider.js";
|
||||
|
||||
/**
|
||||
* Available OpenAI models with pricing (USD per million tokens).
|
||||
* Source: https://platform.openai.com/docs/pricing
|
||||
*/
|
||||
const { models: AVAILABLE_MODELS, pricing: MODEL_PRICING } = buildModelList([
|
||||
// ===== Current Models =====
|
||||
{
|
||||
id: "gpt-4.1",
|
||||
name: "GPT-4.1",
|
||||
pricing: { input: 2, output: 8 },
|
||||
contextWindow: 1047576,
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
id: "gpt-4.1-mini",
|
||||
name: "GPT-4.1 Mini",
|
||||
pricing: { input: 0.4, output: 1.6 },
|
||||
contextWindow: 1047576
|
||||
},
|
||||
{
|
||||
id: "gpt-4.1-nano",
|
||||
name: "GPT-4.1 Nano",
|
||||
pricing: { input: 0.1, output: 0.4 },
|
||||
contextWindow: 1047576
|
||||
},
|
||||
{
|
||||
id: "o3",
|
||||
name: "o3",
|
||||
pricing: { input: 2, output: 8 },
|
||||
contextWindow: 200000
|
||||
},
|
||||
{
|
||||
id: "o4-mini",
|
||||
name: "o4-mini",
|
||||
pricing: { input: 1.1, output: 4.4 },
|
||||
contextWindow: 200000
|
||||
},
|
||||
// ===== Legacy Models =====
|
||||
{
|
||||
id: "gpt-4o",
|
||||
name: "GPT-4o",
|
||||
pricing: { input: 2.5, output: 10 },
|
||||
contextWindow: 128000,
|
||||
isLegacy: true
|
||||
},
|
||||
{
|
||||
id: "gpt-4o-mini",
|
||||
name: "GPT-4o Mini",
|
||||
pricing: { input: 0.15, output: 0.6 },
|
||||
contextWindow: 128000,
|
||||
isLegacy: true
|
||||
}
|
||||
]);
|
||||
|
||||
export class OpenAiProvider extends BaseProvider {
|
||||
name = "openai";
|
||||
protected defaultModel = "gpt-4.1";
|
||||
protected titleModel = "gpt-4.1-mini";
|
||||
protected availableModels = AVAILABLE_MODELS;
|
||||
protected modelPricing = MODEL_PRICING;
|
||||
|
||||
private openai: OpenAISDKProvider;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
super();
|
||||
if (!apiKey) {
|
||||
throw new Error("API key is required for OpenAI provider");
|
||||
}
|
||||
this.openai = createOpenAI({ apiKey });
|
||||
}
|
||||
|
||||
protected createModel(modelId: string) {
|
||||
return this.openai(modelId);
|
||||
}
|
||||
|
||||
protected override addWebSearchTool(tools: ToolSet): void {
|
||||
tools.web_search = this.openai.tools.webSearch();
|
||||
}
|
||||
}
|
||||
78
apps/server/src/services/llm/skills/index.ts
Normal file
78
apps/server/src/services/llm/skills/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* LLM skills — on-demand instruction sets that an LLM can load when it needs
|
||||
* specialized knowledge (e.g. search syntax). Only names and descriptions are
|
||||
* included in the system prompt; full content is fetched via the load_skill tool.
|
||||
*/
|
||||
|
||||
import { readFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
import resourceDir from "../../resource_dir.js";
|
||||
|
||||
const SKILLS_DIR = join(resourceDir.RESOURCE_DIR, "llm", "skills");
|
||||
|
||||
interface SkillDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
file: string;
|
||||
}
|
||||
|
||||
const SKILLS: SkillDefinition[] = [
|
||||
{
|
||||
name: "search_syntax",
|
||||
description: "Trilium search query syntax reference — labels, relations, note properties, boolean logic, ordering, and more.",
|
||||
file: "search_syntax.md"
|
||||
},
|
||||
{
|
||||
name: "backend_scripting",
|
||||
description: "Backend (Node.js) scripting API — creating notes, handling events, accessing entities, database operations, and automation.",
|
||||
file: "backend_scripting.md"
|
||||
},
|
||||
{
|
||||
name: "frontend_scripting",
|
||||
description: "Frontend (browser) scripting API — UI widgets, navigation, dialogs, editor access, Preact/JSX components, and keyboard shortcuts.",
|
||||
file: "frontend_scripting.md"
|
||||
}
|
||||
];
|
||||
|
||||
async function loadSkillContent(name: string): Promise<string | null> {
|
||||
const skill = SKILLS.find((s) => s.name === name);
|
||||
if (!skill) {
|
||||
return null;
|
||||
}
|
||||
return readFile(join(SKILLS_DIR, skill.file), "utf-8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a summary of available skills for inclusion in the system prompt.
|
||||
*/
|
||||
export function getSkillsSummary(): string {
|
||||
return SKILLS
|
||||
.map((s) => `- **${s.name}**: ${s.description}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* The load_skill tool — lets the LLM fetch full instructions on demand.
|
||||
*/
|
||||
export const loadSkill = tool({
|
||||
description: "Load a skill to get specialized instructions. Available skills:\n"
|
||||
+ SKILLS.map((s) => `- ${s.name}: ${s.description}`).join("\n"),
|
||||
inputSchema: z.object({
|
||||
name: z.string().describe("The skill name to load")
|
||||
}),
|
||||
execute: async ({ name }) => {
|
||||
const content = await loadSkillContent(name);
|
||||
if (!content) {
|
||||
return { error: `Unknown skill: '${name}'. Available: ${SKILLS.map((s) => s.name).join(", ")}` };
|
||||
}
|
||||
return { skill: name, instructions: content };
|
||||
}
|
||||
});
|
||||
|
||||
export const skillTools = {
|
||||
load_skill: loadSkill
|
||||
};
|
||||
102
apps/server/src/services/llm/tools/hierarchy_tools.ts
Normal file
102
apps/server/src/services/llm/tools/hierarchy_tools.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* LLM tools for navigating the note hierarchy (tree structure, branches).
|
||||
*/
|
||||
|
||||
import { tool } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
import becca from "../../../becca/becca.js";
|
||||
import type BNote from "../../../becca/entities/bnote.js";
|
||||
|
||||
/**
|
||||
* Get the child notes of a given note.
|
||||
*/
|
||||
export const getChildNotes = tool({
|
||||
description: "Get the immediate child notes of a note. Returns each child's ID, title, type, and whether it has children of its own. Use noteId 'root' to list top-level notes.",
|
||||
inputSchema: z.object({
|
||||
noteId: z.string().describe("The ID of the parent note (use 'root' for top-level)")
|
||||
}),
|
||||
execute: async ({ noteId }) => {
|
||||
const note = becca.getNote(noteId);
|
||||
if (!note) {
|
||||
return { error: "Note not found" };
|
||||
}
|
||||
|
||||
return note.getChildNotes().map((child) => ({
|
||||
noteId: child.noteId,
|
||||
title: child.getTitleOrProtected(),
|
||||
type: child.type,
|
||||
childCount: child.getChildNotes().length
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
//#region Subtree tool implementation
|
||||
const MAX_DEPTH = 5;
|
||||
const MAX_CHILDREN_PER_LEVEL = 10;
|
||||
|
||||
interface SubtreeNode {
|
||||
noteId: string;
|
||||
title: string;
|
||||
type: string;
|
||||
children?: SubtreeNode[] | string;
|
||||
}
|
||||
|
||||
function buildSubtree(note: BNote, depth: number, maxDepth: number): SubtreeNode {
|
||||
const node: SubtreeNode = {
|
||||
noteId: note.noteId,
|
||||
title: note.getTitleOrProtected(),
|
||||
type: note.type
|
||||
};
|
||||
|
||||
if (depth >= maxDepth) {
|
||||
const childCount = note.getChildNotes().length;
|
||||
if (childCount > 0) {
|
||||
node.children = `${childCount} children not shown (depth limit reached)`;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
const children = note.getChildNotes();
|
||||
if (children.length === 0) {
|
||||
return node;
|
||||
}
|
||||
|
||||
const shown = children.slice(0, MAX_CHILDREN_PER_LEVEL);
|
||||
node.children = shown.map((child) => buildSubtree(child, depth + 1, maxDepth));
|
||||
|
||||
if (children.length > MAX_CHILDREN_PER_LEVEL) {
|
||||
node.children.push({
|
||||
noteId: "",
|
||||
title: `... and ${children.length - MAX_CHILDREN_PER_LEVEL} more`,
|
||||
type: "truncated"
|
||||
});
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a subtree of notes up to a specified depth.
|
||||
*/
|
||||
export const getSubtree = tool({
|
||||
description: "Get a nested subtree of notes starting from a given note, traversing multiple levels deep. Useful for understanding the structure of a section of the note tree. Each level shows up to 10 children.",
|
||||
inputSchema: z.object({
|
||||
noteId: z.string().describe("The ID of the root note for the subtree (use 'root' for the entire tree)"),
|
||||
depth: z.number().min(1).max(MAX_DEPTH).optional().describe(`How many levels deep to traverse (1-${MAX_DEPTH}). Defaults to 2.`)
|
||||
}),
|
||||
execute: async ({ noteId, depth = 2 }) => {
|
||||
const note = becca.getNote(noteId);
|
||||
if (!note) {
|
||||
return { error: "Note not found" };
|
||||
}
|
||||
|
||||
return buildSubtree(note, 0, depth);
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
|
||||
export const hierarchyTools = {
|
||||
get_child_notes: getChildNotes,
|
||||
get_subtree: getSubtree
|
||||
};
|
||||
@@ -5,3 +5,5 @@
|
||||
|
||||
export { noteTools, currentNoteTools } from "./note_tools.js";
|
||||
export { attributeTools } from "./attribute_tools.js";
|
||||
export { hierarchyTools } from "./hierarchy_tools.js";
|
||||
export { skillTools } from "../skills/index.js";
|
||||
|
||||
@@ -43,15 +43,33 @@ function setNoteContentFromLlm(note: { type: string; title: string; setContent:
|
||||
* Search for notes in the knowledge base.
|
||||
*/
|
||||
export const searchNotes = tool({
|
||||
description: "Search for notes in the user's knowledge base. Returns note metadata including title, type, and IDs.",
|
||||
description: [
|
||||
"Search for notes in the user's knowledge base using Trilium search syntax.",
|
||||
"For complex queries (boolean logic, relations, regex, ordering), load the 'search_syntax' skill first via load_skill.",
|
||||
"Common patterns:",
|
||||
"- Full-text: 'rings tolkien' (notes containing both words)",
|
||||
"- By label: '#book', '#status = done', '#year >= 2000'",
|
||||
"- By type: 'note.type = code'",
|
||||
"- By relation: '~author', '~author.title *= Tolkien'",
|
||||
"- Combined: 'tolkien #book' (full-text + label filter)",
|
||||
"- Negation: '#!archived' (notes WITHOUT label)"
|
||||
].join(" "),
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe("Search query (supports Trilium search syntax)")
|
||||
query: z.string().describe("Search query in Trilium search syntax"),
|
||||
fastSearch: z.boolean().optional().describe("If true, skip content search (only titles and attributes). Faster for large databases."),
|
||||
includeArchivedNotes: z.boolean().optional().describe("If true, include archived notes in results."),
|
||||
ancestorNoteId: z.string().optional().describe("Limit search to a subtree rooted at this note ID."),
|
||||
limit: z.number().optional().describe("Maximum number of results to return. Defaults to 10.")
|
||||
}),
|
||||
execute: async ({ query }) => {
|
||||
const searchContext = new SearchContext({});
|
||||
execute: async ({ query, fastSearch, includeArchivedNotes, ancestorNoteId, limit = 10 }) => {
|
||||
const searchContext = new SearchContext({
|
||||
fastSearch,
|
||||
includeArchivedNotes,
|
||||
ancestorNoteId
|
||||
});
|
||||
const results = searchService.findResultsWithQuery(query, searchContext);
|
||||
|
||||
return results.slice(0, 10).map(sr => {
|
||||
return results.slice(0, limit).map(sr => {
|
||||
const note = becca.notes[sr.noteId];
|
||||
if (!note) return null;
|
||||
return {
|
||||
@@ -168,14 +186,27 @@ export const appendToNote = tool({
|
||||
* Create a new note.
|
||||
*/
|
||||
export const createNote = tool({
|
||||
description: "Create a new note in the user's knowledge base. Returns the created note's ID and title.",
|
||||
description: [
|
||||
"Create a new note in the user's knowledge base. Returns the created note's ID and title.",
|
||||
"Set type to 'text' for rich text notes (content in Markdown) or 'code' for code notes (must also set mime).",
|
||||
"Common mime values for code notes:",
|
||||
"'application/javascript;env=frontend' (JS frontend),",
|
||||
"'application/javascript;env=backend' (JS backend),",
|
||||
"'text/jsx' (Preact JSX, preferred for frontend widgets),",
|
||||
"'text/css', 'text/html', 'application/json', 'text/x-python', 'text/x-sh'."
|
||||
].join(" "),
|
||||
inputSchema: z.object({
|
||||
parentNoteId: z.string().describe("The ID of the parent note where the new note will be created. Use 'root' for top-level notes."),
|
||||
parentNoteId: z.string().describe("The ID of the parent note. Use 'root' for top-level notes."),
|
||||
title: z.string().describe("The title of the new note"),
|
||||
content: z.string().describe("The content of the note (Markdown for text notes, plain text for code notes)"),
|
||||
type: z.enum(["text", "code"]).optional().describe("The type of note to create. Defaults to 'text'.")
|
||||
type: z.enum(["text", "code"]).describe("The type of note to create."),
|
||||
mime: z.string().optional().describe("MIME type, REQUIRED for code notes (e.g. 'application/javascript;env=backend', 'text/jsx'). Ignored for text notes.")
|
||||
}),
|
||||
execute: async ({ parentNoteId, title, content, type = "text" }) => {
|
||||
execute: async ({ parentNoteId, title, content, type, mime }) => {
|
||||
if (type === "code" && !mime) {
|
||||
return { error: "mime is required when creating code notes" };
|
||||
}
|
||||
|
||||
const parentNote = becca.getNote(parentNoteId);
|
||||
if (!parentNote) {
|
||||
return { error: "Parent note not found" };
|
||||
@@ -193,7 +224,8 @@ export const createNote = tool({
|
||||
parentNoteId,
|
||||
title,
|
||||
content: htmlContent,
|
||||
type
|
||||
type,
|
||||
...(mime ? { mime } : {})
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -38,6 +38,8 @@ export interface ModelInfo {
|
||||
id: string;
|
||||
/** Human-readable name (e.g., "Claude Sonnet 4") */
|
||||
name: string;
|
||||
/** Provider type that owns this model (e.g., "anthropic", "openai") */
|
||||
provider?: string;
|
||||
/** Pricing per million tokens */
|
||||
pricing: ModelPricing;
|
||||
/** Whether this is the default model */
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@preact/preset-vite": "2.10.5",
|
||||
"eslint": "10.1.0",
|
||||
"eslint-config-preact": "2.0.0",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "6.0.2",
|
||||
"user-agent-data-types": "0.4.3",
|
||||
"vite": "8.0.3",
|
||||
"vitest": "4.1.2"
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"title": "Ressources",
|
||||
"icon_packs": "Packs d'icônes",
|
||||
"download": "Télécharger",
|
||||
"website": "Site Internet"
|
||||
"website": "Site Internet",
|
||||
"icon_packs_intro": "Élargissez la sélection d’icônes disponibles pour vos notes en utilisant un pack d’icônes. Pour plus d'informations sur les packs d'icônes, consultez <DocumentationLink>la documentation officielle</DocumentationLink>."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
"rollup-plugin-webpack-stats": "3.1.0",
|
||||
"tslib": "2.8.1",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "6.0.2",
|
||||
"typescript-eslint": "8.57.2",
|
||||
"upath": "2.0.1",
|
||||
"vite": "8.0.3",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"stylelint": "17.6.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "6.0.2",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.1.2",
|
||||
"webdriverio": "9.27.0"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
"outDir": "out-tsc",
|
||||
"typeRoots": [
|
||||
"typings",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"stylelint": "17.6.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "6.0.2",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.1.2",
|
||||
"webdriverio": "9.27.0"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
"outDir": "out-tsc",
|
||||
"typeRoots": [
|
||||
"typings",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"stylelint": "17.6.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "6.0.2",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.1.2",
|
||||
"webdriverio": "9.27.0"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
"outDir": "out-tsc",
|
||||
"typeRoots": [
|
||||
"typings",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"stylelint": "17.6.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "6.0.2",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.1.2",
|
||||
"webdriverio": "9.27.0"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
"outDir": "out-tsc",
|
||||
"typeRoots": [
|
||||
"typings",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"stylelint": "17.6.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript": "6.0.2",
|
||||
"vite-plugin-svgo": "2.0.0",
|
||||
"vitest": "4.1.2",
|
||||
"webdriverio": "9.27.0"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
"outDir": "out-tsc",
|
||||
"typeRoots": [
|
||||
"typings",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"ckeditor5-premium-features": "47.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@smithy/middleware-retry": "4.4.44",
|
||||
"@smithy/middleware-retry": "4.4.45",
|
||||
"@types/jquery": "4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||
|
||||
@@ -63,6 +63,8 @@ export interface LlmModelInfo {
|
||||
id: string;
|
||||
/** Human-readable name (e.g., "Claude Sonnet 4") */
|
||||
name: string;
|
||||
/** Provider type that owns this model (e.g., "anthropic", "openai") */
|
||||
provider?: string;
|
||||
/** Pricing per million tokens */
|
||||
pricing: LlmModelPricing;
|
||||
/** Whether this is the default model */
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||
|
||||
@@ -37,6 +37,6 @@
|
||||
"esbuild": "0.27.4",
|
||||
"eslint": "10.1.0",
|
||||
"highlight.js": "11.11.1",
|
||||
"typescript": "5.9.3"
|
||||
"typescript": "6.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default function debounce<T extends (...args: unknown[]) => unknown>(executor: T, delay: number) {
|
||||
let timeout: NodeJS.Timeout | null;
|
||||
let timeout: ReturnType<typeof setTimeout> | null;
|
||||
return function(...args: Parameters<T>): void {
|
||||
const callback = () => {
|
||||
timeout = null;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
|
||||
|
||||
2540
pnpm-lock.yaml
generated
2540
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@
|
||||
"customConditions": ["development"],
|
||||
"verbatimModuleSyntax": false, // TODO: Re-enable it when migrating back to ESM.
|
||||
"resolveJsonModule": true,
|
||||
"downlevelIteration": true,
|
||||
"esModuleInterop": true,
|
||||
"noUncheckedSideEffectImports": false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user