Hybrid content automation pilot: generation + review + drafts live in the panel (reusing the insight pipeline's DeepSeek client, prompt_templates versioning, cost_ledger and budget_settings); publishing/distribution will go through n8n (Faz C, not built). Channels: blog, LinkedIn, X, Instagram. Topic sourcing is automatic (LLM-generated ideas). Approval model: drafts sit in the panel for manual review/edit/publish. Faz A (worker): - ContentTopic / ContentDraft Prisma models (content_topics, content_drafts) - content-prompts.ts: 5 seed prompts (topic ideas[pro] + blog[pro] + linkedin/x/instagram[flash]), Turkish B2B automotive tone, per-channel JSON schemas - content-budget.ts: separate budget envelope (sums only content_* spend) - content-topics job (auto idea gen, backlog-capped, title dedupe) + content-generate job (queued topic -> one draft per channel) - content-pipeline scheduler (separate BullMQ queue, topics@*/8h, generate@*/10min), wired into index.ts; seeded via seed-runtime - content budget settings (caps + content_paused kill switch); seed default content_paused=true for a safe first deploy Faz B (web): - /content (queue + auto/manual triggers + manual topic form), /content/t/[id] (per-channel draft cards: preview, JSON edits, approve/reject), /content/costs (content-only spend) - server actions (audit-logged), manual trigger API routes, contentQueue(), nav + Cmd+K entries - content caps surfaced on /insights/settings/budgets + whitelisted Both packages typecheck. Schema applies on deploy (web start runs prisma db push). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
140 lines
4.8 KiB
TypeScript
140 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import {
|
|
CommandDialog,
|
|
CommandEmpty,
|
|
CommandGroup,
|
|
CommandInput,
|
|
CommandItem,
|
|
CommandList,
|
|
CommandSeparator,
|
|
} from "@/components/ui/command";
|
|
import {
|
|
ActivityIcon,
|
|
CoinsIcon,
|
|
FolderIcon,
|
|
LayoutDashboardIcon,
|
|
LightbulbIcon,
|
|
LogOutIcon,
|
|
PenLineIcon,
|
|
ScrollTextIcon,
|
|
Settings2Icon,
|
|
SlidersHorizontalIcon,
|
|
SparklesIcon,
|
|
TerminalIcon,
|
|
} from "lucide-react";
|
|
import { signOut } from "@/lib/auth-client";
|
|
|
|
type ProjectLite = { key: string; name: string; status: string };
|
|
|
|
export function CommandPalette({ projects }: { projects: ProjectLite[] }) {
|
|
const router = useRouter();
|
|
const [open, setOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const onKey = (e: KeyboardEvent) => {
|
|
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
e.preventDefault();
|
|
setOpen((v) => !v);
|
|
}
|
|
};
|
|
window.addEventListener("keydown", onKey);
|
|
return () => window.removeEventListener("keydown", onKey);
|
|
}, []);
|
|
|
|
const go = (path: string) => {
|
|
setOpen(false);
|
|
router.push(path);
|
|
};
|
|
|
|
return (
|
|
<CommandDialog open={open} onOpenChange={setOpen} title="Command palette" description="Quick navigation">
|
|
<CommandInput placeholder="Jump to project, page, or action…" />
|
|
<CommandList>
|
|
<CommandEmpty>No match.</CommandEmpty>
|
|
|
|
<CommandGroup heading="Navigation">
|
|
<CommandItem onSelect={() => go("/")}><LayoutDashboardIcon /> Overview</CommandItem>
|
|
<CommandItem onSelect={() => go("/projects")}><FolderIcon /> Projects</CommandItem>
|
|
<CommandItem onSelect={() => go("/operations")}><TerminalIcon /> Operations</CommandItem>
|
|
<CommandItem onSelect={() => go("/events")}><ActivityIcon /> Events</CommandItem>
|
|
<CommandItem onSelect={() => go("/audit")}><ScrollTextIcon /> Audit</CommandItem>
|
|
<CommandItem onSelect={() => go("/settings")}><Settings2Icon /> Settings</CommandItem>
|
|
</CommandGroup>
|
|
|
|
<CommandSeparator />
|
|
|
|
<CommandGroup heading="Insights">
|
|
<CommandItem keywords={["inbox","triage"]} onSelect={() => go("/insights")}>
|
|
<LightbulbIcon /> Insight inbox
|
|
</CommandItem>
|
|
<CommandItem keywords={["brief","daily","summary"]} onSelect={() => go("/insights/brief")}>
|
|
<LightbulbIcon /> Daily brief
|
|
</CommandItem>
|
|
<CommandItem keywords={["eval","test","quality"]} onSelect={() => go("/insights/settings/eval-sets")}>
|
|
<SparklesIcon /> Eval sets
|
|
</CommandItem>
|
|
<CommandItem keywords={["cost","spend","budget","llm"]} onSelect={() => go("/insights/costs")}>
|
|
<CoinsIcon /> Cost dashboard
|
|
</CommandItem>
|
|
<CommandItem keywords={["pipeline","sessions","posthog"]} onSelect={() => go("/insights/pipeline")}>
|
|
<ActivityIcon /> Pipeline (sessions)
|
|
</CommandItem>
|
|
<CommandItem keywords={["pattern","cluster","group"]} onSelect={() => go("/insights/patterns")}>
|
|
<SparklesIcon /> Patterns (clusters)
|
|
</CommandItem>
|
|
<CommandItem keywords={["budget","cap","pause"]} onSelect={() => go("/insights/settings/budgets")}>
|
|
<SlidersHorizontalIcon /> Budget settings
|
|
</CommandItem>
|
|
<CommandItem keywords={["prompt","template","llm"]} onSelect={() => go("/insights/settings/prompts")}>
|
|
<SparklesIcon /> Prompt registry
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
|
|
<CommandSeparator />
|
|
|
|
<CommandGroup heading="Content">
|
|
<CommandItem keywords={["content","icerik","konu","kuyruk","queue"]} onSelect={() => go("/content")}>
|
|
<PenLineIcon /> İçerik kuyruğu
|
|
</CommandItem>
|
|
<CommandItem keywords={["content","cost","maliyet","spend"]} onSelect={() => go("/content/costs")}>
|
|
<CoinsIcon /> İçerik maliyeti
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
|
|
<CommandSeparator />
|
|
|
|
<CommandGroup heading="Projects">
|
|
{projects.map((p) => (
|
|
<CommandItem
|
|
key={p.key}
|
|
keywords={[p.key, p.name, p.status]}
|
|
onSelect={() => go(`/projects/${p.key}`)}
|
|
>
|
|
<FolderIcon />
|
|
<span>{p.name}</span>
|
|
<span className="ml-auto text-xs text-muted-foreground">{p.status}</span>
|
|
</CommandItem>
|
|
))}
|
|
</CommandGroup>
|
|
|
|
<CommandSeparator />
|
|
|
|
<CommandGroup heading="Account">
|
|
<CommandItem
|
|
onSelect={async () => {
|
|
setOpen(false);
|
|
await signOut();
|
|
router.replace("/login");
|
|
}}
|
|
>
|
|
<LogOutIcon /> Sign out
|
|
</CommandItem>
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</CommandDialog>
|
|
);
|
|
}
|