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 >
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user