Add full mobile responsive design with hamburger menu, responsive components, and mobile optimizations

This commit is contained in:
Gemini AI
2025-12-26 15:57:31 +04:00
Unverified
parent d3c3490655
commit 6b33913471
10 changed files with 341 additions and 243 deletions

View File

@@ -65,5 +65,51 @@
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Mobile optimizations */
html {
scroll-behavior: smooth;
-webkit-tap-highlight-color: transparent;
}
/* Better touch targets */
button, a, [role="button"] {
touch-action: manipulation;
}
/* Prevent text selection on buttons */
button {
-webkit-user-select: none;
user-select: none;
}
/* Safe area padding for notched devices */
.safe-area-inset {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
}
/* Scrollbar styling for mobile-like experience */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: hsl(var(--border));
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground));
} }
} }

View File

@@ -7,6 +7,7 @@ const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "PromptArch - AI Prompt Engineering Platform", title: "PromptArch - AI Prompt Engineering Platform",
description: "Transform vague ideas into production-ready prompts and PRDs", description: "Transform vague ideas into production-ready prompts and PRDs",
viewport: "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no",
}; };
export default function RootLayout({ export default function RootLayout({

View File

@@ -41,7 +41,7 @@ export default function Home() {
return ( return (
<div className="flex min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800"> <div className="flex min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
<Sidebar currentView={currentView} onViewChange={setCurrentView} /> <Sidebar currentView={currentView} onViewChange={setCurrentView} />
<main className="flex-1 overflow-auto p-8"> <main className="flex-1 overflow-auto pt-16 lg:pt-0 px-4 py-4 lg:p-8">
<div className="mx-auto max-w-7xl"> <div className="mx-auto max-w-7xl">
{renderContent()} {renderContent()}
</div> </div>

View File

@@ -126,28 +126,28 @@ export default function ActionPlanGenerator() {
}; };
return ( return (
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-2"> <div className="mx-auto grid max-w-7xl gap-4 lg:gap-6 grid-cols-1 lg:grid-cols-2">
<Card className="h-fit"> <Card className="h-fit">
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base lg:text-lg">
<ListTodo className="h-5 w-5" /> <ListTodo className="h-4 w-4 lg:h-5 lg:w-5" />
Action Plan Generator Action Plan Generator
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Convert PRD into actionable implementation plan Convert PRD into actionable implementation plan
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-3 lg:space-y-4 p-4 lg:p-6 pt-0 lg:pt-0">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">AI Provider</label> <label className="text-xs lg:text-sm font-medium">AI Provider</label>
<div className="flex gap-2"> <div className="flex flex-wrap gap-1.5 lg:gap-2">
{(["qwen", "ollama", "zai"] as const).map((provider) => ( {(["qwen", "ollama", "zai"] as const).map((provider) => (
<Button <Button
key={provider} key={provider}
variant={selectedProvider === provider ? "default" : "outline"} variant={selectedProvider === provider ? "default" : "outline"}
size="sm" size="sm"
onClick={() => setSelectedProvider(provider)} onClick={() => setSelectedProvider(provider)}
className="capitalize" className="capitalize text-xs lg:text-sm h-8 lg:h-9 px-2.5 lg:px-3"
> >
{provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"} {provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"}
</Button> </Button>
@@ -156,11 +156,11 @@ export default function ActionPlanGenerator() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Model</label> <label className="text-xs lg:text-sm font-medium">Model</label>
<select <select
value={selectedModel} value={selectedModel}
onChange={(e) => setSelectedModel(selectedProvider, e.target.value)} onChange={(e) => setSelectedModel(selectedProvider, e.target.value)}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs lg:text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
> >
{models.map((model) => ( {models.map((model) => (
<option key={model} value={model}> <option key={model} value={model}>
@@ -171,36 +171,36 @@ export default function ActionPlanGenerator() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">PRD / Requirements</label> <label className="text-xs lg:text-sm font-medium">PRD / Requirements</label>
<Textarea <Textarea
placeholder="Paste your PRD or project requirements here..." placeholder="Paste your PRD or project requirements here..."
value={currentPrompt} value={currentPrompt}
onChange={(e) => setCurrentPrompt(e.target.value)} onChange={(e) => setCurrentPrompt(e.target.value)}
className="min-h-[200px] resize-y" className="min-h-[150px] lg:min-h-[200px] resize-y text-sm"
/> />
</div> </div>
{error && ( {error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive"> <div className="rounded-md bg-destructive/10 p-2.5 lg:p-3 text-xs lg:text-sm text-destructive">
{error} {error}
{!apiKeys[selectedProvider] && ( {!apiKeys[selectedProvider] && (
<div className="mt-2 flex items-center gap-2"> <div className="mt-1.5 lg:mt-2 flex items-center gap-2">
<Settings className="h-4 w-4" /> <Settings className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
<span className="text-xs">Configure API key in Settings</span> <span className="text-[10px] lg:text-xs">Configure API key in Settings</span>
</div> </div>
)} )}
</div> </div>
)} )}
<Button onClick={handleGenerate} disabled={isProcessing || !currentPrompt.trim()} className="w-full"> <Button onClick={handleGenerate} disabled={isProcessing || !currentPrompt.trim()} className="w-full h-9 lg:h-10 text-xs lg:text-sm">
{isProcessing ? ( {isProcessing ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4 animate-spin" />
Generating Action Plan... Generating...
</> </>
) : ( ) : (
<> <>
<ListTodo className="mr-2 h-4 w-4" /> <ListTodo className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4" />
Generate Action Plan Generate Action Plan
</> </>
)} )}
@@ -209,43 +209,43 @@ export default function ActionPlanGenerator() {
</Card> </Card>
<Card className={cn(!actionPlan && "opacity-50")}> <Card className={cn(!actionPlan && "opacity-50")}>
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between text-base lg:text-lg">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" /> <CheckCircle2 className="h-4 w-4 lg:h-5 lg:w-5 text-green-500" />
Action Plan Action Plan
</span> </span>
{actionPlan && ( {actionPlan && (
<Button variant="ghost" size="icon" onClick={handleCopy}> <Button variant="ghost" size="icon" onClick={handleCopy} className="h-8 w-8 lg:h-9 lg:w-9">
{copied ? ( {copied ? (
<CheckCircle2 className="h-4 w-4 text-green-500" /> <CheckCircle2 className="h-3.5 w-3.5 lg:h-4 lg:w-4 text-green-500" />
) : ( ) : (
<Copy className="h-4 w-4" /> <Copy className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)} )}
</Button> </Button>
)} )}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Task breakdown, frameworks, and architecture recommendations Task breakdown, frameworks, and architecture recommendations
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
{actionPlan ? ( {actionPlan ? (
<div className="space-y-4"> <div className="space-y-3 lg:space-y-4">
<div className="rounded-md border bg-primary/5 p-4"> <div className="rounded-md border bg-primary/5 p-3 lg:p-4">
<h4 className="mb-2 flex items-center gap-2 font-semibold"> <h4 className="mb-1.5 lg:mb-2 flex items-center gap-2 font-semibold text-xs lg:text-sm">
<Clock className="h-4 w-4" /> <Clock className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
Implementation Roadmap Implementation Roadmap
</h4> </h4>
<pre className="whitespace-pre-wrap text-sm">{actionPlan.rawContent}</pre> <pre className="whitespace-pre-wrap text-xs lg:text-sm">{actionPlan.rawContent}</pre>
</div> </div>
<div className="rounded-md border bg-muted/30 p-4"> <div className="rounded-md border bg-muted/30 p-3 lg:p-4">
<h4 className="mb-2 flex items-center gap-2 font-semibold"> <h4 className="mb-1.5 lg:mb-2 flex items-center gap-2 font-semibold text-xs lg:text-sm">
<AlertTriangle className="h-4 w-4" /> <AlertTriangle className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
Quick Notes Quick Notes
</h4> </h4>
<ul className="list-inside list-disc space-y-1 text-sm text-muted-foreground"> <ul className="list-inside list-disc space-y-0.5 lg:space-y-1 text-[10px] lg:text-xs text-muted-foreground">
<li>Review all task dependencies before starting</li> <li>Review all task dependencies before starting</li>
<li>Set up recommended framework architecture</li> <li>Set up recommended framework architecture</li>
<li>Follow best practices for security and performance</li> <li>Follow best practices for security and performance</li>
@@ -254,7 +254,7 @@ export default function ActionPlanGenerator() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="flex h-[300px] items-center justify-center text-center text-sm text-muted-foreground"> <div className="flex h-[200px] lg:h-[300px] items-center justify-center text-center text-xs lg:text-sm text-muted-foreground">
Action plan will appear here Action plan will appear here
</div> </div>
)} )}

View File

@@ -4,7 +4,6 @@ import useStore from "@/lib/store";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Clock, Trash2, RotateCcw } from "lucide-react"; import { Clock, Trash2, RotateCcw } from "lucide-react";
import { cn } from "@/lib/utils";
export default function HistoryPanel() { export default function HistoryPanel() {
const { history, setCurrentPrompt, clearHistory } = useStore(); const { history, setCurrentPrompt, clearHistory } = useStore();
@@ -22,11 +21,11 @@ export default function HistoryPanel() {
if (history.length === 0) { if (history.length === 0) {
return ( return (
<Card> <Card>
<CardContent className="flex h-[400px] items-center justify-center"> <CardContent className="flex h-[300px] lg:h-[400px] items-center justify-center p-4 lg:p-6">
<div className="text-center"> <div className="text-center">
<Clock className="mx-auto h-12 w-12 text-muted-foreground/50" /> <Clock className="mx-auto h-10 w-10 lg:h-12 lg:w-12 text-muted-foreground/50" />
<p className="mt-4 text-muted-foreground">No history yet</p> <p className="mt-3 lg:mt-4 text-sm lg:text-base text-muted-foreground">No history yet</p>
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-1.5 lg:mt-2 text-xs lg:text-sm text-muted-foreground">
Start enhancing prompts to see them here Start enhancing prompts to see them here
</p> </p>
</div> </div>
@@ -37,35 +36,35 @@ export default function HistoryPanel() {
return ( return (
<Card> <Card>
<CardHeader className="flex-row items-center justify-between"> <CardHeader className="flex-row items-center justify-between p-4 lg:p-6">
<div> <div>
<CardTitle>History</CardTitle> <CardTitle className="text-base lg:text-lg">History</CardTitle>
<CardDescription>{history.length} items</CardDescription> <CardDescription className="text-xs lg:text-sm">{history.length} items</CardDescription>
</div> </div>
<Button variant="outline" size="icon" onClick={handleClear}> <Button variant="outline" size="icon" onClick={handleClear} className="h-8 w-8 lg:h-9 lg:w-9">
<Trash2 className="h-4 w-4" /> <Trash2 className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
</Button> </Button>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-2 lg:space-y-3 p-4 lg:p-6 pt-0 lg:pt-0">
{history.map((item) => ( {history.map((item) => (
<div <div
key={item.id} key={item.id}
className="rounded-md border bg-muted/30 p-4 transition-colors hover:bg-muted/50" className="rounded-md border bg-muted/30 p-3 lg:p-4 transition-colors hover:bg-muted/50"
> >
<div className="mb-2 flex items-center justify-between"> <div className="mb-1.5 lg:mb-2 flex items-center justify-between gap-2">
<span className="text-xs text-muted-foreground"> <span className="text-[10px] lg:text-xs text-muted-foreground truncate">
{new Date(item.timestamp).toLocaleString()} {new Date(item.timestamp).toLocaleString()}
</span> </span>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="h-6 w-6" className="h-6 w-6 flex-shrink-0"
onClick={() => handleRestore(item.prompt)} onClick={() => handleRestore(item.prompt)}
> >
<RotateCcw className="h-3 w-3" /> <RotateCcw className="h-3 w-3" />
</Button> </Button>
</div> </div>
<p className="line-clamp-3 text-sm">{item.prompt}</p> <p className="line-clamp-3 text-xs lg:text-sm">{item.prompt}</p>
</div> </div>
))} ))}
</CardContent> </CardContent>

View File

@@ -140,28 +140,28 @@ export default function PRDGenerator() {
]; ];
return ( return (
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-2"> <div className="mx-auto grid max-w-7xl gap-4 lg:gap-6 grid-cols-1 lg:grid-cols-2">
<Card className="h-fit"> <Card className="h-fit">
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base lg:text-lg">
<FileText className="h-5 w-5" /> <FileText className="h-4 w-4 lg:h-5 lg:w-5" />
PRD Generator PRD Generator
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Generate comprehensive Product Requirements Document from your idea Generate comprehensive Product Requirements Document from your idea
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-3 lg:space-y-4 p-4 lg:p-6 pt-0 lg:pt-0">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">AI Provider</label> <label className="text-xs lg:text-sm font-medium">AI Provider</label>
<div className="flex gap-2"> <div className="flex flex-wrap gap-1.5 lg:gap-2">
{(["qwen", "ollama", "zai"] as const).map((provider) => ( {(["qwen", "ollama", "zai"] as const).map((provider) => (
<Button <Button
key={provider} key={provider}
variant={selectedProvider === provider ? "default" : "outline"} variant={selectedProvider === provider ? "default" : "outline"}
size="sm" size="sm"
onClick={() => setSelectedProvider(provider)} onClick={() => setSelectedProvider(provider)}
className="capitalize" className="capitalize text-xs lg:text-sm h-8 lg:h-9 px-2.5 lg:px-3"
> >
{provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"} {provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"}
</Button> </Button>
@@ -170,11 +170,11 @@ export default function PRDGenerator() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Model</label> <label className="text-xs lg:text-sm font-medium">Model</label>
<select <select
value={selectedModel} value={selectedModel}
onChange={(e) => setSelectedModel(selectedProvider, e.target.value)} onChange={(e) => setSelectedModel(selectedProvider, e.target.value)}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs lg:text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
> >
{models.map((model) => ( {models.map((model) => (
<option key={model} value={model}> <option key={model} value={model}>
@@ -185,36 +185,36 @@ export default function PRDGenerator() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Your Idea</label> <label className="text-xs lg:text-sm font-medium">Your Idea</label>
<Textarea <Textarea
placeholder="e.g., A task management app with real-time collaboration features" placeholder="e.g., A task management app with real-time collaboration features"
value={currentPrompt} value={currentPrompt}
onChange={(e) => setCurrentPrompt(e.target.value)} onChange={(e) => setCurrentPrompt(e.target.value)}
className="min-h-[200px] resize-y" className="min-h-[150px] lg:min-h-[200px] resize-y text-sm"
/> />
</div> </div>
{error && ( {error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive"> <div className="rounded-md bg-destructive/10 p-2.5 lg:p-3 text-xs lg:text-sm text-destructive">
{error} {error}
{!apiKeys[selectedProvider] && ( {!apiKeys[selectedProvider] && (
<div className="mt-2 flex items-center gap-2"> <div className="mt-1.5 lg:mt-2 flex items-center gap-2">
<Settings className="h-4 w-4" /> <Settings className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
<span className="text-xs">Configure API key in Settings</span> <span className="text-[10px] lg:text-xs">Configure API key in Settings</span>
</div> </div>
)} )}
</div> </div>
)} )}
<Button onClick={handleGenerate} disabled={isProcessing || !currentPrompt.trim()} className="w-full"> <Button onClick={handleGenerate} disabled={isProcessing || !currentPrompt.trim()} className="w-full h-9 lg:h-10 text-xs lg:text-sm">
{isProcessing ? ( {isProcessing ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4 animate-spin" />
Generating PRD... Generating PRD...
</> </>
) : ( ) : (
<> <>
<FileText className="mr-2 h-4 w-4" /> <FileText className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4" />
Generate PRD Generate PRD
</> </>
)} )}
@@ -223,52 +223,52 @@ export default function PRDGenerator() {
</Card> </Card>
<Card className={cn(!prd && "opacity-50")}> <Card className={cn(!prd && "opacity-50")}>
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between text-base lg:text-lg">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" /> <CheckCircle2 className="h-4 w-4 lg:h-5 lg:w-5 text-green-500" />
Generated PRD Generated PRD
</span> </span>
{prd && ( {prd && (
<Button variant="ghost" size="icon" onClick={handleCopy}> <Button variant="ghost" size="icon" onClick={handleCopy} className="h-8 w-8 lg:h-9 lg:w-9">
{copied ? ( {copied ? (
<CheckCircle2 className="h-4 w-4 text-green-500" /> <CheckCircle2 className="h-3.5 w-3.5 lg:h-4 lg:w-4 text-green-500" />
) : ( ) : (
<Copy className="h-4 w-4" /> <Copy className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)} )}
</Button> </Button>
)} )}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Structured requirements document ready for development Structured requirements document ready for development
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
{prd ? ( {prd ? (
<div className="space-y-3"> <div className="space-y-2 lg:space-y-3">
{sections.map((section) => ( {sections.map((section) => (
<div key={section.id} className="rounded-md border bg-muted/30"> <div key={section.id} className="rounded-md border bg-muted/30">
<button <button
onClick={() => toggleSection(section.id)} onClick={() => toggleSection(section.id)}
className="flex w-full items-center justify-between px-4 py-3 text-left font-medium transition-colors hover:bg-muted/50" className="flex w-full items-center justify-between px-3 lg:px-4 py-2.5 lg:py-3 text-left font-medium transition-colors hover:bg-muted/50 text-xs lg:text-sm"
> >
<span>{section.title}</span> <span>{section.title}</span>
{expandedSections.includes(section.id) ? ( {expandedSections.includes(section.id) ? (
<ChevronUp className="h-4 w-4" /> <ChevronUp className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
) : ( ) : (
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)} )}
</button> </button>
{expandedSections.includes(section.id) && ( {expandedSections.includes(section.id) && (
<div className="border-t bg-background px-4 py-3"> <div className="border-t bg-background px-3 lg:px-4 py-2.5 lg:py-3">
<pre className="whitespace-pre-wrap text-sm">{prd.overview}</pre> <pre className="whitespace-pre-wrap text-xs lg:text-sm">{prd.overview}</pre>
</div> </div>
)} )}
</div> </div>
))} ))}
</div> </div>
) : ( ) : (
<div className="flex h-[300px] items-center justify-center text-center text-sm text-muted-foreground"> <div className="flex h-[200px] lg:h-[300px] items-center justify-center text-center text-xs lg:text-sm text-muted-foreground">
PRD will appear here PRD will appear here
</div> </div>
)} )}

View File

@@ -117,21 +117,21 @@ export default function PromptEnhancer() {
}; };
return ( return (
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-2"> <div className="mx-auto grid max-w-7xl gap-4 lg:gap-6 grid-cols-1 lg:grid-cols-2">
<Card className="h-fit"> <Card className="h-fit">
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base lg:text-lg">
<Sparkles className="h-5 w-5" /> <Sparkles className="h-4 w-4 lg:h-5 lg:w-5" />
Input Prompt Input Prompt
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Enter your prompt and we'll enhance it for AI coding agents Enter your prompt and we'll enhance it for AI coding agents
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-3 lg:space-y-4 p-4 lg:p-6 pt-0 lg:pt-0">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">AI Provider</label> <label className="text-xs lg:text-sm font-medium">AI Provider</label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-1.5 lg:gap-2">
{(["qwen", "ollama", "zai"] as const).map((provider) => ( {(["qwen", "ollama", "zai"] as const).map((provider) => (
<Button <Button
key={provider} key={provider}
@@ -139,7 +139,7 @@ export default function PromptEnhancer() {
size="sm" size="sm"
onClick={() => setSelectedProvider(provider)} onClick={() => setSelectedProvider(provider)}
className={cn( className={cn(
"capitalize", "capitalize text-xs lg:text-sm h-8 lg:h-9 px-2.5 lg:px-3",
selectedProvider === provider && "bg-primary text-primary-foreground" selectedProvider === provider && "bg-primary text-primary-foreground"
)} )}
> >
@@ -150,11 +150,11 @@ export default function PromptEnhancer() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Model</label> <label className="text-xs lg:text-sm font-medium">Model</label>
<select <select
value={selectedModel} value={selectedModel}
onChange={(e) => setSelectedModel(selectedProvider, e.target.value)} onChange={(e) => setSelectedModel(selectedProvider, e.target.value)}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs lg:text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
> >
{models.map((model) => ( {models.map((model) => (
<option key={model} value={model}> <option key={model} value={model}>
@@ -165,77 +165,77 @@ export default function PromptEnhancer() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Your Prompt</label> <label className="text-xs lg:text-sm font-medium">Your Prompt</label>
<Textarea <Textarea
placeholder="e.g., Create a user authentication system with JWT tokens" placeholder="e.g., Create a user authentication system with JWT tokens"
value={currentPrompt} value={currentPrompt}
onChange={(e) => setCurrentPrompt(e.target.value)} onChange={(e) => setCurrentPrompt(e.target.value)}
className="min-h-[200px] resize-y" className="min-h-[150px] lg:min-h-[200px] resize-y text-sm"
/> />
</div> </div>
{error && ( {error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive"> <div className="rounded-md bg-destructive/10 p-2.5 lg:p-3 text-xs lg:text-sm text-destructive">
{error} {error}
{!apiKeys[selectedProvider] && ( {!apiKeys[selectedProvider] && (
<div className="mt-2 flex items-center gap-2"> <div className="mt-1.5 lg:mt-2 flex items-center gap-2">
<Settings className="h-4 w-4" /> <Settings className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
<span className="text-xs">Configure API key in Settings</span> <span className="text-[10px] lg:text-xs">Configure API key in Settings</span>
</div> </div>
)} )}
</div> </div>
)} )}
<div className="flex gap-2"> <div className="flex gap-2">
<Button onClick={handleEnhance} disabled={isProcessing || !currentPrompt.trim()} className="flex-1"> <Button onClick={handleEnhance} disabled={isProcessing || !currentPrompt.trim()} className="flex-1 h-9 lg:h-10 text-xs lg:text-sm">
{isProcessing ? ( {isProcessing ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4 animate-spin" />
Enhancing... Enhancing...
</> </>
) : ( ) : (
<> <>
<Sparkles className="mr-2 h-4 w-4" /> <Sparkles className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4" />
Enhance Prompt Enhance Prompt
</> </>
)} )}
</Button> </Button>
<Button variant="outline" onClick={handleClear} disabled={isProcessing}> <Button variant="outline" onClick={handleClear} disabled={isProcessing} className="h-9 lg:h-10 text-xs lg:text-sm px-3">
<RefreshCw className="mr-2 h-4 w-4" /> <RefreshCw className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4" />
Clear <span className="hidden sm:inline">Clear</span>
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<Card className={cn(!enhancedPrompt && "opacity-50")}> <Card className={cn(!enhancedPrompt && "opacity-50")}>
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between text-base lg:text-lg">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" /> <CheckCircle2 className="h-4 w-4 lg:h-5 lg:w-5 text-green-500" />
Enhanced Prompt Enhanced Prompt
</span> </span>
{enhancedPrompt && ( {enhancedPrompt && (
<Button variant="ghost" size="icon" onClick={handleCopy}> <Button variant="ghost" size="icon" onClick={handleCopy} className="h-8 w-8 lg:h-9 lg:w-9">
{copied ? ( {copied ? (
<CheckCircle2 className="h-4 w-4 text-green-500" /> <CheckCircle2 className="h-3.5 w-3.5 lg:h-4 lg:w-4 text-green-500" />
) : ( ) : (
<Copy className="h-4 w-4" /> <Copy className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)} )}
</Button> </Button>
)} )}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Professional prompt ready for coding agents Professional prompt ready for coding agents
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
{enhancedPrompt ? ( {enhancedPrompt ? (
<div className="rounded-md border bg-muted/50 p-4"> <div className="rounded-md border bg-muted/50 p-3 lg:p-4">
<pre className="whitespace-pre-wrap text-sm">{enhancedPrompt}</pre> <pre className="whitespace-pre-wrap text-xs lg:text-sm">{enhancedPrompt}</pre>
</div> </div>
) : ( ) : (
<div className="flex h-[200px] items-center justify-center text-center text-sm text-muted-foreground"> <div className="flex h-[150px] lg:h-[200px] items-center justify-center text-center text-xs lg:text-sm text-muted-foreground">
Enhanced prompt will appear here Enhanced prompt will appear here
</div> </div>
)} )}

View File

@@ -93,21 +93,21 @@ export default function SettingsPanel() {
}, []); }, []);
return ( return (
<div className="mx-auto max-w-3xl space-y-6"> <div className="mx-auto max-w-3xl space-y-4 lg:space-y-6">
<Card> <Card>
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base lg:text-lg">
<Key className="h-5 w-5" /> <Key className="h-4 w-4 lg:h-5 lg:w-5" />
API Configuration API Configuration
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Configure API keys for different AI providers Configure API keys for different AI providers
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-6"> <CardContent className="space-y-4 lg:space-y-6 p-4 lg:p-6 pt-0 lg:pt-0">
<div className="space-y-2"> <div className="space-y-2">
<label className="flex items-center gap-2 text-sm font-medium"> <label className="flex items-center gap-2 text-xs lg:text-sm font-medium">
<Server className="h-4 w-4" /> <Server className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
Qwen Code API Key Qwen Code API Key
</label> </label>
<div className="relative"> <div className="relative">
@@ -116,24 +116,24 @@ export default function SettingsPanel() {
placeholder="Enter your Qwen API key" placeholder="Enter your Qwen API key"
value={apiKeys.qwen || ""} value={apiKeys.qwen || ""}
onChange={(e) => handleApiKeyChange("qwen", e.target.value)} onChange={(e) => handleApiKeyChange("qwen", e.target.value)}
className="font-mono text-sm" className="font-mono text-xs lg:text-sm pr-10"
/> />
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
size="icon" size="icon"
className="absolute right-0 top-0 h-full" className="absolute right-0 top-0 h-full w-9 lg:w-10"
onClick={() => setShowApiKey((prev) => ({ ...prev, qwen: !prev.qwen }))} onClick={() => setShowApiKey((prev) => ({ ...prev, qwen: !prev.qwen }))}
> >
{showApiKey.qwen ? ( {showApiKey.qwen ? (
<EyeOff className="h-4 w-4" /> <EyeOff className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
) : ( ) : (
<Eye className="h-4 w-4" /> <Eye className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)} )}
</Button> </Button>
</div> </div>
<div className="flex items-center gap-4"> <div className="flex flex-col sm:flex-row sm:items-center gap-2 lg:gap-4">
<p className="text-xs text-muted-foreground flex-1"> <p className="text-[10px] lg:text-xs text-muted-foreground flex-1">
Get API key from{" "} Get API key from{" "}
<a <a
href="https://help.aliyun.com/zh/dashscope/" href="https://help.aliyun.com/zh/dashscope/"
@@ -147,7 +147,7 @@ export default function SettingsPanel() {
<Button <Button
variant={qwenTokens ? "secondary" : "outline"} variant={qwenTokens ? "secondary" : "outline"}
size="sm" size="sm"
className="h-8" className="h-7 lg:h-8 text-[10px] lg:text-xs w-full sm:w-auto"
onClick={handleQwenAuth} onClick={handleQwenAuth}
disabled={isAuthLoading} disabled={isAuthLoading}
> >
@@ -159,15 +159,15 @@ export default function SettingsPanel() {
</Button> </Button>
</div> </div>
{qwenTokens && ( {qwenTokens && (
<p className="text-[10px] text-green-600 dark:text-green-400 font-medium"> <p className="text-[9px] lg:text-[10px] text-green-600 dark:text-green-400 font-medium">
Authenticated via OAuth (Expires: {new Date(qwenTokens.expiresAt || 0).toLocaleString()}) Authenticated via OAuth (Expires: {new Date(qwenTokens.expiresAt || 0).toLocaleString()})
</p> </p>
)} )}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="flex items-center gap-2 text-sm font-medium"> <label className="flex items-center gap-2 text-xs lg:text-sm font-medium">
<Server className="h-4 w-4" /> <Server className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
Ollama Cloud API Key Ollama Cloud API Key
</label> </label>
<div className="relative"> <div className="relative">
@@ -176,23 +176,23 @@ export default function SettingsPanel() {
placeholder="Enter your Ollama API key" placeholder="Enter your Ollama API key"
value={apiKeys.ollama || ""} value={apiKeys.ollama || ""}
onChange={(e) => handleApiKeyChange("ollama", e.target.value)} onChange={(e) => handleApiKeyChange("ollama", e.target.value)}
className="font-mono text-sm" className="font-mono text-xs lg:text-sm pr-10"
/> />
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
size="icon" size="icon"
className="absolute right-0 top-0 h-full" className="absolute right-0 top-0 h-full w-9 lg:w-10"
onClick={() => setShowApiKey((prev) => ({ ...prev, ollama: !prev.ollama }))} onClick={() => setShowApiKey((prev) => ({ ...prev, ollama: !prev.ollama }))}
> >
{showApiKey.ollama ? ( {showApiKey.ollama ? (
<EyeOff className="h-4 w-4" /> <EyeOff className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
) : ( ) : (
<Eye className="h-4 w-4" /> <Eye className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)} )}
</Button> </Button>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-[10px] lg:text-xs text-muted-foreground">
Get API key from{" "} Get API key from{" "}
<a <a
href="https://ollama.com/cloud" href="https://ollama.com/cloud"
@@ -206,8 +206,8 @@ export default function SettingsPanel() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="flex items-center gap-2 text-sm font-medium"> <label className="flex items-center gap-2 text-xs lg:text-sm font-medium">
<Server className="h-4 w-4" /> <Server className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
Z.AI Plan API Key Z.AI Plan API Key
</label> </label>
<div className="relative"> <div className="relative">
@@ -216,23 +216,23 @@ export default function SettingsPanel() {
placeholder="Enter your Z.AI API key" placeholder="Enter your Z.AI API key"
value={apiKeys.zai || ""} value={apiKeys.zai || ""}
onChange={(e) => handleApiKeyChange("zai", e.target.value)} onChange={(e) => handleApiKeyChange("zai", e.target.value)}
className="font-mono text-sm" className="font-mono text-xs lg:text-sm pr-10"
/> />
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
size="icon" size="icon"
className="absolute right-0 top-0 h-full" className="absolute right-0 top-0 h-full w-9 lg:w-10"
onClick={() => setShowApiKey((prev) => ({ ...prev, zai: !prev.zai }))} onClick={() => setShowApiKey((prev) => ({ ...prev, zai: !prev.zai }))}
> >
{showApiKey.zai ? ( {showApiKey.zai ? (
<EyeOff className="h-4 w-4" /> <EyeOff className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
) : ( ) : (
<Eye className="h-4 w-4" /> <Eye className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)} )}
</Button> </Button>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-[10px] lg:text-xs text-muted-foreground">
Get API key from{" "} Get API key from{" "}
<a <a
href="https://docs.z.ai" href="https://docs.z.ai"
@@ -245,45 +245,44 @@ export default function SettingsPanel() {
</p> </p>
</div> </div>
<Button onClick={handleSave} className="w-full"> <Button onClick={handleSave} className="w-full h-9 lg:h-10 text-xs lg:text-sm">
<Save className="mr-2 h-4 w-4" /> <Save className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4" />
Save API Keys Save API Keys
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card>
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle>Default Provider</CardTitle> <CardTitle className="text-base lg:text-lg">Default Provider</CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Select your preferred AI provider Select your preferred AI provider
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-3 lg:space-y-4 p-4 lg:p-6 pt-0 lg:pt-0">
<div className="grid gap-3"> <div className="grid gap-2 lg:gap-3">
{(["qwen", "ollama", "zai"] as const).map((provider) => ( {(["qwen", "ollama", "zai"] as const).map((provider) => (
<button <button
key={provider} key={provider}
onClick={() => setSelectedProvider(provider)} onClick={() => setSelectedProvider(provider)}
className={`flex items-center gap-3 rounded-lg border p-4 text-left transition-colors hover:bg-muted/50 ${ className={`flex items-center gap-2 lg:gap-3 rounded-lg border p-3 lg:p-4 text-left transition-colors hover:bg-muted/50 ${selectedProvider === provider
selectedProvider === provider
? "border-primary bg-primary/5" ? "border-primary bg-primary/5"
: "border-border" : "border-border"
}`} }`}
> >
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-primary/10"> <div className="flex h-8 w-8 lg:h-10 lg:w-10 items-center justify-center rounded-md bg-primary/10">
<Server className="h-5 w-5 text-primary" /> <Server className="h-4 w-4 lg:h-5 lg:w-5 text-primary" />
</div> </div>
<div className="flex-1"> <div className="flex-1 min-w-0">
<h3 className="font-medium capitalize">{provider}</h3> <h3 className="font-medium capitalize text-sm lg:text-base">{provider}</h3>
<p className="text-sm text-muted-foreground"> <p className="text-[10px] lg:text-sm text-muted-foreground truncate">
{provider === "qwen" && "Alibaba DashScope API"} {provider === "qwen" && "Alibaba DashScope API"}
{provider === "ollama" && "Ollama Cloud API"} {provider === "ollama" && "Ollama Cloud API"}
{provider === "zai" && "Z.AI Plan API"} {provider === "zai" && "Z.AI Plan API"}
</p> </p>
</div> </div>
{selectedProvider === provider && ( {selectedProvider === provider && (
<div className="h-2 w-2 rounded-full bg-primary" /> <div className="h-2 w-2 rounded-full bg-primary flex-shrink-0" />
)} )}
</button> </button>
))} ))}
@@ -292,15 +291,15 @@ export default function SettingsPanel() {
</Card> </Card>
<Card> <Card>
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle>Data Privacy</CardTitle> <CardTitle className="text-base lg:text-lg">Data Privacy</CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Your data handling preferences Your data handling preferences
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
<div className="rounded-md border bg-muted/30 p-4"> <div className="rounded-md border bg-muted/30 p-3 lg:p-4">
<p className="text-sm"> <p className="text-xs lg:text-sm">
All API keys are stored locally in your browser. Your prompts are sent directly to the selected AI provider and are not stored by PromptArch. All API keys are stored locally in your browser. Your prompts are sent directly to the selected AI provider and are not stored by PromptArch.
</p> </p>
</div> </div>

View File

@@ -1,8 +1,9 @@
"use client"; "use client";
import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import useStore from "@/lib/store"; import useStore from "@/lib/store";
import { Sparkles, FileText, ListTodo, Palette, History, Settings, Github } from "lucide-react"; import { Sparkles, FileText, ListTodo, Palette, History, Settings, Github, Menu, X } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export type View = "enhance" | "prd" | "action" | "uxdesigner" | "history" | "settings"; export type View = "enhance" | "prd" | "action" | "uxdesigner" | "history" | "settings";
@@ -14,87 +15,137 @@ interface SidebarProps {
export default function Sidebar({ currentView, onViewChange }: SidebarProps) { export default function Sidebar({ currentView, onViewChange }: SidebarProps) {
const history = useStore((state) => state.history); const history = useStore((state) => state.history);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const menuItems = [ const menuItems = [
{ id: "enhance" as View, label: "Prompt Enhancer", icon: Sparkles }, { id: "enhance" as View, label: "Prompt Enhancer", icon: Sparkles },
{ id: "prd" as View, label: "PRD Generator", icon: FileText }, { id: "prd" as View, label: "PRD Generator", icon: FileText },
{ id: "action" as View, label: "Action Plan", icon: ListTodo }, { id: "action" as View, label: "Action Plan", icon: ListTodo },
{ id: "uxdesigner" as View, label: "UX Designer Prompt", icon: Palette }, { id: "uxdesigner" as View, label: "UX Designer", icon: Palette },
{ id: "history" as View, label: "History", icon: History, count: history.length }, { id: "history" as View, label: "History", icon: History, count: history.length },
{ id: "settings" as View, label: "Settings", icon: Settings }, { id: "settings" as View, label: "Settings", icon: Settings },
]; ];
return ( const handleViewChange = (view: View) => {
<aside className="flex h-screen w-64 flex-col border-r bg-card"> onViewChange(view);
<div className="border-b p-6"> setIsMobileMenuOpen(false);
};
const SidebarContent = () => (
<>
<div className="border-b p-4 lg:p-6">
<a href="https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer" target="_blank" rel="noopener noreferrer" className="block"> <a href="https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer" target="_blank" rel="noopener noreferrer" className="block">
<h1 className="flex items-center gap-2 text-xl font-bold hover:opacity-80 transition-opacity"> <h1 className="flex items-center gap-2 text-lg lg:text-xl font-bold hover:opacity-80 transition-opacity">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground"> <div className="flex h-7 w-7 lg:h-8 lg:w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm lg:text-base">
PA PA
</div> </div>
PromptArch PromptArch
</h1> </h1>
</a> </a>
<a href="https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer" target="_blank" rel="noopener noreferrer" className="mt-3 flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs text-primary hover:bg-primary/10 transition-colors"> <a href="https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer" target="_blank" rel="noopener noreferrer" className="mt-2 lg:mt-3 flex items-center gap-1.5 rounded-md px-2 lg:px-3 py-1 lg:py-1.5 text-xs text-primary hover:bg-primary/10 transition-colors">
<Github className="h-3.5 w-3.5" /> <Github className="h-3 w-3 lg:h-3.5 lg:w-3.5" />
<span>View on GitHub</span> <span>View on GitHub</span>
</a> </a>
<p className="mt-2 text-xs text-muted-foreground"> <p className="mt-1 lg:mt-2 text-[10px] lg:text-xs text-muted-foreground">
Forked from <a href="https://github.com/ClavixDev/Clavix" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Clavix</a> Forked from <a href="https://github.com/ClavixDev/Clavix" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Clavix</a>
</p> </p>
</div> </div>
<nav className="flex-1 space-y-1 p-4"> <nav className="flex-1 space-y-1 p-3 lg:p-4 overflow-y-auto">
{menuItems.map((item) => ( {menuItems.map((item) => (
<Button <Button
key={item.id} key={item.id}
variant={currentView === item.id ? "default" : "ghost"} variant={currentView === item.id ? "default" : "ghost"}
className={cn( className={cn(
"w-full justify-start gap-2", "w-full justify-start gap-2 h-9 lg:h-10 text-sm",
currentView === item.id && "bg-primary text-primary-foreground" currentView === item.id && "bg-primary text-primary-foreground"
)} )}
onClick={() => onViewChange(item.id)} onClick={() => handleViewChange(item.id)}
> >
<item.icon className="h-4 w-4" /> <item.icon className="h-4 w-4" />
<span className="flex-1 text-left">{item.label}</span> <span className="flex-1 text-left truncate">{item.label}</span>
{item.count !== undefined && item.count > 0 && ( {item.count !== undefined && item.count > 0 && (
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary-foreground text-xs font-medium"> <span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary-foreground text-xs font-medium text-primary">
{item.count} {item.count}
</span> </span>
)} )}
</Button> </Button>
))} ))}
<div className="mt-8 p-3 text-[10px] leading-relaxed text-muted-foreground border-t border-border/50 pt-4"> <div className="mt-6 lg:mt-8 p-2 lg:p-3 text-[9px] lg:text-[10px] leading-relaxed text-muted-foreground border-t border-border/50 pt-3 lg:pt-4">
<p className="font-semibold text-foreground mb-1">Developed by Roman | RyzenAdvanced</p> <p className="font-semibold text-foreground mb-1">Developed by Roman | RyzenAdvanced</p>
<div className="space-y-1"> <div className="space-y-0.5 lg:space-y-1">
<p> <p>
GitHub: <a href="https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">roman-ryzenadvanced</a> GitHub: <a href="https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">roman-ryzenadvanced</a>
</p> </p>
<p> <p>
Telegram: <a href="https://t.me/VibeCodePrompterSystem" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">@VibeCodePrompterSystem</a> Telegram: <a href="https://t.me/VibeCodePrompterSystem" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">@VibeCodePrompterSystem</a>
</p> </p>
<p className="mt-2 text-[9px] opacity-80"> <p className="mt-1 lg:mt-2 text-[8px] lg:text-[9px] opacity-80">
100% Developed using GLM 4.7 model on TRAE.AI IDE. 100% Developed using GLM 4.7 model on TRAE.AI IDE.
</p> </p>
<p className="text-[9px] opacity-80"> <p className="text-[8px] lg:text-[9px] opacity-80">
Model Info: <a href="https://z.ai/subscribe?ic=R0K78RJKNW" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Learn here</a> Model Info: <a href="https://z.ai/subscribe?ic=R0K78RJKNW" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Learn here</a>
</p> </p>
</div> </div>
</div> </div>
</nav> </nav>
<div className="border-t p-4"> <div className="border-t p-3 lg:p-4 hidden lg:block">
<div className="rounded-md bg-muted/50 p-3 text-xs text-muted-foreground"> <div className="rounded-md bg-muted/50 p-2 lg:p-3 text-[10px] lg:text-xs text-muted-foreground">
<p className="font-medium text-foreground">Quick Tips</p> <p className="font-medium text-foreground">Quick Tips</p>
<ul className="mt-2 space-y-1"> <ul className="mt-1.5 lg:mt-2 space-y-0.5 lg:space-y-1">
<li> Use different providers for best results</li> <li> Use different providers for best results</li>
<li> Copy enhanced prompts to your AI agent</li> <li> Copy enhanced prompts to your AI agent</li>
<li> PRDs generate better action plans</li> <li> PRDs generate better action plans</li>
<li> UX Designer Prompt for design tasks</li>
</ul> </ul>
</div> </div>
</div> </div>
</>
);
return (
<>
{/* Mobile Header */}
<div className="lg:hidden fixed top-0 left-0 right-0 z-50 flex items-center justify-between border-b bg-card px-4 py-3">
<a href="https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer" target="_blank" rel="noopener noreferrer" className="flex items-center gap-2">
<div className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold">
PA
</div>
<span className="font-bold text-lg">PromptArch</span>
</a>
<Button
variant="ghost"
size="icon"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="h-9 w-9"
>
{isMobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</Button>
</div>
{/* Mobile Menu Overlay */}
{isMobileMenuOpen && (
<div
className="lg:hidden fixed inset-0 z-40 bg-black/50 backdrop-blur-sm"
onClick={() => setIsMobileMenuOpen(false)}
/>
)}
{/* Mobile Slide-out Menu */}
<aside
className={cn(
"lg:hidden fixed top-0 left-0 z-50 flex h-full w-72 max-w-[80vw] flex-col border-r bg-card transition-transform duration-300 ease-in-out",
isMobileMenuOpen ? "translate-x-0" : "-translate-x-full"
)}
>
<SidebarContent />
</aside> </aside>
{/* Desktop Sidebar */}
<aside className="hidden lg:flex h-screen w-64 flex-col border-r bg-card flex-shrink-0">
<SidebarContent />
</aside>
</>
); );
} }

View File

@@ -119,21 +119,21 @@ export default function UXDesignerPrompt() {
}; };
return ( return (
<div className="mx-auto grid max-w-7xl gap-6 lg:grid-cols-2"> <div className="mx-auto grid max-w-7xl gap-4 lg:gap-6 grid-cols-1 lg:grid-cols-2">
<Card className="h-fit"> <Card className="h-fit">
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base lg:text-lg">
<Palette className="h-5 w-5" /> <Palette className="h-4 w-4 lg:h-5 lg:w-5" />
UX Designer Prompt UX Designer Prompt
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Describe your app idea and get the BEST EVER prompt for UX design Describe your app idea and get the BEST EVER prompt for UX design
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-3 lg:space-y-4 p-4 lg:p-6 pt-0 lg:pt-0">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">AI Provider</label> <label className="text-xs lg:text-sm font-medium">AI Provider</label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-1.5 lg:gap-2">
{(["ollama", "zai"] as const).map((provider) => ( {(["ollama", "zai"] as const).map((provider) => (
<Button <Button
key={provider} key={provider}
@@ -141,7 +141,7 @@ export default function UXDesignerPrompt() {
size="sm" size="sm"
onClick={() => setSelectedProvider(provider)} onClick={() => setSelectedProvider(provider)}
className={cn( className={cn(
"capitalize", "capitalize text-xs lg:text-sm h-8 lg:h-9 px-2.5 lg:px-3",
selectedProvider === provider && "bg-primary text-primary-foreground" selectedProvider === provider && "bg-primary text-primary-foreground"
)} )}
> >
@@ -152,11 +152,11 @@ export default function UXDesignerPrompt() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">Model</label> <label className="text-xs lg:text-sm font-medium">Model</label>
<select <select
value={selectedModel} value={selectedModel}
onChange={(e) => setSelectedModel(selectedProvider, e.target.value)} onChange={(e) => setSelectedModel(selectedProvider, e.target.value)}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs lg:text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
> >
{models.map((model) => ( {models.map((model) => (
<option key={model} value={model}> <option key={model} value={model}>
@@ -167,79 +167,81 @@ export default function UXDesignerPrompt() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium">App Description</label> <label className="text-xs lg:text-sm font-medium">App Description</label>
<Textarea <Textarea
placeholder="e.g., A fitness tracking app with workout plans, nutrition tracking, and social features for sharing progress with friends" placeholder="e.g., A fitness tracking app with workout plans, nutrition tracking, and social features for sharing progress with friends"
value={currentPrompt} value={currentPrompt}
onChange={(e) => setCurrentPrompt(e.target.value)} onChange={(e) => setCurrentPrompt(e.target.value)}
className="min-h-[200px] resize-y" className="min-h-[150px] lg:min-h-[200px] resize-y text-sm"
/> />
<p className="text-xs text-muted-foreground"> <p className="text-[10px] lg:text-xs text-muted-foreground">
Describe what kind of app you want, target users, key features, and any specific design preferences. Describe what kind of app you want, target users, key features, and any specific design preferences.
</p> </p>
</div> </div>
{error && ( {error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive"> <div className="rounded-md bg-destructive/10 p-2.5 lg:p-3 text-xs lg:text-sm text-destructive">
{error} {error}
{!apiKeys[selectedProvider] && ( {!apiKeys[selectedProvider] && (
<div className="mt-2 flex items-center gap-2"> <div className="mt-1.5 lg:mt-2 flex items-center gap-2">
<Settings className="h-4 w-4" /> <Settings className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
<span className="text-xs">Configure API key in Settings</span> <span className="text-[10px] lg:text-xs">Configure API key in Settings</span>
</div> </div>
)} )}
</div> </div>
)} )}
<div className="flex gap-2"> <div className="flex gap-2">
<Button onClick={handleGenerate} disabled={isProcessing || !currentPrompt.trim()} className="flex-1"> <Button onClick={handleGenerate} disabled={isProcessing || !currentPrompt.trim()} className="flex-1 h-9 lg:h-10 text-xs lg:text-sm">
{isProcessing ? ( {isProcessing ? (
<> <>
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> <Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4 animate-spin" />
Generating... Generating...
</> </>
) : ( ) : (
<> <>
<Palette className="mr-2 h-4 w-4" /> <Palette className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4" />
Generate UX Prompt Generate UX Prompt
</> </>
)} )}
</Button> </Button>
<Button variant="outline" onClick={handleClear} disabled={isProcessing}> <Button variant="outline" onClick={handleClear} disabled={isProcessing} className="h-9 lg:h-10 text-xs lg:text-sm px-3">
Clear <span className="hidden sm:inline">Clear</span>
<span className="sm:hidden">×</span>
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<Card className={cn(!generatedPrompt && "opacity-50")}> <Card className={cn(!generatedPrompt && "opacity-50")}>
<CardHeader> <CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between text-base lg:text-lg">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" /> <CheckCircle2 className="h-4 w-4 lg:h-5 lg:w-5 text-green-500" />
Best Ever UX Prompt <span className="hidden sm:inline">Best Ever UX Prompt</span>
<span className="sm:hidden">UX Prompt</span>
</span> </span>
{generatedPrompt && ( {generatedPrompt && (
<Button variant="ghost" size="icon" onClick={handleCopy}> <Button variant="ghost" size="icon" onClick={handleCopy} className="h-8 w-8 lg:h-9 lg:w-9">
{copied ? ( {copied ? (
<CheckCircle2 className="h-4 w-4 text-green-500" /> <CheckCircle2 className="h-3.5 w-3.5 lg:h-4 lg:w-4 text-green-500" />
) : ( ) : (
<Copy className="h-4 w-4" /> <Copy className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)} )}
</Button> </Button>
)} )}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs lg:text-sm">
Comprehensive UX design prompt ready for designers Comprehensive UX design prompt ready for designers
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
{generatedPrompt ? ( {generatedPrompt ? (
<div className="rounded-md border bg-muted/50 p-4"> <div className="rounded-md border bg-muted/50 p-3 lg:p-4 max-h-[350px] lg:max-h-[400px] overflow-y-auto">
<pre className="whitespace-pre-wrap text-sm">{generatedPrompt}</pre> <pre className="whitespace-pre-wrap text-xs lg:text-sm">{generatedPrompt}</pre>
</div> </div>
) : ( ) : (
<div className="flex h-[400px] items-center justify-center text-center text-sm text-muted-foreground"> <div className="flex h-[250px] lg:h-[400px] items-center justify-center text-center text-xs lg:text-sm text-muted-foreground px-4">
Your comprehensive UX designer prompt will appear here Your comprehensive UX designer prompt will appear here
</div> </div>
)} )}