feat: add download ZIP and push to GitHub features, fix code overflow in chat

This commit is contained in:
Gemini AI
2025-12-29 19:20:25 +04:00
Unverified
parent 5fcc6c0948
commit 3d00e5dca4
6 changed files with 386 additions and 3 deletions

108
lib/artifact-utils.ts Normal file
View 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 };
}

View File

@@ -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",
}
}
};

View File

@@ -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) =>