From c3f4aebe46e1b7019d25053d908e76eee6e10d7b Mon Sep 17 00:00:00 2001 From: ElasticBottle Date: Mon, 23 Feb 2026 19:45:59 +0800 Subject: [PATCH 1/3] chore: fix workflow generations --- apps/seo/src/routeTree.gen.ts | 21 ++ apps/seo/src/routes/_authed/admin/route.tsx | 161 ++++++++++++ .../api-seo/src/lib/ai/arktype-json-schema.ts | 60 +++++ .../ai/tools/google-search-console-tool.ts | 7 +- .../src/lib/ai/tools/settings-tools.ts | 9 +- packages/api-seo/src/routes/admin.ts | 110 ++++++++ packages/api-seo/src/routes/index.ts | 1 + .../src/workflows/onboarding-workflow.ts | 20 +- .../api-seo/src/workflows/planner-workflow.ts | 5 +- .../strategy-phase-generation-workflow.ts | 3 +- .../strategy-suggestions-workflow.ts | 236 ++++++++++-------- .../api-seo/src/workflows/writer-workflow.ts | 17 +- .../schema/arktype-json-schema-transformer.ts | 38 +++ packages/core/src/schemas/strategy-parsers.ts | 2 +- 14 files changed, 554 insertions(+), 136 deletions(-) create mode 100644 apps/seo/src/routes/_authed/admin/route.tsx create mode 100644 packages/api-seo/src/lib/ai/arktype-json-schema.ts create mode 100644 packages/api-seo/src/routes/admin.ts create mode 100644 packages/core/src/schema/arktype-json-schema-transformer.ts diff --git a/apps/seo/src/routeTree.gen.ts b/apps/seo/src/routeTree.gen.ts index 8887313d1..9a28944a5 100644 --- a/apps/seo/src/routeTree.gen.ts +++ b/apps/seo/src/routeTree.gen.ts @@ -13,6 +13,7 @@ import { Route as LoginRouteImport } from './routes/login' import { Route as AuthedRouteRouteImport } from './routes/_authed/route' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiSplatRouteImport } from './routes/api/$' +import { Route as AuthedAdminRouteRouteImport } from './routes/_authed/admin/route' import { Route as AuthedOrganizationSlugRouteRouteImport } from './routes/_authed/$organizationSlug/route' import { Route as AuthedOnboardingIndexRouteImport } from './routes/_authed/onboarding/index' import { Route as AuthedOrganizationSlugIndexRouteImport } from './routes/_authed/$organizationSlug/index' @@ -54,6 +55,11 @@ const ApiSplatRoute = ApiSplatRouteImport.update({ path: '/api/$', getParentRoute: () => rootRouteImport, } as any) +const AuthedAdminRouteRoute = AuthedAdminRouteRouteImport.update({ + id: '/admin', + path: '/admin', + getParentRoute: () => AuthedRouteRoute, +} as any) const AuthedOrganizationSlugRouteRoute = AuthedOrganizationSlugRouteRouteImport.update({ id: '/$organizationSlug', @@ -187,6 +193,7 @@ export interface FileRoutesByFullPath { '/': typeof IndexRoute '/login': typeof LoginRoute '/$organizationSlug': typeof AuthedOrganizationSlugRouteRouteWithChildren + '/admin': typeof AuthedAdminRouteRoute '/api/$': typeof ApiSplatRoute '/$organizationSlug/$projectSlug': typeof AuthedOrganizationSlugProjectSlugRouteRouteWithChildren '/invite/$invitationId': typeof AuthedInviteInvitationIdRoute @@ -212,6 +219,7 @@ export interface FileRoutesByFullPath { export interface FileRoutesByTo { '/': typeof IndexRoute '/login': typeof LoginRoute + '/admin': typeof AuthedAdminRouteRoute '/api/$': typeof ApiSplatRoute '/invite/$invitationId': typeof AuthedInviteInvitationIdRoute '/api/rpc/$': typeof ApiRpcSplatRoute @@ -238,6 +246,7 @@ export interface FileRoutesById { '/_authed': typeof AuthedRouteRouteWithChildren '/login': typeof LoginRoute '/_authed/$organizationSlug': typeof AuthedOrganizationSlugRouteRouteWithChildren + '/_authed/admin': typeof AuthedAdminRouteRoute '/api/$': typeof ApiSplatRoute '/_authed/$organizationSlug/$projectSlug': typeof AuthedOrganizationSlugProjectSlugRouteRouteWithChildren '/_authed/invite/$invitationId': typeof AuthedInviteInvitationIdRoute @@ -266,6 +275,7 @@ export interface FileRouteTypes { | '/' | '/login' | '/$organizationSlug' + | '/admin' | '/api/$' | '/$organizationSlug/$projectSlug' | '/invite/$invitationId' @@ -291,6 +301,7 @@ export interface FileRouteTypes { to: | '/' | '/login' + | '/admin' | '/api/$' | '/invite/$invitationId' | '/api/rpc/$' @@ -316,6 +327,7 @@ export interface FileRouteTypes { | '/_authed' | '/login' | '/_authed/$organizationSlug' + | '/_authed/admin' | '/api/$' | '/_authed/$organizationSlug/$projectSlug' | '/_authed/invite/$invitationId' @@ -377,6 +389,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ApiSplatRouteImport parentRoute: typeof rootRouteImport } + '/_authed/admin': { + id: '/_authed/admin' + path: '/admin' + fullPath: '/admin' + preLoaderRoute: typeof AuthedAdminRouteRouteImport + parentRoute: typeof AuthedRouteRoute + } '/_authed/$organizationSlug': { id: '/_authed/$organizationSlug' path: '/$organizationSlug' @@ -615,6 +634,7 @@ const AuthedOrganizationSlugRouteRouteWithChildren = interface AuthedRouteRouteChildren { AuthedOrganizationSlugRouteRoute: typeof AuthedOrganizationSlugRouteRouteWithChildren + AuthedAdminRouteRoute: typeof AuthedAdminRouteRoute AuthedInviteInvitationIdRoute: typeof AuthedInviteInvitationIdRoute AuthedOnboardingIndexRoute: typeof AuthedOnboardingIndexRoute } @@ -622,6 +642,7 @@ interface AuthedRouteRouteChildren { const AuthedRouteRouteChildren: AuthedRouteRouteChildren = { AuthedOrganizationSlugRouteRoute: AuthedOrganizationSlugRouteRouteWithChildren, + AuthedAdminRouteRoute: AuthedAdminRouteRoute, AuthedInviteInvitationIdRoute: AuthedInviteInvitationIdRoute, AuthedOnboardingIndexRoute: AuthedOnboardingIndexRoute, } diff --git a/apps/seo/src/routes/_authed/admin/route.tsx b/apps/seo/src/routes/_authed/admin/route.tsx new file mode 100644 index 000000000..01872c82b --- /dev/null +++ b/apps/seo/src/routes/_authed/admin/route.tsx @@ -0,0 +1,161 @@ +import { Button } from "@rectangular-labs/ui/components/ui/button"; +import { Input } from "@rectangular-labs/ui/components/ui/input"; +import { Label } from "@rectangular-labs/ui/components/ui/label"; +import { toast } from "@rectangular-labs/ui/components/ui/sonner"; +import { Textarea } from "@rectangular-labs/ui/components/ui/textarea"; +import { useMutation } from "@tanstack/react-query"; +import { createFileRoute, notFound } from "@tanstack/react-router"; +import { useState } from "react"; +import { getApiClientRq } from "~/lib/api"; + +export const Route = createFileRoute("/_authed/admin")({ + beforeLoad: ({ context }) => { + if (!context.user?.email?.endsWith("fluidposts.com")) { + throw notFound(); + } + }, + component: RouteComponent, +}); + +function RouteComponent() { + const api = getApiClientRq(); + const [projectSlug, setProjectSlug] = useState(""); + + const { mutate: triggerOnboarding, isPending } = useMutation( + api.admin.triggerOnboardingTask.mutationOptions({ + onSuccess: () => { + toast.success("Triggered onboarding workflow successfully."); + setProjectSlug(""); + }, + onError: (error) => { + toast.error(error.message); + }, + }), + ); + + const [instructions, setInstructions] = useState(""); + + const { mutate: triggerStrategySuggestions, isPending: isPendingStrategy } = + useMutation( + api.admin.triggerStrategySuggestionsTask.mutationOptions({ + onSuccess: () => { + toast.success( + "Triggered strategy suggestions workflow successfully.", + ); + setProjectSlug(""); + setInstructions(""); + }, + onError: (error) => { + toast.error(error.message); + }, + }), + ); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!projectSlug.trim()) { + toast.error("Please enter a project slug"); + return; + } + triggerOnboarding({ projectSlug }); + }; + + const handleStrategySubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!projectSlug.trim()) { + toast.error("Please enter a project slug"); + return; + } + triggerStrategySuggestions({ + projectSlug, + instructions, + }); + }; + + return ( +
+
+

Admin

+

+ Internal tools for the fluidposts team. +

+
+ +
+
+

+ Trigger Onboarding Task +

+

+ Trigger the onboarding workflow in the api-seo package for a + specific project. +

+
+
+
+
+ + setProjectSlug(e.target.value)} + placeholder="e.g. acme-corp" + value={projectSlug} + /> +
+ +
+
+
+ +
+
+

+ Trigger Strategy Suggestions Task +

+

+ Trigger the strategy suggestions workflow in the api-seo package for + a specific project. +

+
+
+
+
+ + setProjectSlug(e.target.value)} + placeholder="e.g. acme-corp" + value={projectSlug} + /> +
+
+ +