+ {selected.subject} +
++ {selected.fromName} + + <{selected.fromEmail}> + +
++ À moi · {selected.time} +
++ {paragraph} +
+ ))} +diff --git a/.env.example b/.env.example index 6f43c74..979aab3 100644 --- a/.env.example +++ b/.env.example @@ -21,3 +21,5 @@ OIDC_CLIENT_SECRET=changeme NEXT_PUBLIC_ONLYOFFICE_URL=http://localhost/office # Rich text editor (TipTap + Hocuspocus — docs texte) NEXT_PUBLIC_HOCUSPOCUS_URL=ws://localhost/collab +# UltiAI (chemin proxy OpenWebUI — même origine) +NEXT_PUBLIC_AI_PUBLIC_PATH=/ai diff --git a/app/api/demo/richtext-save/route.ts b/app/api/demo/richtext-save/route.ts new file mode 100644 index 0000000..f5081ac --- /dev/null +++ b/app/api/demo/richtext-save/route.ts @@ -0,0 +1,11 @@ +/** + * Sauvegarde factice pour la démo publique UltiDocs. + * Zéro rétention : le corps de la requête est ignoré. + */ +export async function PUT() { + return new Response(null, { status: 204 }) +} + +export async function POST() { + return new Response(null, { status: 204 }) +} diff --git a/app/chat/page.tsx b/app/chat/page.tsx new file mode 100644 index 0000000..96ad825 --- /dev/null +++ b/app/chat/page.tsx @@ -0,0 +1,50 @@ +"use client" + +import { Sparkles } from "lucide-react" +import { AiChatIframe } from "@/components/ai/ai-chat-iframe" +import { useAiConfig, useAiQuota } from "@/lib/api/hooks/use-ai-queries" + +export default function ChatPage() { + const { data: config, isLoading } = useAiConfig() + const { data: quota } = useAiQuota(Boolean(config?.enabled)) + + if (isLoading) { + return ( +
+ UltiAI n'est pas activé. Activez le plugin dans l'administration. +
+
diff --git a/app/mail/mail-app-shell.tsx b/app/mail/mail-app-shell.tsx
index 25aaede..1e99781 100644
--- a/app/mail/mail-app-shell.tsx
+++ b/app/mail/mail-app-shell.tsx
@@ -45,6 +45,7 @@ import { MailNotificationsBridge } from "@/components/gmail/mail-notifications-b
import { useWebSocket } from "@/lib/api/ws"
import { useMailSettingsStore } from "@/lib/stores/mail-settings-store"
import { FilePreviewDialog } from "@/components/drive/file-preview-dialog"
+import { AiChatPanel } from "@/components/ai/ai-chat-panel"
const MAIL_SETTINGS_PATH = "/mail/settings"
@@ -191,6 +192,7 @@ function MailAppInner() {
+ Les panneaux mail/drive/contacts ne sauvegardent pas l'historique. +
++ Pipeline OpenWebUI → fichiers .ultichat.json sur le drive utilisateur. +
++ Mode lecture — l'IA peut analyser le document mais pas le modifier. +
+ ) : null} ++ {selected.fromName} + + <{selected.fromEmail}> + +
++ À moi · {selected.time} +
++ {paragraph} +
+ ))} ++ {query + ? "Aucun message ne correspond à votre recherche." + : "Ce dossier est vide."} +
+{error}
+ {showBack ? ( + + ) : null} +{error ?? "Document introuvable"} @@ -180,24 +189,28 @@ export function RichTextEditor({ fileId }: { fileId: string }) {
{attrs.graphicType === "image" ? "Options image" - : attrs.graphicType === "shape" - ? "Options forme" - : "Options dégradé"} + : attrs.graphicType === "draw" + ? "Options dessin" + : attrs.graphicType === "shape" + ? "Options forme" + : "Options dégradé"}
+ {label} +
++ Sélectionnez du texte pour créer un hyperlien. +
+ ) : null} ++ Saisissez au moins 2 caractères pour rechercher dans votre Drive. +
+ ) : driveLoading ? ( ++ Aucun fichier pour « {trimmedDriveQuery} » +
+ ) : ( + driveSuggestions.map((item) => ( ++ {title} +
+ ) : null} +{subtitle}
++ Le nouveau style sera enregistré dans vos styles personnels. +
+{loadError}
+{collabError}
++ {error ?? "Dessin introuvable"} +
+ +