Add full mobile responsive design with hamburger menu, responsive components, and mobile optimizations
This commit is contained in:
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function ActionPlanGenerator() {
|
|||||||
const loadAvailableModels = async () => {
|
const loadAvailableModels = async () => {
|
||||||
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
|
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
|
||||||
setAvailableModels(selectedProvider, fallbackModels);
|
setAvailableModels(selectedProvider, fallbackModels);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await modelAdapter.listModels(selectedProvider);
|
const result = await modelAdapter.listModels(selectedProvider);
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default function PRDGenerator() {
|
|||||||
const loadAvailableModels = async () => {
|
const loadAvailableModels = async () => {
|
||||||
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
|
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
|
||||||
setAvailableModels(selectedProvider, fallbackModels);
|
setAvailableModels(selectedProvider, fallbackModels);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await modelAdapter.listModels(selectedProvider);
|
const result = await modelAdapter.listModels(selectedProvider);
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default function PromptEnhancer() {
|
|||||||
const loadAvailableModels = async () => {
|
const loadAvailableModels = async () => {
|
||||||
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
|
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
|
||||||
setAvailableModels(selectedProvider, fallbackModels);
|
setAvailableModels(selectedProvider, fallbackModels);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await modelAdapter.listModels(selectedProvider);
|
const result = await modelAdapter.listModels(selectedProvider);
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default function SettingsPanel() {
|
|||||||
|
|
||||||
const handleApiKeyChange = (provider: string, value: string) => {
|
const handleApiKeyChange = (provider: string, value: string) => {
|
||||||
setApiKey(provider as "qwen" | "ollama" | "zai", value);
|
setApiKey(provider as "qwen" | "ollama" | "zai", value);
|
||||||
|
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case "qwen":
|
case "qwen":
|
||||||
modelAdapter.updateQwenApiKey(value);
|
modelAdapter.updateQwenApiKey(value);
|
||||||
@@ -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,27 +147,27 @@ 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}
|
||||||
>
|
>
|
||||||
{isAuthLoading
|
{isAuthLoading
|
||||||
? "Signing in..."
|
? "Signing in..."
|
||||||
: qwenTokens
|
: qwenTokens
|
||||||
? "Logout from Qwen"
|
? "Logout from Qwen"
|
||||||
: "Login with Qwen (OAuth)"}
|
: "Login with Qwen (OAuth)"}
|
||||||
</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
</aside>
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
{/* Desktop Sidebar */}
|
||||||
|
<aside className="hidden lg:flex h-screen w-64 flex-col border-r bg-card flex-shrink-0">
|
||||||
|
<SidebarContent />
|
||||||
|
</aside>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default function UXDesignerPrompt() {
|
|||||||
const loadAvailableModels = async () => {
|
const loadAvailableModels = async () => {
|
||||||
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
|
const fallbackModels = modelAdapter.getAvailableModels(selectedProvider);
|
||||||
setAvailableModels(selectedProvider, fallbackModels);
|
setAvailableModels(selectedProvider, fallbackModels);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await modelAdapter.listModels(selectedProvider);
|
const result = await modelAdapter.listModels(selectedProvider);
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user