import { Action, ActionPanel, Clipboard, Form, Icon, showToast, Toast, useNavigation, } from "@raycast/api"; import { useEffect, useMemo, useState } from "react"; import { type Creativity, CREATIVITY_OPTIONS, DEFAULT_MODEL, getLastUsedModel, MODEL_OPTIONS, setLastUsedModel, STRICT_PLACEHOLDER_RULE, } from "./ai"; import { PseudonymizationConfirm } from "./ai-views"; import { getSelectedTextSafely } from "./selection"; import { createSession, getActiveSessionId, getSession, listSessions, setActiveSession, updateSessionMapping, } from "./sessions"; import type { EntityType, VelumSession } from "./types"; import { NEW_SESSION_ID } from "./ui"; import { getEntityTypes, normalizePseudonymizeResponse, pseudonymize, } from "./velum"; function buildActionItemsPrompt( pseudonymizedText: string, instructions: string, ): string { const extra = instructions.trim() ? `\n\nZusätzliche Anweisungen des Nutzers:\n${instructions.trim()}` : ""; return [ "Du erhältst pseudonymisierten Text (Meeting-Notizen, Email-Thread, Transkript).", "Personenbezogene Daten wurden durch Platzhalter wie , , ersetzt.", "", STRICT_PLACEHOLDER_RULE, "", "Aufgabe: Extrahiere alle Action Items aus dem Text als deutschsprachige Markdown-Tabelle.", "", "Format:", "| Aufgabe | Verantwortlich | Deadline | Status |", "| --- | --- | --- | --- |", "", "Regeln:", "- Liste nur konkret formulierte Aufgaben/Zusagen, keine vagen Absichten.", '- Verantwortliche werden als Platzhalter referenziert (z. B. ); wenn unklar, schreibe „—".', '- Deadline: Datum oder Zeitraum aus dem Text. Wenn nichts genannt, „—".', '- Status: „Offen", „In Arbeit" oder „Erledigt" — wenn nicht erkennbar, „Offen".', '- Wenn der Text keine Action Items enthält, antworte mit Kopfzeile, Trennzeile und einer Zeile „| Keine Action Items | — | — | — |".', "", "Antworte ausschließlich mit der Tabelle, ohne einleitende Floskeln und ohne Code-Fences (keine ``` Zeilen).", extra, "", "--- Text (pseudonymisiert) ---", pseudonymizedText, "--- Ende ---", ].join("\n"); } type FormValues = { text: string; sessionId: string; entityTypes: string[]; model: string; creativity: Creativity; instructions: string; }; export default function Command() { const [text, setText] = useState(""); const [sessions, setSessions] = useState([]); const [activeSessionId, setActiveSessionId] = useState(); const [entityTypes, setEntityTypes] = useState([]); const [selectedEntityTypes, setSelectedEntityTypes] = useState([]); const [model, setModel] = useState(DEFAULT_MODEL); const [isLoading, setIsLoading] = useState(true); const { push } = useNavigation(); useEffect(() => { async function load() { const [loadedSessions, activeId, lastModel] = await Promise.all([ listSessions(), getActiveSessionId(), getLastUsedModel(), ]); setSessions(loadedSessions); setActiveSessionId(activeId); setModel(lastModel); const selection = await getSelectedTextSafely(); if (selection && selection.trim()) { setText(selection); } try { const loadedEntityTypes = await getEntityTypes(); setEntityTypes(loadedEntityTypes); setSelectedEntityTypes(loadedEntityTypes); } catch { setEntityTypes([]); } finally { setIsLoading(false); } } load(); }, []); const defaultSessionId = useMemo( () => activeSessionId || sessions[0]?.id || NEW_SESSION_ID, [activeSessionId, sessions], ); async function loadSelectedText() { const selected = await getSelectedTextSafely(); if (!selected) { await showToast({ style: Toast.Style.Failure, title: "Kein markierter Text gefunden", }); return; } setText(selected); } async function loadClipboardText() { const clipboardText = await Clipboard.readText(); if (!clipboardText) { await showToast({ style: Toast.Style.Failure, title: "Zwischenablage enthält keinen Text", }); return; } setText(clipboardText); } async function handleSubmit(values: FormValues) { const input = values.text.trim(); if (!input) { await showToast({ style: Toast.Style.Failure, title: "Text zum Extrahieren eingeben", }); return; } void setLastUsedModel(values.model); const toast = await showToast({ style: Toast.Style.Animated, title: "Pseudonymisiere…", }); try { const session = values.sessionId === NEW_SESSION_ID ? await createSession() : ((await getSession(values.sessionId)) ?? (await createSession())); await setActiveSession(session.id); const rawResult = await pseudonymize( input, session.mapping, values.entityTypes, ); const pseudoResult = normalizePseudonymizeResponse(rawResult); const updatedSession = await updateSessionMapping( session.id, pseudoResult.mapping, pseudoResult.selected_entity_types, ); toast.style = Toast.Style.Success; toast.title = "Pseudonymisiert"; toast.message = `${pseudoResult.entity_count} Zuordnungen`; push( , ); } catch (error) { toast.style = Toast.Style.Failure; toast.title = "Pseudonymisierung fehlgeschlagen"; toast.message = error instanceof Error ? error.message : String(error); } } return (
} > {sessions.map((session) => ( ))} {entityTypes.length > 0 ? ( {entityTypes.map((entityType) => ( ))} ) : null} {MODEL_OPTIONS.map((option) => ( ))} {CREATIVITY_OPTIONS.map((value) => ( ))} ); }