feat: support artifact preview

This commit is contained in:
Henry Li
2026-01-17 15:09:44 +08:00
parent 80c928fcf5
commit 0c6f8353bf
16 changed files with 482 additions and 42 deletions

View File

@@ -0,0 +1,20 @@
import { useQuery } from "@tanstack/react-query";
import { loadArtifactContent } from "./loader";
export function useArtifactContent({
filepath,
threadId,
enabled,
}: {
filepath: string;
threadId: string;
enabled?: boolean;
}) {
const { data, isLoading, error } = useQuery({
queryKey: ["artifact", filepath, threadId],
queryFn: () => loadArtifactContent({ filepath, threadId }),
enabled,
});
return { content: data, isLoading, error };
}

View File

@@ -0,0 +1 @@
export * from "./loader";

View File

@@ -0,0 +1,14 @@
import { urlOfArtifact } from "./utils";
export async function loadArtifactContent({
filepath,
threadId,
}: {
filepath: string;
threadId: string;
}) {
const url = urlOfArtifact({ filepath, threadId });
const response = await fetch(url);
const text = await response.text();
return text;
}

View File

@@ -0,0 +1,11 @@
export function urlOfArtifact({
filepath,
threadId,
download = false,
}: {
filepath: string;
threadId: string;
download?: boolean;
}) {
return `http://localhost:8000/api/threads/${threadId}/artifacts${filepath}${download ? "?download=true" : ""}`;
}

View File

@@ -1,8 +1,159 @@
import type { BundledLanguage } from "shiki";
const extensionMap: Record<string, BundledLanguage> = {
// JavaScript/TypeScript ecosystem
js: "javascript",
jsx: "jsx",
ts: "typescript",
tsx: "tsx",
mjs: "javascript",
cjs: "javascript",
mts: "typescript",
cts: "typescript",
// Web
html: "html",
htm: "html",
css: "css",
scss: "scss",
sass: "sass",
less: "less",
vue: "vue",
svelte: "svelte",
astro: "astro",
// Python
py: "python",
pyi: "python",
pyw: "python",
// Java/JVM
java: "java",
kt: "kotlin",
kts: "kotlin",
scala: "scala",
groovy: "groovy",
// C/C++
c: "c",
h: "c",
cpp: "cpp",
cc: "cpp",
cxx: "cpp",
hpp: "cpp",
hxx: "cpp",
hh: "cpp",
// C#
cs: "csharp",
// Go
go: "go",
// Rust
rs: "rust",
// Ruby
rb: "ruby",
rake: "ruby",
// PHP
php: "php",
// Shell/Bash
sh: "bash",
bash: "bash",
zsh: "zsh",
fish: "fish",
// Config & Data
json: "json",
jsonc: "jsonc",
json5: "json5",
yaml: "yaml",
yml: "yaml",
toml: "toml",
xml: "xml",
ini: "ini",
env: "dotenv",
// Markdown & Docs
md: "markdown",
mdx: "mdx",
rst: "rst",
// SQL
sql: "sql",
// Other languages
swift: "swift",
dart: "dart",
lua: "lua",
r: "r",
matlab: "matlab",
julia: "jl",
elm: "elm",
haskell: "haskell",
hs: "haskell",
elixir: "elixir",
ex: "elixir",
clj: "clojure",
cljs: "clojure",
// Infrastructure
dockerfile: "dockerfile",
docker: "docker",
tf: "terraform",
tfvars: "terraform",
hcl: "hcl",
// Build & Config
makefile: "makefile",
cmake: "cmake",
gradle: "groovy",
// Git
gitignore: "git-commit",
gitattributes: "git-commit",
// Misc
graphql: "graphql",
gql: "graphql",
proto: "protobuf",
prisma: "prisma",
wasm: "wasm",
zig: "zig",
v: "v",
};
export function getFileName(filepath: string) {
return filepath.split("/").pop()!;
}
export function getFileExtension(filepath: string) {
return filepath.split(".").pop()!.toLocaleLowerCase();
}
export function checkCodeFile(
filepath: string,
):
| { isCodeFile: true; language: BundledLanguage }
| { isCodeFile: false; language: null } {
const extension = getFileExtension(filepath);
const isCodeFile = extension in extensionMap;
if (isCodeFile) {
return {
isCodeFile: true,
language: extensionMap[extension] as unknown as BundledLanguage,
};
}
return {
isCodeFile: false,
language: null,
};
}
export function getFileExtensionDisplayName(filepath: string) {
const fileName = getFileName(filepath);
const extension = fileName.split(".").pop()!.toLocaleLowerCase();
switch (extension) {