feat: add download ZIP and push to GitHub features, fix code overflow in chat
This commit is contained in:
108
lib/artifact-utils.ts
Normal file
108
lib/artifact-utils.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import JSZip from "jszip";
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
export async function downloadArtifactAsZip(data: string, type: string, language: string = "html") {
|
||||
const zip = new JSZip();
|
||||
const extension = language === "html" || type === "web" || type === "app" ? "html" : (language === "typescript" || language === "tsx" ? "tsx" : "txt");
|
||||
const filename = `artifact-${Date.now()}.${extension}`;
|
||||
|
||||
// Check if data contains common multi-file structures (simple heuristic)
|
||||
// If it looks like a full project (multiple files defined in one block), we could parse it,
|
||||
// but for now we'll just save the main artifact.
|
||||
zip.file(filename, data);
|
||||
|
||||
// Add a basic README
|
||||
zip.file("README.md", `# AI Generated Artifact\n\nType: ${type}\nGenerated: ${new Date().toLocaleString()}`);
|
||||
|
||||
const content = await zip.generateAsync({ type: "blob" });
|
||||
saveAs(content, `promptarch-artifact-${Date.now()}.zip`);
|
||||
}
|
||||
|
||||
export async function pushToGithub(
|
||||
token: string,
|
||||
repoName: string,
|
||||
files: { path: string; content: string }[],
|
||||
description: string = "Generated by PromptArch"
|
||||
) {
|
||||
const headers = {
|
||||
'Authorization': `token ${token}`,
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
// 1. Check if repo exists, if not create it
|
||||
let repoData;
|
||||
const userResponse = await fetch('https://api.github.com/user', { headers });
|
||||
if (!userResponse.ok) throw new Error("Failed to authenticate with GitHub");
|
||||
const userData = await userResponse.json();
|
||||
const username = userData.login;
|
||||
|
||||
const repoCheckResponse = await fetch(`https://api.github.com/repos/${username}/${repoName}`, { headers });
|
||||
|
||||
if (repoCheckResponse.status === 404) {
|
||||
// Create repo
|
||||
const createResponse = await fetch('https://api.github.com/user/repos', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
name: repoName,
|
||||
description,
|
||||
auto_init: true
|
||||
})
|
||||
});
|
||||
if (!createResponse.ok) throw new Error("Failed to create repository");
|
||||
repoData = await createResponse.json();
|
||||
// Wait a bit for repo to be ready
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
} else {
|
||||
repoData = await repoCheckResponse.json();
|
||||
}
|
||||
|
||||
// 2. Get latest commit SHA of default branch
|
||||
const branchResponse = await fetch(`https://api.github.com/repos/${username}/${repoName}/branches/${repoData.default_branch}`, { headers });
|
||||
const branchData = await branchResponse.json();
|
||||
const latestCommitSha = branchData.commit.sha;
|
||||
|
||||
// 3. Create a new tree
|
||||
const treeItems = files.map(file => ({
|
||||
path: file.path,
|
||||
mode: '100644',
|
||||
type: 'blob',
|
||||
content: file.content
|
||||
}));
|
||||
|
||||
const treeResponse = await fetch(`https://api.github.com/repos/${username}/${repoName}/git/trees`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
base_tree: latestCommitSha,
|
||||
tree: treeItems
|
||||
})
|
||||
});
|
||||
const treeData = await treeResponse.json();
|
||||
|
||||
// 4. Create a new commit
|
||||
const commitResponse = await fetch(`https://api.github.com/repos/${username}/${repoName}/git/commits`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
message: `Update from PromptArch: ${new Date().toISOString()}`,
|
||||
tree: treeData.sha,
|
||||
parents: [latestCommitSha]
|
||||
})
|
||||
});
|
||||
const commitData = await commitResponse.json();
|
||||
|
||||
// 5. Update the reference
|
||||
const refResponse = await fetch(`https://api.github.com/repos/${username}/${repoName}/git/refs/heads/${repoData.default_branch}`, {
|
||||
method: 'PATCH',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
sha: commitData.sha
|
||||
})
|
||||
});
|
||||
|
||||
if (!refResponse.ok) throw new Error("Failed to update branch reference");
|
||||
|
||||
return { url: repoData.html_url };
|
||||
}
|
||||
@@ -435,6 +435,14 @@ export const translations = {
|
||||
live: "Live",
|
||||
chatTitle: "New Chat",
|
||||
chatPrefix: "Chat",
|
||||
downloadZip: "Download ZIP",
|
||||
pushToGithub: "Push to GitHub",
|
||||
githubAuth: "GitHub Auth",
|
||||
repositoryName: "Repository Name",
|
||||
pushingToGithub: "Pushing to GitHub...",
|
||||
successPush: "Successfully pushed to GitHub!",
|
||||
errorPush: "Failed to push to GitHub",
|
||||
enterGithubToken: "Enter GitHub Personal Access Token",
|
||||
}
|
||||
},
|
||||
ru: {
|
||||
@@ -871,6 +879,14 @@ export const translations = {
|
||||
live: "Живой",
|
||||
chatTitle: "Новый чат",
|
||||
chatPrefix: "Чат",
|
||||
downloadZip: "Скачать ZIP",
|
||||
pushToGithub: "Пуш в GitHub",
|
||||
githubAuth: "GitHub Auth",
|
||||
repositoryName: "Название репозитория",
|
||||
pushingToGithub: "Отправка в GitHub...",
|
||||
successPush: "Успешно отправлено в GitHub!",
|
||||
errorPush: "Ошибка при отправке в GitHub",
|
||||
enterGithubToken: "Введите GitHub Personal Access Token",
|
||||
}
|
||||
},
|
||||
he: {
|
||||
@@ -1307,6 +1323,14 @@ export const translations = {
|
||||
live: "חי",
|
||||
chatTitle: "צ'אט חדש",
|
||||
chatPrefix: "צ'אט",
|
||||
downloadZip: "הורד ZIP",
|
||||
pushToGithub: "דחוף ל-GitHub",
|
||||
githubAuth: "אימות GitHub",
|
||||
repositoryName: "שם מאגר",
|
||||
pushingToGithub: "דוחף ל-GitHub...",
|
||||
successPush: "נדחף בהצלחה ל-GitHub!",
|
||||
errorPush: "נכשל בדחיפה ל-GitHub",
|
||||
enterGithubToken: "הזן GitHub Personal Access Token",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ interface AppState {
|
||||
refreshToken?: string;
|
||||
expiresAt?: number;
|
||||
} | null;
|
||||
githubToken?: string | null;
|
||||
isProcessing: boolean;
|
||||
error: string | null;
|
||||
history: {
|
||||
@@ -65,6 +66,7 @@ interface AppState {
|
||||
setAvailableModels: (provider: ModelProvider, models: string[]) => void;
|
||||
setApiKey: (provider: ModelProvider, key: string) => void;
|
||||
setQwenTokens: (tokens?: { accessToken: string; refreshToken?: string; expiresAt?: number } | null) => void;
|
||||
setGithubToken: (token: string | null) => void;
|
||||
setProcessing: (processing: boolean) => void;
|
||||
setError: (error: string | null) => void;
|
||||
addToHistory: (prompt: string) => void;
|
||||
@@ -107,6 +109,7 @@ const useStore = create<AppState>((set) => ({
|
||||
ollama: "",
|
||||
zai: "",
|
||||
},
|
||||
githubToken: null,
|
||||
isProcessing: false,
|
||||
error: null,
|
||||
history: [],
|
||||
@@ -174,6 +177,7 @@ const useStore = create<AppState>((set) => ({
|
||||
apiKeys: { ...state.apiKeys, [provider]: key },
|
||||
})),
|
||||
setQwenTokens: (tokens) => set({ qwenTokens: tokens }),
|
||||
setGithubToken: (token) => set({ githubToken: token }),
|
||||
setProcessing: (processing) => set({ isProcessing: processing }),
|
||||
setError: (error) => set({ error }),
|
||||
addToHistory: (prompt) =>
|
||||
|
||||
Reference in New Issue
Block a user