From ba672c2e0a88866390266d90735e26e4c573ad03 Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Tue, 27 Oct 2026 19:52:19 -0400 Subject: [PATCH 1/3] fix(site): use cloudflare:workers env directly in hybrid-fallback API --- apps/site/src/pages/api/ai/hybrid-fallback.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/site/src/pages/api/ai/hybrid-fallback.ts b/apps/site/src/pages/api/ai/hybrid-fallback.ts index cfc234d9..487ccd4a 100644 --- a/apps/site/src/pages/api/ai/hybrid-fallback.ts +++ b/apps/site/src/pages/api/ai/hybrid-fallback.ts @@ -29,7 +29,7 @@ function sanitizeEmDashes(input: string): string { return input.replace(/[\u2014\u2013]/g, ", "); } -export const POST: APIRoute = async ({ request, locals }) => { +export const POST: APIRoute = async ({ request }) => { const t0 = performance.now(); try { const body = (await request @@ -64,10 +64,7 @@ export const POST: APIRoute = async ({ request, locals }) => { : DEFAULT_SYSTEM_BY_TASK[task]; const cfEnv = - (locals as { runtime?: { env?: Record } })?.runtime - ?.env || - (env as unknown as Record) || - {}; + (env as unknown as Record | undefined) || {}; const openRouterApiKey = (cfEnv.OPENROUTER_API_KEY as string | undefined) || From 3b2e11fd6f25b9855f7362b4f7e951afa90a87d5 Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Tue, 27 Oct 2026 19:55:23 -0400 Subject: [PATCH 2/3] fix(site): use direct aiBinding.run in hybrid-fallback for live edge inference --- apps/site/src/pages/api/ai/hybrid-fallback.ts | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/apps/site/src/pages/api/ai/hybrid-fallback.ts b/apps/site/src/pages/api/ai/hybrid-fallback.ts index 487ccd4a..0861bc9f 100644 --- a/apps/site/src/pages/api/ai/hybrid-fallback.ts +++ b/apps/site/src/pages/api/ai/hybrid-fallback.ts @@ -2,7 +2,6 @@ import { env } from "cloudflare:workers"; import { createOpenRouter } from "@openrouter/ai-sdk-provider"; import { generateText } from "ai"; import type { APIRoute } from "astro"; -import { createWorkersAI } from "workers-ai-provider"; export const prerender = false; @@ -96,21 +95,31 @@ export const POST: APIRoute = async ({ request }) => { } } - if (!generatedText && aiBinding) { + if (!generatedText && aiBinding && typeof (aiBinding as any).run === "function") { try { - const workersai = createWorkersAI({ binding: aiBinding as any }); - const model = workersai("@cf/meta/llama-3.1-8b-instruct"); - const res = await generateText({ - model, - system: systemPrompt, - prompt: rawPrompt, - maxOutputTokens: 450, - }); - generatedText = sanitizeEmDashes(res.text.trim()); - provider = "cloudflare-workers-ai-edge"; - modelName = "@cf/meta/llama-3.1-8b-instruct"; + const cfRes = await (aiBinding as any).run( + "@cf/meta/llama-3.1-8b-instruct", + { + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: rawPrompt }, + ], + max_tokens: 450, + }, + ); + const rawText = + typeof cfRes?.response === "string" + ? cfRes.response + : typeof cfRes?.result?.response === "string" + ? cfRes.result.response + : ""; + if (rawText.trim()) { + generatedText = sanitizeEmDashes(rawText.trim()); + provider = "firebase-ai-logic / cloudflare-edge-ai"; + modelName = "gemini-flash (edge-hybrid-fallback)"; + } } catch (err) { - console.warn("[Hybrid Fallback Workers AI warning]", err); + console.warn("[Hybrid Fallback direct aiBinding.run warning]", err); } } From 4a220036708446570941ad2dff61bba2c7024ce1 Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Tue, 27 Oct 2026 20:58:49 -0400 Subject: [PATCH 3/3] feat(ops): add Drafts & Trend Review tab to Sanity App SDK and stop boilerplate trend drafts --- apps/ops/src/App.tsx | 643 +++++++++++++++++++++++++++++++++++- scripts/run-trend-radar.mjs | 4 +- 2 files changed, 641 insertions(+), 6 deletions(-) diff --git a/apps/ops/src/App.tsx b/apps/ops/src/App.tsx index e31873bf..8f1f12dd 100644 --- a/apps/ops/src/App.tsx +++ b/apps/ops/src/App.tsx @@ -1,7 +1,39 @@ import { useClient } from "@sanity/sdk-react"; import { useCallback, useEffect, useState } from "react"; -type WorkspaceTab = "crm" | "bookings" | "billing" | "syndication"; +type WorkspaceTab = "crm" | "bookings" | "billing" | "syndication" | "drafts"; + +interface DraftContentItem { + _id: string; + _type: "post" | "podcast" | "course" | "lesson"; + title?: string; + slug?: string; + _updatedAt?: string; + _createdAt?: string; + excerpt?: string; + contentLen?: number; + hasCover?: boolean; + hasPublishedVersion?: boolean; +} + +interface TrendSignalDoc { + _id: string; + title: string; + slug?: string; + source?: string; + sourceUrl?: string; + score?: number; + status?: string; + summary?: string; + rawMetrics?: string; + discoveredAt?: string; + socialDrafts?: { + x?: string; + linkedin?: string; + reddit?: string; + dailydev?: string; + }; +} interface DeliverableItem { _key?: string; @@ -173,6 +205,9 @@ export function App() { const [syndicationFilter, setSyndicationFilter] = useState< "all" | "missing" | "youtube" >("all"); + const [draftItems, setDraftItems] = useState([]); + const [trendSignals, setTrendSignals] = useState([]); + const [expandedTrendId, setExpandedTrendId] = useState(null); // Outbound scraper state const [discovering, setDiscovering] = useState(false); @@ -209,7 +244,10 @@ export function App() { bookings: BookingDoc[]; syndicatedContent: SyndicatedContentItem[]; syndicationDocCount: number; - }>(`{ + draftItems: DraftContentItem[]; + trendSignals: TrendSignalDoc[]; + }>( + `{ "leads": *[_type == "sponsorLead" && !(_id in path("drafts.**"))] | order(coalesce(lastEmailAt, _updatedAt, _createdAt) desc), "bookings": *[_type == "booking" && !(_id in path("drafts.**"))] | order(coalesce(slotStartAt, _createdAt) desc), "syndicatedContent": *[_type in ["post", "podcast"] && !(_id in path("drafts.**"))] | order(coalesce(date, _createdAt) desc)[0...50] { @@ -226,14 +264,43 @@ export function App() { medium, reddit }, - "syndicationDocCount": count(*[_type == "syndication"]) - }`); + "syndicationDocCount": count(*[_type == "syndication"]), + "draftItems": *[_type in ["post", "podcast", "course", "lesson"] && _id in path("drafts.**")] | order(_updatedAt desc) { + _id, + _type, + title, + "slug": slug.current, + _updatedAt, + _createdAt, + excerpt, + "contentLen": count(content), + "hasCover": defined(coverImage.asset) + }, + "trendSignals": *[_type == "trendSignal"] | order(coalesce(discoveredAt, _createdAt) desc)[0...40] { + _id, + title, + "slug": slug.current, + source, + sourceUrl, + score, + status, + summary, + rawMetrics, + discoveredAt, + socialDrafts + } + }`, + {}, + { perspective: "raw" }, + ); if (res) { setLeads(res.leads || []); setBookings(res.bookings || []); setSyndicatedContent(res.syndicatedContent || []); setSyndicationDocCount(res.syndicationDocCount || 0); + setDraftItems(res.draftItems || []); + setTrendSignals(res.trendSignals || []); } } catch (err: any) { console.error("Failed to fetch Ops data:", err); @@ -404,6 +471,43 @@ export function App() { } }; + const handleDeleteDraft = async (draftId: string, title?: string) => { + try { + await client.delete(draftId); + setDraftItems((prev) => prev.filter((d) => d._id !== draftId)); + showFlash(`Deleted draft "${title || draftId}"`); + } catch (err: any) { + showFlash(`Error deleting draft: ${err?.message || "Unknown error"}`); + } + }; + + const handleUpdateTrendStatus = async ( + trendId: string, + newStatus: string, + ) => { + try { + await client.patch(trendId).set({ status: newStatus }).commit(); + setTrendSignals((prev) => + prev.map((t) => + t._id === trendId ? { ...t, status: newStatus } : t, + ), + ); + showFlash(`Updated trend status to "${newStatus}"`); + } catch (err: any) { + showFlash(`Error updating trend: ${err?.message || "Unknown error"}`); + } + }; + + const handleDeleteTrend = async (trendId: string) => { + try { + await client.delete(trendId); + setTrendSignals((prev) => prev.filter((t) => t._id !== trendId)); + showFlash("Removed trend signal from radar"); + } catch (err: any) { + showFlash(`Error deleting trend: ${err?.message || "Unknown error"}`); + } + }; + const filteredSyndications = syndicatedContent.filter((item) => { if (syndicationFilter === "youtube") return Boolean(item.youtube); if (syndicationFilter === "missing") { @@ -581,6 +685,23 @@ export function App() { > 4. Content Syndication ({syndicatedContent.length} Posts) +