mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Improve workspace file viewer previews
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
d503982075
commit
fec3c19a0e
8 files changed
+185
-63
No files matched your search
@@ -215,6 +215,28 @@ describeEmbeddedPostgres("workspace file resources", () => {
|
||||
expect(resolved.capabilities.preview).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves and reads video workspace files as base64 previews", async () => {
|
||||
const { projectRoot, executionRoot } = await makeWorkspace();
|
||||
const graph = await seedGraph(db, { projectRoot, executionRoot });
|
||||
await fs.writeFile(path.join(projectRoot, "demo.mp4"), Buffer.from([0, 0, 0, 24, 102, 116, 121, 112]));
|
||||
|
||||
const resolved = await workspaceFileResourceService(db).resolve(graph.issueId, {
|
||||
path: "demo.mp4",
|
||||
workspace: "project",
|
||||
});
|
||||
expect(resolved.previewKind).toBe("video");
|
||||
expect(resolved.contentType).toBe("video/mp4");
|
||||
expect(resolved.capabilities.preview).toBe(true);
|
||||
|
||||
const content = await workspaceFileResourceService(db).readContent(graph.issueId, {
|
||||
path: "demo.mp4",
|
||||
workspace: "project",
|
||||
});
|
||||
expect(content.resource.previewKind).toBe("video");
|
||||
expect(content.content.encoding).toBe("base64");
|
||||
expect(content.content.data).toBe(Buffer.from([0, 0, 0, 24, 102, 116, 121, 112]).toString("base64"));
|
||||
});
|
||||
|
||||
it("lists and searches safe file candidates from the preferred execution workspace", async () => {
|
||||
const { root, projectRoot, executionRoot } = await makeWorkspace();
|
||||
const graph = await seedGraph(db, { projectRoot, executionRoot });
|
||||
|
||||
@@ -87,6 +87,13 @@ const IMAGE_CONTENT_TYPES = new Map([
|
||||
[".webp", "image/webp"],
|
||||
]);
|
||||
|
||||
const VIDEO_CONTENT_TYPES = new Map([
|
||||
[".m4v", "video/mp4"],
|
||||
[".mov", "video/quicktime"],
|
||||
[".mp4", "video/mp4"],
|
||||
[".webm", "video/webm"],
|
||||
]);
|
||||
|
||||
type IssueRow = typeof issues.$inferSelect;
|
||||
type ExecutionWorkspaceRow = typeof executionWorkspaces.$inferSelect;
|
||||
type ProjectWorkspaceRow = typeof projectWorkspaces.$inferSelect;
|
||||
@@ -118,7 +125,9 @@ type WorkspaceFileListQueryInput = {
|
||||
};
|
||||
|
||||
function previewCapForKind(kind: WorkspaceFilePreviewKind) {
|
||||
return kind === "image" || kind === "pdf" ? WORKSPACE_FILE_MEDIA_MAX_BYTES : WORKSPACE_FILE_TEXT_MAX_BYTES;
|
||||
return kind === "image" || kind === "video" || kind === "pdf"
|
||||
? WORKSPACE_FILE_MEDIA_MAX_BYTES
|
||||
: WORKSPACE_FILE_TEXT_MAX_BYTES;
|
||||
}
|
||||
|
||||
function relativePathFromReal(rootReal: string, targetReal: string) {
|
||||
@@ -184,6 +193,7 @@ function shouldPruneSegments(segments: string[]) {
|
||||
function contentTypeForPath(filePath: string): string | null {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
if (IMAGE_CONTENT_TYPES.has(ext)) return IMAGE_CONTENT_TYPES.get(ext) ?? null;
|
||||
if (VIDEO_CONTENT_TYPES.has(ext)) return VIDEO_CONTENT_TYPES.get(ext) ?? null;
|
||||
if (ext === ".pdf") return "application/pdf";
|
||||
if (ext === ".svg") return "image/svg+xml";
|
||||
if (ext === ".html" || ext === ".htm") return "text/html";
|
||||
@@ -194,6 +204,7 @@ function contentTypeForPath(filePath: string): string | null {
|
||||
function previewKindForKnownContentType(contentType: string | null): WorkspaceFilePreviewKind | null {
|
||||
if (!contentType) return null;
|
||||
if (contentType.startsWith("image/") && contentType !== "image/svg+xml") return "image";
|
||||
if (contentType.startsWith("video/")) return "video";
|
||||
if (contentType === "application/pdf") return "pdf";
|
||||
if (contentType === "text/html") return "unsupported";
|
||||
if (contentType === "image/svg+xml" || contentType.startsWith("text/")) return "text";
|
||||
|
||||
@@ -1936,6 +1936,7 @@ async function isRuntimeServiceUrlHealthy(
|
||||
) {
|
||||
if (!url) return true;
|
||||
const healthUrl = resolveRuntimeServiceHealthUrl(url, input);
|
||||
if (!healthUrl) return true;
|
||||
try {
|
||||
const response = await fetch(healthUrl, { signal: AbortSignal.timeout(2_000) });
|
||||
return response.ok;
|
||||
|
||||
Reference in new issue
Block a user