From 6cfd244b4110d3bc9de8a258ad6712d39454f304 Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Wed, 19 Aug 2026 08:35:00 -0600 Subject: [PATCH] # Conversation Summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Conversation Overview A single user message. The user pasted a large, partially garbled/mangled unified diff of changes to a **Next.js self‑hosted board/to‑do web app** and asked the assistant to *"Generate a concise and descriptive git commit message for the selected code changes. The message should summarize the changes made, their purpose, and any relevant context. Do not edit any files, just provide the commit message as output."* No assistant reply has been produced yet. ## Subject of the Diff (what the changes do) The diff introduces two related **AI features** plus shared AI plumbing: 1. **Per‑group "Status Update" feature** - New `components/board/status-update-dialog.tsx` — a dialog that generates a group status update, supports a follow‑up chat list (`ChatMessageList`, `MarkdownPreview`), copy‑to‑clipboard, and calls `converseAboutGroupStatus(group.id, next)`. - `components/board/group-card.tsx` (modified) — imports `ClipboardList` + `StatusUpdateDialog`, adds `statusUpdateOpen` state, and a **"Status Update"** dropdown item gated on `aiConfigured`. - New `lib/actions/group-status-ai.ts` (`"use server"`) — backing server action(s) incl. `converseAboutGroupStatus`. 2. **"Global Chat" feature** - New `lib/actions/global-chat.ts` (`"use server"`) — `sendChatMessage(messages: ChatMessage[])`: free‑form Q&A across every board the user owns (Home + all Projects). Re‑fetches/re‑serializes context each turn; uses `requireUserId()`, `getAllGroupsForUser()`, `formatAllGroupsForPrompt()`, and a shared `callChatCompletion` helper. Includes a `SYSTEM_PROMPT_HEADER` instructing the model to answer only from provided context. 3. **Shared AI context/formatting layer** - New `lib/ai/context.ts` (`server-only`) — `formatAllGroupsForPrompt`, truncation with hard caps: `NOTE_CHAR_CAP=4,000`, `DETAILS_CHAR_CAP=300`, `TOTAL_CHAR_CAP=60,000`. Comment notes single‑user scale (no RAG; whole dataset goes into the prompt, capped by these limits). - New `types/ai.ts` — `AiGroupContext` interface (group + scope label + category + archived flag + todos). **Deliberately includes archived groups** so historical questions are answerable. - `lib/board.ts` (modified) — new `getAllGroupsForUser(userId)` using one Prisma query with `categoryAccessFilter`, flattening all categories/groups/todos (unlike `getBoard`, it does **not** filter archived groups). ## Key Technical Details - Stack: Next.js, React, Tailwind, shadcn‑style UI (Dialog/Button), `MarkdownPreview`/`ChatMessageList`. - `"use server"` server actions; `"server-only"` context module; Prisma ORM; `requireUserId()` auth; `categoryAccessFilter` access control. - A shared `callChatCompletion` helper is referenced (handles provider errors, 401/403 API‑key rejection, empty/invalid JSON responses) — its full definition is not cleanly visible in the pasted diff. - The pasted diff/file contents contain visible corruption (broken lines, garbled characters), but the feature intent is clearly discernible. ## User Request (deliverable) Produce **only** a concise, descriptive git commit message (no file edits). The expected message should summarize: adding per‑group AI "Status Update" + a global cross‑board "Chat", along with shared AI context formatting (`lib/ai/context.ts`, `types/ai.ts`, `getAllGroupsForUser`) and new server actions (`global-chat.ts`, `group-status-ai.ts`). A representative phrasing would be something like: *"Add AI status updates per group and a global chat across all boards, backed by shared AI context formatting."* ## Current State / Next Steps - No assistant response exists yet. - Next step is to emit the commit message as plain text output (no code/file changes). --- app/(app)/chat/page.tsx | 18 +++ components/ai/chat-thread.tsx | 138 ++++++++++++++++ components/board/group-card.tsx | 11 ++ components/board/notes-dialog.tsx | 11 +- components/board/status-update-dialog.tsx | 184 ++++++++++++++++++++++ components/chat/chat-page-client.tsx | 86 ++++++++++ components/nav/nav-items.ts | 7 +- lib/actions/chat-ai.ts | 40 +++++ lib/actions/group-status-ai.ts | 70 ++++++++ lib/ai/chat-completion.ts | 91 +++++++++++ lib/ai/context.ts | 90 +++++++++++ lib/board.ts | 42 +++++ types/ai.ts | 23 +++ 13 files changed, 808 insertions(+), 3 deletions(-) create mode 100644 app/(app)/chat/page.tsx create mode 100644 components/ai/chat-thread.tsx create mode 100644 components/board/status-update-dialog.tsx create mode 100644 components/chat/chat-page-client.tsx create mode 100644 lib/actions/chat-ai.ts create mode 100644 lib/actions/group-status-ai.ts create mode 100644 lib/ai/chat-completion.ts create mode 100644 lib/ai/context.ts create mode 100644 types/ai.ts diff --git a/app/(app)/chat/page.tsx b/app/(app)/chat/page.tsx new file mode 100644 index 0000000..e9035db --- /dev/null +++ b/app/(app)/chat/page.tsx @@ -0,0 +1,18 @@ +import { redirect } from "next/navigation"; + +import { auth } from "@/auth"; +import { getAiSettingsView } from "@/lib/ai-settings"; +import { ChatPageClient } from "@/components/chat/chat-page-client"; + +export default async function ChatPage() { + const session = await auth(); + if (!session?.user) redirect("/login"); + + const aiSettings = await getAiSettingsView(); + const aiConfigured = !!(aiSettings.apiUrl && aiSettings.model); + + // No board data fetched here -- sendChatMessage (lib/actions/chat-ai.ts) + // gathers and re-serializes the user's full context itself on every + // turn, so this page only needs the aiConfigured gate. + return ; +} diff --git a/components/ai/chat-thread.tsx b/components/ai/chat-thread.tsx new file mode 100644 index 0000000..2b4bf94 --- /dev/null +++ b/components/ai/chat-thread.tsx @@ -0,0 +1,138 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Mic, Send, Square } from "lucide-react"; +import { MarkdownPreview } from "@/components/markdown/markdown-widgets"; + +export interface ChatBubbleMessage { + role: "user" | "assistant"; + content: string; +} + +/** + * Shared message-bubble list for the two plain-text chat surfaces (the + * global Chat page and the group Status Update follow-up Q&A) -- both + * just render a back-and-forth conversation with no ask/finalize review + * step, unlike GroupAiDialog/TodoAiDialog (which inline their own nearly + * identical list because they also render an editable "pending" state + * this shared component doesn't need to know about). + */ +export function ChatMessageList({ + messages, + sending, + error, + emptyHint, + colorMode, +}: { + messages: ChatBubbleMessage[]; + sending: boolean; + error: string | null; + emptyHint: string; + colorMode?: "light" | "dark"; +}) { + return ( +
+ {messages.length === 0 ? ( +

+ {emptyHint} +

+ ) : ( + messages.map((message, index) => ( +
+ {message.role === "assistant" ? ( + + ) : ( + message.content + )} +
+ )) + )} + {sending && ( +
+ Thinking… +
+ )} + {error &&

{error}

} +
+ ); +} + +/** + * Shared textarea + mic + send composer, matching the input row already + * used by GroupAiDialog/TodoAiDialog (voice input via + * useSpeechRecognition, Enter-to-send, Shift+Enter for a newline). + */ +export function ChatComposer({ + value, + onChange, + onSend, + sending, + listening, + micSupported, + onStartListening, + onStopListening, + placeholder, +}: { + value: string; + onChange: (value: string) => void; + onSend: () => void; + sending: boolean; + listening: boolean; + micSupported: boolean; + onStartListening: () => void; + onStopListening: () => void; + placeholder?: string; +}) { + return ( +
+