feat: add download ZIP and push to GitHub features, fix code overflow in chat
This commit is contained in:
@@ -9,6 +9,8 @@ import {
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypeHighlight from "rehype-highlight";
|
||||
import { Download, Github } from "lucide-react";
|
||||
import { downloadArtifactAsZip, pushToGithub } from "@/lib/artifact-utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AIAssistMessage } from "@/types";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -443,7 +445,9 @@ export default function AIAssist() {
|
||||
selectedProvider,
|
||||
selectedModels,
|
||||
setSelectedModel,
|
||||
setSelectedProvider
|
||||
setSelectedProvider,
|
||||
githubToken,
|
||||
setGithubToken
|
||||
} = useStore();
|
||||
const t = translations[language].aiAssist;
|
||||
const common = translations[language].common;
|
||||
@@ -473,6 +477,11 @@ export default function AIAssist() {
|
||||
const [isAuthenticatingQwen, setIsAuthenticatingQwen] = useState(false);
|
||||
const [qwenAuthError, setQwenAuthError] = useState<string | null>(null);
|
||||
|
||||
const [isPushingToGithub, setIsPushingToGithub] = useState(false);
|
||||
const [showGithubDialog, setShowGithubDialog] = useState(false);
|
||||
const [githubRepoName, setGithubRepoName] = useState("my-ai-artifact");
|
||||
const [tempGithubToken, setTempGithubToken] = useState(githubToken || "");
|
||||
|
||||
// Check if Qwen is authenticated
|
||||
const isQwenAuthed = modelAdapter.hasQwenAuth();
|
||||
|
||||
@@ -715,6 +724,31 @@ export default function AIAssist() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadZip = () => {
|
||||
if (!previewData) return;
|
||||
downloadArtifactAsZip(previewData.data, previewData.type, previewData.language);
|
||||
};
|
||||
|
||||
const handlePushToGithub = async () => {
|
||||
if (!previewData || !tempGithubToken || !githubRepoName) return;
|
||||
setIsPushingToGithub(true);
|
||||
try {
|
||||
setGithubToken(tempGithubToken);
|
||||
const extension = previewData.language === "html" || previewData.type === "web" || previewData.type === "app" ? "html" : (previewData.language === "typescript" || previewData.language === "tsx" ? "tsx" : "txt");
|
||||
const files = [
|
||||
{ path: `index.${extension}`, content: previewData.data },
|
||||
{ path: "README.md", content: `# Generated by PromptArch\n\nType: ${previewData.type}\nGenerated: ${new Date().toLocaleString()}` }
|
||||
];
|
||||
const result = await pushToGithub(tempGithubToken, githubRepoName, files);
|
||||
alert(`${t.successPush}\nURL: ${result.url}`);
|
||||
setShowGithubDialog(false);
|
||||
} catch (err) {
|
||||
alert(`${t.errorPush}: ${err instanceof Error ? err.message : "Unknown error"}`);
|
||||
} finally {
|
||||
setIsPushingToGithub(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="ai-assist h-[calc(100vh-140px)] flex flex-col lg:flex-row gap-4 lg:gap-8 overflow-hidden animate-in fade-in duration-700">
|
||||
{/* --- Chat Panel --- */}
|
||||
@@ -934,8 +968,32 @@ export default function AIAssist() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none leading-relaxed font-medium">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeHighlight]}>
|
||||
<div className="prose prose-sm dark:prose-invert max-w-full overflow-hidden leading-relaxed font-medium">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeHighlight]}
|
||||
components={{
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
return !inline ? (
|
||||
<div className="relative group/code w-full overflow-hidden">
|
||||
<pre className={cn(
|
||||
"p-4 rounded-xl bg-slate-950/50 border border-white/5 overflow-x-auto scrollbar-thin scrollbar-thumb-white/10",
|
||||
className
|
||||
)}>
|
||||
<code className={cn("whitespace-pre", className)} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
) : (
|
||||
<code className={cn("bg-blue-500/10 dark:bg-blue-500/20 px-1.5 py-0.5 rounded text-blue-600 dark:text-blue-300", className)} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{parseStreamingContent(msg.content, msg.agent || "general").chatDisplay || (msg.role === "assistant" ? "..." : "")}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
@@ -1103,6 +1161,24 @@ export default function AIAssist() {
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10 text-blue-200/70 hover:text-white hover:bg-blue-900 rounded-2xl"
|
||||
onClick={handleDownloadZip}
|
||||
title={t.downloadZip}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-10 w-10 text-blue-200/70 hover:text-white hover:bg-blue-900 rounded-2xl"
|
||||
onClick={() => setShowGithubDialog(true)}
|
||||
title={t.pushToGithub}
|
||||
>
|
||||
<Github className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -1159,6 +1235,65 @@ export default function AIAssist() {
|
||||
color: inherit;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* GitHub Push Dialog */}
|
||||
{showGithubDialog && (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-md p-4 animate-in fade-in duration-300">
|
||||
<Card className="w-full max-w-md bg-[#0b1414] border border-blue-900/50 shadow-2xl p-8 rounded-[2rem] animate-in zoom-in-95 duration-300">
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="p-3 bg-white rounded-2xl">
|
||||
<Github className="h-6 w-6 text-black" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-black text-white">{t.pushToGithub}</h3>
|
||||
<p className="text-xs text-blue-200/50 mt-1">{t.githubAuth}</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-auto text-blue-200/40 hover:text-white"
|
||||
onClick={() => setShowGithubDialog(false)}
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-black uppercase tracking-widest text-blue-400">{t.enterGithubToken}</label>
|
||||
<Input
|
||||
type="password"
|
||||
value={tempGithubToken}
|
||||
onChange={(e) => setTempGithubToken(e.target.value)}
|
||||
placeholder="ghp_xxxxxxxxxxxx"
|
||||
className="bg-white/5 border-blue-900/50 text-white placeholder:text-blue-900/50 rounded-xl h-12"
|
||||
/>
|
||||
<p className="text-[9px] text-blue-200/30">
|
||||
Requires 'repo' scope. Get one at github.com/settings/tokens
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-black uppercase tracking-widest text-blue-400">{t.repositoryName}</label>
|
||||
<Input
|
||||
value={githubRepoName}
|
||||
onChange={(e) => setGithubRepoName(e.target.value)}
|
||||
placeholder="repo-name"
|
||||
className="bg-white/5 border-blue-900/50 text-white placeholder:text-blue-900/50 rounded-xl h-12"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handlePushToGithub}
|
||||
disabled={isPushingToGithub || !tempGithubToken || !githubRepoName}
|
||||
className="w-full bg-blue-600 hover:bg-blue-500 text-white font-black uppercase tracking-widest py-6 rounded-2xl shadow-lg shadow-blue-500/20 disabled:opacity-50"
|
||||
>
|
||||
{isPushingToGithub ? t.pushingToGithub : t.pushToGithub}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
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) =>
|
||||
|
||||
109
package-lock.json
generated
109
package-lock.json
generated
@@ -18,6 +18,8 @@
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-next": "^15.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "^16.1.1",
|
||||
"postcss": "^8.4.49",
|
||||
@@ -36,6 +38,7 @@
|
||||
"zustand": "^5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2"
|
||||
@@ -1377,6 +1380,13 @@
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/file-saver": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
|
||||
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
@@ -2436,6 +2446,12 @@
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
@@ -3490,6 +3506,12 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-saver": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -3923,6 +3945,12 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/immer": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
||||
@@ -3958,6 +3986,12 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/inline-style-parser": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
|
||||
@@ -4532,6 +4566,18 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
|
||||
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
|
||||
"license": "(MIT OR GPL-3.0-or-later)",
|
||||
"dependencies": {
|
||||
"lie": "~3.3.0",
|
||||
"pako": "~1.0.2",
|
||||
"readable-stream": "~2.3.6",
|
||||
"setimmediate": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -4572,6 +4618,15 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
@@ -5904,6 +5959,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@@ -6175,6 +6236,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@@ -6327,6 +6394,27 @@
|
||||
"pify": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream/node_modules/isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@@ -6605,6 +6693,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-push-apply": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
|
||||
@@ -6699,6 +6793,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.34.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
|
||||
@@ -6900,6 +7000,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.includes": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-next": "^15.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "^16.1.1",
|
||||
"postcss": "^8.4.49",
|
||||
@@ -36,6 +38,7 @@
|
||||
"zustand": "^5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/react": "^19.0.1",
|
||||
"@types/react-dom": "^19.0.2"
|
||||
|
||||
Reference in New Issue
Block a user