Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Replaced hardcoded "Agenda" labels with dynamic ULTICAL_APP_NAME in various components for consistency. - Introduced new AiUsageSection and CompteAiUsageSection components to track AI usage and costs. - Updated settings and metadata to reflect changes in AI cost policies and usage limits. - Enhanced user interface elements for better accessibility and user experience across admin settings.
96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
export type AiEmbedApp = "mail" | "drive" | "contacts" | "docs" | "standalone"
|
|
|
|
export type AiChatContext = {
|
|
app: AiEmbedApp
|
|
temporary?: boolean
|
|
messageId?: string
|
|
accountId?: string
|
|
drivePath?: string
|
|
fileId?: string
|
|
contactId?: string
|
|
subject?: string
|
|
snippet?: string
|
|
/** UltiDocs — titre affiché */
|
|
documentTitle?: string
|
|
/** Chemin du fichier source (.docx, .md…) si sidecar */
|
|
sourcePath?: string
|
|
/** Extrait texte du document */
|
|
documentExcerpt?: string
|
|
/** Sélection courante dans l'éditeur */
|
|
selectionText?: string
|
|
/** JSON TipTap (éventuellement tronqué) */
|
|
documentJson?: string
|
|
/** Prompt système additionnel (non sérialisé en URL) */
|
|
systemPromptExtra?: string
|
|
}
|
|
|
|
export type AiPostMessage =
|
|
| {
|
|
type: "ULTI_CONTEXT_UPDATE"
|
|
context: AiChatContext
|
|
systemPrompt?: string
|
|
}
|
|
| { type: "ULTI_DOCS_APPLY"; payload: unknown }
|
|
| { type: "ULTI_ASSISTANT_TEXT"; text: string }
|
|
| { type: "ULTI_THEME"; theme: "light" | "dark" }
|
|
| {
|
|
type: "ULTI_SESSION"
|
|
token_secret?: string
|
|
mcp_url?: string
|
|
enabled_tools?: string[]
|
|
session_id?: string
|
|
}
|
|
| { type: "ULTI_OPEN_LINK"; href: string }
|
|
| { type: "ULTI_TOOL_RESULT"; payload: unknown }
|
|
|
|
export function buildEmbedSearchParams(
|
|
context: AiChatContext,
|
|
defaultModel?: string,
|
|
): string {
|
|
const params = new URLSearchParams()
|
|
const model = defaultModel?.trim()
|
|
if (model) params.set("model", model)
|
|
if (context.temporary !== false) params.set("temporary-chat", "true")
|
|
if (context.app) params.set("app", context.app)
|
|
if (context.messageId) params.set("message_id", context.messageId)
|
|
if (context.accountId) params.set("account_id", context.accountId)
|
|
if (context.drivePath) params.set("path", context.drivePath)
|
|
if (context.fileId) params.set("file_id", context.fileId)
|
|
if (context.contactId) params.set("contact_id", context.contactId)
|
|
if (context.subject) params.set("subject", context.subject)
|
|
if (context.snippet) params.set("snippet", context.snippet)
|
|
return params.toString()
|
|
}
|
|
|
|
export function systemPromptFromContext(context: AiChatContext): string {
|
|
const lines = [
|
|
"Tu es UltiAI, l'assistant intégré à la suite Ultimail (mail, drive, contacts, UltiCal).",
|
|
"Réponds en français sauf demande contraire. Utilise les tools disponibles pour agir sur les données utilisateur.",
|
|
"Recherche suite (index local) via suite_search ; recherche web publique via web_search si configurée.",
|
|
"Après chaque appel d'outil, réponds toujours en langage naturel : résume le résultat, cite les sources (sujet, chemin, nom), propose la suite.",
|
|
"Ne termine jamais un tour utilisateur avec uniquement un appel d'outil sans texte explicatif.",
|
|
"Respecte strictement le paramètre limit des tools (ne demande pas plus de résultats que nécessaire).",
|
|
]
|
|
if (context.app === "mail" && context.subject) {
|
|
lines.push(`Contexte mail — sujet: ${context.subject}`)
|
|
if (context.snippet) lines.push(`Extrait: ${context.snippet}`)
|
|
}
|
|
if (context.app === "drive" && context.drivePath) {
|
|
lines.push(`Contexte drive — fichier/dossier: ${context.drivePath}`)
|
|
}
|
|
if (context.app === "contacts" && context.contactId) {
|
|
lines.push(`Contexte contacts — fiche: ${context.contactId}`)
|
|
}
|
|
if (context.app === "docs") {
|
|
if (context.documentTitle) lines.push(`Document: ${context.documentTitle}`)
|
|
if (context.drivePath) lines.push(`Sidecar: ${context.drivePath}`)
|
|
if (context.sourcePath) lines.push(`Source: ${context.sourcePath}`)
|
|
if (context.selectionText) lines.push(`Sélection: ${context.selectionText}`)
|
|
if (context.documentExcerpt) lines.push(`Contenu:\n${context.documentExcerpt}`)
|
|
}
|
|
if (context.systemPromptExtra) {
|
|
lines.push("", context.systemPromptExtra)
|
|
}
|
|
return lines.join("\n")
|
|
}
|