"use server"; import { revalidatePath } from "next/cache"; import type { ChatView, LotseChatActionState } from "@/lib/lotse/chat"; import { moduleGuard } from "@/server/action-guard"; import { ctxFromGuard, type ServiceCtx } from "@/server/services/context"; import { confirmAllProposals, confirmProposal, discardProposal } from "@/server/services/lotse/chat/confirm"; import { getChatView, startNewConversation } from "@/server/services/lotse/chat/conversations"; import { sendLotseMessage } from "@/server/services/lotse/chat/engine"; import { chatErrorState, field } from "./_chat-state"; /** * Lotse chat for technicians (lane L16) — thin adapters: moduleGuard("lotse") with DB-authoritative * permissions → service → fresh chat view. Every action needs `lotse:use` + `field:execute`; the * services check the tenant switch, ownership and scope again. */ const guard = moduleGuard("lotse"); async function viewOrUndefined(ctx: ServiceCtx, conversationId: string | null): Promise { try { return conversationId ? await getChatView(ctx, { conversationId }) : undefined; } catch { return undefined; } } function revalidateOrder(view: ChatView) { if (view.workOrder) revalidatePath(`/m/orders/${view.workOrder.id}`); revalidatePath("/m", "layout"); } /** Send a chat message (fields: text, conversationId?, workOrderId?). */ export async function sendChatMessageAction(_prev: LotseChatActionState, fd: FormData): Promise { let ctx: ServiceCtx | null = null; try { ctx = ctxFromGuard(await guard("lotse:use", "field:execute")); const view = await sendLotseMessage(ctx, { text: fd.get("text")?.toString() ?? "", conversationId: field(fd, "conversationId"), workOrderId: field(fd, "workOrderId") }); return { status: "ok", at: Date.now(), view }; } catch (err) { return chatErrorState(err, ctx ? await viewOrUndefined(ctx, field(fd, "conversationId")) : undefined); } } /** Confirm one card (fields: proposalId, conversationId, edits? = JSON object of changed values). */ export async function confirmProposalAction(_prev: LotseChatActionState, fd: FormData): Promise { let ctx: ServiceCtx | null = null; try { ctx = ctxFromGuard(await guard("lotse:use", "field:execute")); const rawEdits = field(fd, "edits"); let edits: Record | null = null; if (rawEdits) { const parsed: unknown = JSON.parse(rawEdits); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) edits = parsed as Record; } await confirmProposal(ctx, { proposalId: field(fd, "proposalId") ?? "", edits }); const view = await getChatView(ctx, { conversationId: field(fd, "conversationId") }); revalidateOrder(view); return { status: "ok", at: Date.now(), view }; } catch (err) { return chatErrorState(err, ctx ? await viewOrUndefined(ctx, field(fd, "conversationId")) : undefined); } } /** Discard one card (fields: proposalId, conversationId). */ export async function discardProposalAction(_prev: LotseChatActionState, fd: FormData): Promise { let ctx: ServiceCtx | null = null; try { ctx = ctxFromGuard(await guard("lotse:use", "field:execute")); await discardProposal(ctx, { proposalId: field(fd, "proposalId") ?? "" }); return { status: "ok", at: Date.now(), view: await getChatView(ctx, { conversationId: field(fd, "conversationId") }) }; } catch (err) { return chatErrorState(err, ctx ? await viewOrUndefined(ctx, field(fd, "conversationId")) : undefined); } } /** „Alle bestätigen" for the cards of one message (fields: messageId, conversationId). */ export async function confirmAllProposalsAction(_prev: LotseChatActionState, fd: FormData): Promise { let ctx: ServiceCtx | null = null; try { ctx = ctxFromGuard(await guard("lotse:use", "field:execute")); await confirmAllProposals(ctx, { messageId: field(fd, "messageId") ?? "" }); const view = await getChatView(ctx, { conversationId: field(fd, "conversationId") }); revalidateOrder(view); return { status: "ok", at: Date.now(), view }; } catch (err) { return chatErrorState(err, ctx ? await viewOrUndefined(ctx, field(fd, "conversationId")) : undefined); } } /** „Neuer Chat" (field: workOrderId?). */ export async function newConversationAction(_prev: LotseChatActionState, fd: FormData): Promise { try { const ctx = ctxFromGuard(await guard("lotse:use", "field:execute")); return { status: "ok", at: Date.now(), view: await startNewConversation(ctx, { workOrderId: field(fd, "workOrderId") }) }; } catch (err) { return chatErrorState(err); } }