- 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
131 lines
3.2 KiB
TypeScript
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);
|
|
},
|
|
};
|
|
}
|