chore: remove files importing pruned packages (ai, cms, cognitive-context)
Step 3 pruned packages/{ai,cms,cognitive-context} but left whole
route groups + feature modules that depended on them. Those files
were unbuildable since that prune. Removes them now so the workspace
can be validated:
Route groups:
- apps/web/src/app/[locale]/(apps)/{chat,image,pdf,tts}/
- apps/web/src/app/[locale]/(marketing)/blog/
Feature modules:
- apps/web/src/modules/{chat,image,pdf,tts,common/ai,marketing/blog}/
- packages/api/src/modules/ai/ (chat, image, pdf, stt, tts, router)
3 stragglers remain (separate handoff to claudemesh-2):
- apps/web/src/app/[locale]/(marketing)/legal/[slug]/page.tsx (cms)
- apps/web/src/app/sitemap.ts (cms)
- apps/web/src/modules/common/layout/credits/index.tsx (ai)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
import { Hono } from "hono";
|
||||
|
||||
import {
|
||||
getUserChats,
|
||||
deleteChat,
|
||||
getChat,
|
||||
streamChat,
|
||||
getChatMessagesWithAttachments,
|
||||
} from "@turbostarter/ai/chat/api";
|
||||
import { chatMessageSchema } from "@turbostarter/ai/chat/schema";
|
||||
import { getCreditsDeduction } from "@turbostarter/ai/chat/utils";
|
||||
|
||||
import { deductCredits, enforceAuth, rateLimiter, validate } from "../../middleware";
|
||||
|
||||
import type { User } from "@turbostarter/auth";
|
||||
|
||||
const chatsRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>()
|
||||
.post(
|
||||
"/",
|
||||
enforceAuth,
|
||||
rateLimiter,
|
||||
validate("json", chatMessageSchema),
|
||||
async (c) => {
|
||||
const input = c.req.valid("json");
|
||||
const creditsAmount = getCreditsDeduction(input.metadata.options, input.parts);
|
||||
|
||||
// Deduct credits
|
||||
await deductCredits(creditsAmount, "chat")(c, async () => { /* noop */ });
|
||||
|
||||
return streamChat({
|
||||
...input,
|
||||
signal: c.req.raw.signal,
|
||||
userId: c.var.user.id,
|
||||
});
|
||||
},
|
||||
)
|
||||
.get("/", enforceAuth, async (c) => c.json(await getUserChats(c.var.user.id)))
|
||||
.delete("/:id", enforceAuth, async (c) => c.json(await deleteChat(c.req.param("id"))))
|
||||
.get("/:id", enforceAuth, async (c) => c.json((await getChat(c.req.param("id"))) ?? null))
|
||||
.get("/:id/messages", enforceAuth, async (c) =>
|
||||
c.json(await getChatMessagesWithAttachments(c.req.param("id"))),
|
||||
);
|
||||
|
||||
export const chatRouter = new Hono().route("/chats", chatsRouter);
|
||||
@@ -1,89 +0,0 @@
|
||||
import { Hono } from "hono";
|
||||
import * as z from "zod";
|
||||
|
||||
import { Credits } from "@turbostarter/ai/credits/utils";
|
||||
import {
|
||||
createGeneration,
|
||||
generateImages,
|
||||
getGeneration,
|
||||
getGenerationImages,
|
||||
getImages,
|
||||
} from "@turbostarter/ai/image/api";
|
||||
import { imageGenerationSchema } from "@turbostarter/ai/image/schema";
|
||||
|
||||
import { deductCredits, enforceAuth, rateLimiter, validate } from "../../middleware";
|
||||
import { withTimeout } from "../../utils";
|
||||
|
||||
import type { User } from "@turbostarter/auth";
|
||||
|
||||
const generationsRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>()
|
||||
.post(
|
||||
"/",
|
||||
enforceAuth,
|
||||
validate("json", imageGenerationSchema),
|
||||
async (c) => {
|
||||
const input = c.req.valid("json");
|
||||
const creditsAmount = input.options.count * Credits.COST.DEFAULT;
|
||||
|
||||
// Deduct credits
|
||||
await deductCredits(creditsAmount, "image-generation")(c, async () => { /* noop */ });
|
||||
|
||||
return c.json(
|
||||
await createGeneration({
|
||||
userId: c.var.user.id,
|
||||
...input,
|
||||
...input.options,
|
||||
}),
|
||||
);
|
||||
},
|
||||
)
|
||||
.post("/:id/images", enforceAuth, rateLimiter, async (c) =>
|
||||
c.json(
|
||||
await withTimeout(
|
||||
generateImages({
|
||||
id: c.req.param("id"),
|
||||
abortSignal: c.req.raw.signal,
|
||||
}),
|
||||
55 * 1000,
|
||||
),
|
||||
),
|
||||
)
|
||||
.get("/:id", enforceAuth, async (c) =>
|
||||
c.json((await getGeneration(c.req.param("id"))) ?? null),
|
||||
)
|
||||
.get("/:id/images", enforceAuth, async (c) =>
|
||||
c.json(await getGenerationImages(c.req.param("id"))),
|
||||
);
|
||||
|
||||
const imagesRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>().get(
|
||||
"/",
|
||||
enforceAuth,
|
||||
validate(
|
||||
"query",
|
||||
z
|
||||
.object({
|
||||
limit: z.number().optional(),
|
||||
cursor: z.coerce.date().optional(),
|
||||
})
|
||||
.optional(),
|
||||
),
|
||||
async (c) =>
|
||||
c.json(
|
||||
await getImages({
|
||||
userId: c.var.user.id,
|
||||
...c.req.valid("query"),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
export const imageRouter = new Hono()
|
||||
.route("/generations", generationsRouter)
|
||||
.route("/images", imagesRouter);
|
||||
@@ -1,254 +0,0 @@
|
||||
import { Hono } from "hono";
|
||||
import * as z from "zod";
|
||||
|
||||
import { Credits } from "@turbostarter/ai/credits/utils";
|
||||
import {
|
||||
createChat,
|
||||
deleteChat,
|
||||
getChat,
|
||||
getChatDocuments,
|
||||
getChatMessages,
|
||||
getDocument,
|
||||
getUserChats,
|
||||
streamChatWithDocuments,
|
||||
} from "@turbostarter/ai/pdf/api";
|
||||
import { pdfMessageSchema } from "@turbostarter/ai/pdf/schema";
|
||||
import {
|
||||
searchWithCitations,
|
||||
getCitationUnitsForChunk,
|
||||
getCitationUnitById,
|
||||
} from "@turbostarter/ai/pdf/search";
|
||||
import {
|
||||
insertPdfChatSchema,
|
||||
insertPdfDocumentSchema,
|
||||
} from "@turbostarter/db/schema/pdf";
|
||||
|
||||
import { deductCredits, enforceAuth, rateLimiter, validate } from "../../middleware";
|
||||
|
||||
import type { User } from "@turbostarter/auth";
|
||||
|
||||
const createChatSchema = z.object({
|
||||
...insertPdfChatSchema.omit({ userId: true }).shape,
|
||||
...insertPdfDocumentSchema.omit({ chatId: true }).shape,
|
||||
});
|
||||
|
||||
type _CreateChatInput = z.infer<typeof createChatSchema>;
|
||||
|
||||
// ============================================================================
|
||||
// Search Schemas
|
||||
// ============================================================================
|
||||
|
||||
const searchInputSchema = z.object({
|
||||
query: z.string().min(1),
|
||||
documentId: z.string(),
|
||||
limit: z.number().min(1).max(20).optional(),
|
||||
threshold: z.number().min(0).max(1).optional(),
|
||||
});
|
||||
|
||||
type _SearchInput = z.infer<typeof searchInputSchema>;
|
||||
|
||||
const chatsRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>()
|
||||
.post(
|
||||
"/",
|
||||
enforceAuth,
|
||||
rateLimiter,
|
||||
validate("json", createChatSchema),
|
||||
async (c) => {
|
||||
const input = c.req.valid("json");
|
||||
|
||||
// Deduct credits
|
||||
await deductCredits(Credits.COST.DEFAULT, "pdf-chat")(c, async () => { /* noop */ });
|
||||
|
||||
return c.json(
|
||||
await createChat({
|
||||
...input,
|
||||
userId: c.var.user.id,
|
||||
}),
|
||||
);
|
||||
},
|
||||
)
|
||||
.get("/", enforceAuth, async (c) => c.json(await getUserChats(c.var.user.id)))
|
||||
.get("/:id", enforceAuth, async (c) =>
|
||||
c.json((await getChat(c.req.param("id"))) ?? null),
|
||||
)
|
||||
.delete("/:id", enforceAuth, async (c) =>
|
||||
c.json(await deleteChat(c.req.param("id"))),
|
||||
)
|
||||
.post(
|
||||
"/:id/messages",
|
||||
enforceAuth,
|
||||
rateLimiter,
|
||||
validate("json", pdfMessageSchema),
|
||||
async (c) => {
|
||||
const input = c.req.valid("json");
|
||||
const chatId = c.req.param("id");
|
||||
|
||||
// Get documents for this chat to enable document-specific search
|
||||
const documents = await getChatDocuments(chatId);
|
||||
const documentIds = documents.map((d) => d.id);
|
||||
console.log(`📝 POST /:id/messages - chatId: ${chatId}, documents found: ${documents.length}, documentIds:`, documentIds);
|
||||
|
||||
// Deduct credits
|
||||
await deductCredits(Credits.COST.DEFAULT, "pdf-chat")(c, async () => { /* noop */ });
|
||||
|
||||
return streamChatWithDocuments({
|
||||
...input,
|
||||
signal: c.req.raw.signal,
|
||||
chatId,
|
||||
documentIds,
|
||||
});
|
||||
},
|
||||
)
|
||||
.get("/:id/messages", enforceAuth, async (c) =>
|
||||
c.json(await getChatMessages(c.req.param("id"))),
|
||||
)
|
||||
.get("/:id/documents", enforceAuth, async (c) =>
|
||||
c.json(await getChatDocuments(c.req.param("id"))),
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// Embeddings Router
|
||||
// ============================================================================
|
||||
|
||||
const embeddingsRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>()
|
||||
.get("/:id", enforceAuth, async (c) => {
|
||||
const { getEmbeddingById } = await import("@turbostarter/ai/pdf/embeddings");
|
||||
const embedding = await getEmbeddingById(c.req.param("id"));
|
||||
if (!embedding) {
|
||||
return c.json({ error: "Embedding not found" }, 404);
|
||||
}
|
||||
return c.json(embedding);
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Search Router (WF-0028 Dual-Resolution Search)
|
||||
// ============================================================================
|
||||
|
||||
const searchRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>()
|
||||
.post(
|
||||
"/",
|
||||
enforceAuth,
|
||||
validate("json", searchInputSchema),
|
||||
async (c) => {
|
||||
const input = c.req.valid("json");
|
||||
const results = await searchWithCitations(input.query, input.documentId, {
|
||||
limit: input.limit,
|
||||
threshold: input.threshold,
|
||||
});
|
||||
return c.json({ data: results });
|
||||
},
|
||||
)
|
||||
// NOTE: More specific route must come BEFORE generic :chunkId route
|
||||
.get("/citation-units/single/:id", enforceAuth, async (c) => {
|
||||
const unitId = c.req.param("id");
|
||||
const unit = await getCitationUnitById(unitId);
|
||||
if (!unit) {
|
||||
return c.json({ error: "Citation unit not found" }, 404);
|
||||
}
|
||||
return c.json({ data: unit });
|
||||
})
|
||||
.get("/citation-units/:chunkId", enforceAuth, async (c) => {
|
||||
const chunkId = c.req.param("chunkId");
|
||||
const units = await getCitationUnitsForChunk(chunkId);
|
||||
return c.json({ data: units });
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Documents Router (document status and management)
|
||||
// ============================================================================
|
||||
|
||||
const documentsRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>()
|
||||
.get("/:id/status", enforceAuth, async (c) => {
|
||||
const document = await getDocument(c.req.param("id"));
|
||||
if (!document) {
|
||||
return c.json({ error: "Document not found" }, 404);
|
||||
}
|
||||
return c.json({
|
||||
id: document.id,
|
||||
processingStatus: document.processingStatus,
|
||||
processingError: document.processingError,
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Diagnostics Router (for debugging embedding issues)
|
||||
// ============================================================================
|
||||
|
||||
const diagnosticsRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>()
|
||||
.get("/chat/:chatId", enforceAuth, async (c) => {
|
||||
const { sql } = await import("@turbostarter/db");
|
||||
const { db } = await import("@turbostarter/db/server");
|
||||
|
||||
const chatId = c.req.param("chatId");
|
||||
|
||||
// Get documents for this chat
|
||||
const documents = await getChatDocuments(chatId);
|
||||
|
||||
if (documents.length === 0) {
|
||||
return c.json({ error: "No documents found for chat", chatId });
|
||||
}
|
||||
|
||||
// Get embedding counts per document
|
||||
const diagnostics = await Promise.all(
|
||||
documents.map(async (doc) => {
|
||||
const countResult = await db.execute<{ count: string }>(sql`
|
||||
SELECT COUNT(*) as count FROM pdf.embedding WHERE document_id = ${doc.id}
|
||||
`);
|
||||
const rows = Array.isArray(countResult) ? countResult : [];
|
||||
const count = parseInt(rows[0]?.count ?? "0", 10);
|
||||
|
||||
// Get sample content
|
||||
const sampleResult = await db.execute<{ content: string; page_number: number }>(sql`
|
||||
SELECT LEFT(content, 100) as content, page_number
|
||||
FROM pdf.embedding
|
||||
WHERE document_id = ${doc.id}
|
||||
LIMIT 2
|
||||
`);
|
||||
const samples = Array.isArray(sampleResult) ? sampleResult : [];
|
||||
|
||||
return {
|
||||
documentId: doc.id,
|
||||
documentName: doc.name,
|
||||
embeddingCount: count,
|
||||
samples: samples.map(s => ({
|
||||
preview: s.content,
|
||||
page: s.page_number,
|
||||
})),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return c.json({
|
||||
chatId,
|
||||
documentCount: documents.length,
|
||||
documents: diagnostics,
|
||||
totalEmbeddings: diagnostics.reduce((sum, d) => sum + d.embeddingCount, 0),
|
||||
});
|
||||
});
|
||||
|
||||
export const pdfRouter = new Hono()
|
||||
.route("/chats", chatsRouter)
|
||||
.route("/documents", documentsRouter)
|
||||
.route("/embeddings", embeddingsRouter)
|
||||
.route("/search", searchRouter)
|
||||
.route("/diagnostics", diagnosticsRouter);
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Hono } from "hono";
|
||||
|
||||
import { getUserCredits } from "@turbostarter/ai/credits/server";
|
||||
|
||||
import { enforceAuth } from "../../middleware";
|
||||
|
||||
import { chatRouter } from "./chat";
|
||||
import { imageRouter } from "./image";
|
||||
import { pdfRouter } from "./pdf";
|
||||
import { sttRouter } from "./stt";
|
||||
import { ttsRouter } from "./tts";
|
||||
|
||||
export const aiRouter = new Hono()
|
||||
.use(enforceAuth)
|
||||
.route("/chat", chatRouter)
|
||||
.route("/pdf", pdfRouter)
|
||||
.route("/image", imageRouter)
|
||||
.route("/tts", ttsRouter)
|
||||
.route("/stt", sttRouter)
|
||||
.get("/credits", async (c) => c.json(await getUserCredits(c.var.user.id)));
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Hono } from "hono";
|
||||
|
||||
import { Credits } from "@turbostarter/ai/credits/utils";
|
||||
import { transcribe } from "@turbostarter/ai/stt/api";
|
||||
import { transcriptionOptionsSchema } from "@turbostarter/ai/stt/schema";
|
||||
|
||||
import { deductCredits, enforceAuth, rateLimiter } from "../../middleware";
|
||||
|
||||
import type { User } from "@turbostarter/auth";
|
||||
|
||||
export const sttRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>().post("/", enforceAuth, rateLimiter, async (c) => {
|
||||
console.log("[STT] Request received");
|
||||
|
||||
// Use Hono's typed FormData methods to work across different runtime environments
|
||||
const formData = await c.req.formData();
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
||||
const audioFile = ((formData as any).get?.("audio") ?? (formData as any).getAll?.("audio")?.[0]) as File | null;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
console.log("[STT] Audio file:", audioFile ? `${audioFile.name} (${audioFile.size} bytes, ${audioFile.type})` : "null");
|
||||
|
||||
if (!audioFile) {
|
||||
return c.json({ error: "No audio file provided" }, 400);
|
||||
}
|
||||
|
||||
// Parse optional parameters
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
||||
const fd = formData as any;
|
||||
const language = (fd.get?.("language") ?? fd.getAll?.("language")?.[0]) as string | null;
|
||||
const prompt = (fd.get?.("prompt") ?? fd.getAll?.("prompt")?.[0]) as string | null;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
const options = transcriptionOptionsSchema.parse({
|
||||
language: language ?? undefined,
|
||||
prompt: prompt ?? undefined,
|
||||
});
|
||||
|
||||
// Deduct credits
|
||||
console.log("[STT] Deducting credits...");
|
||||
await deductCredits(Credits.COST.DEFAULT, "speech-to-text")(c, async () => { /* noop */ });
|
||||
console.log("[STT] Credits deducted, calling OpenAI Whisper...");
|
||||
|
||||
try {
|
||||
const result = await transcribe(audioFile, options);
|
||||
console.log("[STT] Transcription successful:", result.text.substring(0, 50));
|
||||
return c.json(result);
|
||||
} catch (error) {
|
||||
console.error("[STT] Transcription error:", error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Hono } from "hono";
|
||||
|
||||
import { Credits } from "@turbostarter/ai/credits/utils";
|
||||
import { getVoices, textToSpeech } from "@turbostarter/ai/tts/api";
|
||||
import { ttsSchema } from "@turbostarter/ai/tts/schema";
|
||||
|
||||
import { deductCredits, enforceAuth, rateLimiter, validate } from "../../middleware";
|
||||
|
||||
import type { User } from "@turbostarter/auth";
|
||||
|
||||
export const ttsRouter = new Hono<{
|
||||
Variables: {
|
||||
user: User;
|
||||
};
|
||||
}>()
|
||||
.post(
|
||||
"/",
|
||||
enforceAuth,
|
||||
rateLimiter,
|
||||
validate("json", ttsSchema),
|
||||
async (c) => {
|
||||
const input = c.req.valid("json");
|
||||
|
||||
// Deduct credits
|
||||
await deductCredits(Credits.COST.HIGH, "text-to-speech")(c, async () => { /* noop */ });
|
||||
|
||||
return new Response(
|
||||
(await textToSpeech(input)) as unknown as ConstructorParameters<
|
||||
typeof Response
|
||||
>[0],
|
||||
{
|
||||
headers: { "Content-Type": "audio/mpeg" },
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.get("/voices", enforceAuth, async (c) => {
|
||||
const voices = await getVoices();
|
||||
return c.json(voices);
|
||||
});
|
||||
Reference in New Issue
Block a user