nexus/server/src/services/chat-files.ts
Nexus Dev 859ba1707b feat(25-07): create placeholderService and add markAsPlaceholder method
- Create server/src/services/placeholder-service.ts with addEntry, replaceEntry, listEntries
- Generates PLACEHOLDERS.md with Active Placeholders and Replaced markdown tables
- Add ChatPlaceholderEntry interface to packages/shared/src/types/chat.ts
- Export ChatPlaceholderEntry from packages/shared/src/index.ts
- Add markAsPlaceholder method to chatFileService in chat-files.ts
2026-04-04 03:55:48 +00:00

131 lines
3.2 KiB
TypeScript

import { eq, desc, asc } from "drizzle-orm";
import type { Db } from "@paperclipai/db";
import { chatFiles, chatFileReferences } from "@paperclipai/db";
const CODE_MIME_TYPES = new Set([
"text/javascript",
"text/typescript",
"application/javascript",
"application/typescript",
"text/css",
"text/html",
"application/json",
"text/x-python",
"text/x-java",
"text/x-c",
"text/x-cpp",
"text/x-csharp",
"text/x-ruby",
"text/x-go",
"text/x-rust",
"text/x-swift",
"text/x-kotlin",
"text/x-php",
"text/x-shellscript",
"application/x-sh",
"text/x-yaml",
"application/x-yaml",
"text/x-toml",
]);
const DOCUMENT_MIME_TYPES = new Set([
"application/pdf",
"text/plain",
"text/markdown",
"text/csv",
]);
export function deriveCategory(mimeType: string): string {
const mt = mimeType.toLowerCase();
if (mt.startsWith("image/")) return "image";
if (CODE_MIME_TYPES.has(mt)) return "code";
if (DOCUMENT_MIME_TYPES.has(mt)) return "document";
return "other";
}
export function chatFileService(db: Db) {
return {
create(
companyId: string,
data: Omit<typeof chatFiles.$inferInsert, "companyId" | "id" | "createdAt" | "updatedAt">,
) {
return db
.insert(chatFiles)
.values({ ...data, companyId })
.returning()
.then((rows) => rows[0]!);
},
getById(id: string) {
return db
.select()
.from(chatFiles)
.where(eq(chatFiles.id, id))
.then((rows) => rows[0] ?? null);
},
listByConversation(conversationId: string, opts?: { limit?: number }) {
const limit = opts?.limit ?? 50;
return db
.select()
.from(chatFiles)
.where(eq(chatFiles.conversationId, conversationId))
.orderBy(desc(chatFiles.createdAt))
.limit(limit);
},
listByMessage(messageId: string) {
return db
.select()
.from(chatFiles)
.where(eq(chatFiles.messageId, messageId))
.orderBy(asc(chatFiles.createdAt));
},
createReference(data: {
fileId: string;
conversationId: string;
messageId?: string;
}) {
return db
.insert(chatFileReferences)
.values(data)
.returning()
.then((rows) => rows[0]!);
},
listReferences(fileId: string) {
return db
.select()
.from(chatFileReferences)
.where(eq(chatFileReferences.fileId, fileId));
},
attachToMessage(fileId: string, messageId: string) {
return db
.update(chatFiles)
.set({ messageId, updatedAt: new Date() })
.where(eq(chatFiles.id, fileId))
.returning()
.then((rows) => rows[0]!);
},
promoteToProject(fileId: string, projectId: string) {
return db
.update(chatFiles)
.set({ projectId, updatedAt: new Date() })
.where(eq(chatFiles.id, fileId))
.returning()
.then((rows) => rows[0] ?? null);
},
markAsPlaceholder(fileId: string) {
return db
.update(chatFiles)
.set({ category: "placeholder", updatedAt: new Date() })
.where(eq(chatFiles.id, fileId))
.returning()
.then((rows) => rows[0] ?? null);
},
};
}