Compare commits
1 Commits
37612bbac2
...
remove-qwe
60
app/page.tsx
60
app/page.tsx
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import type { View } from "@/components/Sidebar";
|
||||
import PromptEnhancer from "@/components/PromptEnhancer";
|
||||
@@ -8,59 +8,9 @@ import PRDGenerator from "@/components/PRDGenerator";
|
||||
import ActionPlanGenerator from "@/components/ActionPlanGenerator";
|
||||
import HistoryPanel from "@/components/HistoryPanel";
|
||||
import SettingsPanel from "@/components/SettingsPanel";
|
||||
import useStore from "@/lib/store";
|
||||
import modelAdapter from "@/lib/services/adapter-instance";
|
||||
|
||||
export default function Home() {
|
||||
const [currentView, setCurrentView] = useState<View>("enhance");
|
||||
const { setQwenTokens, setApiKey } = useStore();
|
||||
|
||||
useEffect(() => {
|
||||
// Handle OAuth callback
|
||||
if (typeof window !== "undefined") {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get("code");
|
||||
|
||||
if (code) {
|
||||
// In a real app, you would exchange the code for tokens here
|
||||
// Since we don't have a backend or real client secret, we'll simulate it
|
||||
console.log("OAuth code received:", code);
|
||||
|
||||
// Mock token exchange
|
||||
const mockAccessToken = "mock_access_token_" + Math.random().toString(36).substr(2, 9);
|
||||
const tokens = {
|
||||
accessToken: mockAccessToken,
|
||||
expiresAt: Date.now() + 3600 * 1000, // 1 hour
|
||||
};
|
||||
|
||||
setQwenTokens(tokens);
|
||||
modelAdapter.setQwenOAuthTokens(tokens.accessToken, undefined, 3600);
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem("promptarch-qwen-tokens", JSON.stringify(tokens));
|
||||
|
||||
// Clear the code from URL
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
|
||||
// Switch to settings to show success (optional)
|
||||
setCurrentView("settings");
|
||||
}
|
||||
|
||||
// Load tokens from localStorage on init
|
||||
const savedTokens = localStorage.getItem("promptarch-qwen-tokens");
|
||||
if (savedTokens) {
|
||||
try {
|
||||
const tokens = JSON.parse(savedTokens);
|
||||
if (tokens.expiresAt > Date.now()) {
|
||||
setQwenTokens(tokens);
|
||||
modelAdapter.setQwenOAuthTokens(tokens.accessToken, tokens.refreshToken, (tokens.expiresAt - Date.now()) / 1000);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load Qwen tokens:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderContent = () => {
|
||||
switch (currentView) {
|
||||
@@ -80,12 +30,10 @@ export default function Home() {
|
||||
};
|
||||
|
||||
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 h-screen bg-background">
|
||||
<Sidebar currentView={currentView} onViewChange={setCurrentView} />
|
||||
<main className="flex-1 overflow-auto p-8">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
{renderContent()}
|
||||
</div>
|
||||
<main className="flex-1 overflow-auto">
|
||||
<div className="container mx-auto p-6">{renderContent()}</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -40,7 +40,6 @@ export default function ActionPlanGenerator() {
|
||||
if (saved) {
|
||||
try {
|
||||
const keys = JSON.parse(saved);
|
||||
if (keys.qwen) modelAdapter.updateQwenApiKey(keys.qwen);
|
||||
if (keys.ollama) modelAdapter.updateOllamaApiKey(keys.ollama);
|
||||
if (keys.zai) modelAdapter.updateZaiApiKey(keys.zai);
|
||||
} catch (e) {
|
||||
@@ -133,7 +132,7 @@ export default function ActionPlanGenerator() {
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">AI Provider</label>
|
||||
<div className="flex gap-2">
|
||||
{(["qwen", "ollama", "zai"] as const).map((provider) => (
|
||||
{(["ollama", "zai"] as const).map((provider) => (
|
||||
<Button
|
||||
key={provider}
|
||||
variant={selectedProvider === provider ? "default" : "outline"}
|
||||
@@ -141,7 +140,7 @@ export default function ActionPlanGenerator() {
|
||||
onClick={() => setSelectedProvider(provider)}
|
||||
className="capitalize"
|
||||
>
|
||||
{provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"}
|
||||
{provider === "ollama" ? "Ollama" : "Z.AI"}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,6 @@ export default function PRDGenerator() {
|
||||
if (saved) {
|
||||
try {
|
||||
const keys = JSON.parse(saved);
|
||||
if (keys.qwen) modelAdapter.updateQwenApiKey(keys.qwen);
|
||||
if (keys.ollama) modelAdapter.updateOllamaApiKey(keys.ollama);
|
||||
if (keys.zai) modelAdapter.updateZaiApiKey(keys.zai);
|
||||
} catch (e) {
|
||||
@@ -147,7 +146,7 @@ export default function PRDGenerator() {
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">AI Provider</label>
|
||||
<div className="flex gap-2">
|
||||
{(["qwen", "ollama", "zai"] as const).map((provider) => (
|
||||
{(["ollama", "zai"] as const).map((provider) => (
|
||||
<Button
|
||||
key={provider}
|
||||
variant={selectedProvider === provider ? "default" : "outline"}
|
||||
@@ -155,7 +154,7 @@ export default function PRDGenerator() {
|
||||
onClick={() => setSelectedProvider(provider)}
|
||||
className="capitalize"
|
||||
>
|
||||
{provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"}
|
||||
{provider === "ollama" ? "Ollama" : "Z.AI"}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,6 @@ export default function PromptEnhancer() {
|
||||
if (saved) {
|
||||
try {
|
||||
const keys = JSON.parse(saved);
|
||||
if (keys.qwen) modelAdapter.updateQwenApiKey(keys.qwen);
|
||||
if (keys.ollama) modelAdapter.updateOllamaApiKey(keys.ollama);
|
||||
if (keys.zai) modelAdapter.updateZaiApiKey(keys.zai);
|
||||
} catch (e) {
|
||||
@@ -124,7 +123,7 @@ export default function PromptEnhancer() {
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">AI Provider</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(["qwen", "ollama", "zai"] as const).map((provider) => (
|
||||
{(["ollama", "zai"] as const).map((provider) => (
|
||||
<Button
|
||||
key={provider}
|
||||
variant={selectedProvider === provider ? "default" : "outline"}
|
||||
@@ -135,7 +134,7 @@ export default function PromptEnhancer() {
|
||||
selectedProvider === provider && "bg-primary text-primary-foreground"
|
||||
)}
|
||||
>
|
||||
{provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"}
|
||||
{provider === "ollama" ? "Ollama" : "Z.AI"}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import useStore from "@/lib/store";
|
||||
import modelAdapter from "@/lib/services/adapter-instance";
|
||||
import { Save, Key, Server, Eye, EyeOff } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function SettingsPanel() {
|
||||
const { apiKeys, setApiKey, selectedProvider, setSelectedProvider, qwenTokens, setQwenTokens } = useStore();
|
||||
const {
|
||||
apiKeys,
|
||||
setApiKey,
|
||||
selectedProvider,
|
||||
setSelectedProvider
|
||||
} = useStore();
|
||||
|
||||
const [showApiKey, setShowApiKey] = useState<Record<string, boolean>>({});
|
||||
|
||||
const handleSave = () => {
|
||||
@@ -27,10 +31,6 @@ export default function SettingsPanel() {
|
||||
if (saved) {
|
||||
try {
|
||||
const keys = JSON.parse(saved);
|
||||
if (keys.qwen) {
|
||||
setApiKey("qwen", keys.qwen);
|
||||
modelAdapter.updateQwenApiKey(keys.qwen);
|
||||
}
|
||||
if (keys.ollama) {
|
||||
setApiKey("ollama", keys.ollama);
|
||||
modelAdapter.updateOllamaApiKey(keys.ollama);
|
||||
@@ -47,12 +47,9 @@ export default function SettingsPanel() {
|
||||
};
|
||||
|
||||
const handleApiKeyChange = (provider: string, value: string) => {
|
||||
setApiKey(provider as "qwen" | "ollama" | "zai", value);
|
||||
setApiKey(provider as "ollama" | "zai", value);
|
||||
|
||||
switch (provider) {
|
||||
case "qwen":
|
||||
modelAdapter.updateQwenApiKey(value);
|
||||
break;
|
||||
case "ollama":
|
||||
modelAdapter.updateOllamaApiKey(value);
|
||||
break;
|
||||
@@ -79,69 +76,6 @@ export default function SettingsPanel() {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-2 text-sm font-medium">
|
||||
<Server className="h-4 w-4" />
|
||||
Qwen Code API Key
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showApiKey.qwen ? "text" : "password"}
|
||||
placeholder="Enter your Qwen API key"
|
||||
value={apiKeys.qwen || ""}
|
||||
onChange={(e) => handleApiKeyChange("qwen", e.target.value)}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="absolute right-0 top-0 h-full"
|
||||
onClick={() => setShowApiKey((prev) => ({ ...prev, qwen: !prev.qwen }))}
|
||||
>
|
||||
{showApiKey.qwen ? (
|
||||
<EyeOff className="h-4 w-4" />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<p className="text-xs text-muted-foreground flex-1">
|
||||
Get API key from{" "}
|
||||
<a
|
||||
href="https://help.aliyun.com/zh/dashscope/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
Alibaba DashScope
|
||||
</a>
|
||||
</p>
|
||||
<Button
|
||||
variant={qwenTokens ? "secondary" : "outline"}
|
||||
size="sm"
|
||||
className="h-8"
|
||||
onClick={() => {
|
||||
if (qwenTokens) {
|
||||
setQwenTokens(undefined as any);
|
||||
localStorage.removeItem("promptarch-qwen-tokens");
|
||||
modelAdapter.updateQwenApiKey(apiKeys.qwen || "");
|
||||
} else {
|
||||
window.location.href = modelAdapter.getQwenAuthUrl();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{qwenTokens ? "Logout from Qwen" : "Login with Qwen (OAuth)"}
|
||||
</Button>
|
||||
</div>
|
||||
{qwenTokens && (
|
||||
<p className="text-[10px] text-green-600 dark:text-green-400 font-medium">
|
||||
✓ Authenticated via OAuth (Expires: {new Date(qwenTokens.expiresAt || 0).toLocaleString()})
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-2 text-sm font-medium">
|
||||
<Server className="h-4 w-4" />
|
||||
@@ -238,7 +172,7 @@ export default function SettingsPanel() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid gap-3">
|
||||
{(["qwen", "ollama", "zai"] as const).map((provider) => (
|
||||
{(["ollama", "zai"] as const).map((provider) => (
|
||||
<button
|
||||
key={provider}
|
||||
onClick={() => setSelectedProvider(provider)}
|
||||
@@ -254,7 +188,6 @@ export default function SettingsPanel() {
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium capitalize">{provider}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{provider === "qwen" && "Alibaba DashScope API"}
|
||||
{provider === "ollama" && "Ollama Cloud API"}
|
||||
{provider === "zai" && "Z.AI Plan API"}
|
||||
</p>
|
||||
@@ -278,7 +211,7 @@ export default function SettingsPanel() {
|
||||
<CardContent>
|
||||
<div className="rounded-md border bg-muted/30 p-4">
|
||||
<p className="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 selected AI provider and are not stored by PromptArch.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
14
lib/store.ts
14
lib/store.ts
@@ -10,11 +10,6 @@ interface AppState {
|
||||
selectedModels: Record<ModelProvider, string>;
|
||||
availableModels: Record<ModelProvider, string[]>;
|
||||
apiKeys: Record<ModelProvider, string>;
|
||||
qwenTokens?: {
|
||||
accessToken: string;
|
||||
refreshToken?: string;
|
||||
expiresAt?: number;
|
||||
};
|
||||
isProcessing: boolean;
|
||||
error: string | null;
|
||||
history: {
|
||||
@@ -31,7 +26,6 @@ interface AppState {
|
||||
setSelectedModel: (provider: ModelProvider, model: string) => void;
|
||||
setAvailableModels: (provider: ModelProvider, models: string[]) => void;
|
||||
setApiKey: (provider: ModelProvider, key: string) => void;
|
||||
setQwenTokens: (tokens: { accessToken: string; refreshToken?: string; expiresAt?: number }) => void;
|
||||
setProcessing: (processing: boolean) => void;
|
||||
setError: (error: string | null) => void;
|
||||
addToHistory: (prompt: string) => void;
|
||||
@@ -44,26 +38,22 @@ const useStore = create<AppState>((set) => ({
|
||||
enhancedPrompt: null,
|
||||
prd: null,
|
||||
actionPlan: null,
|
||||
selectedProvider: "qwen",
|
||||
selectedProvider: "ollama",
|
||||
selectedModels: {
|
||||
qwen: "qwen-coder-plus",
|
||||
ollama: "gpt-oss:120b",
|
||||
zai: "glm-4.7",
|
||||
},
|
||||
availableModels: {
|
||||
qwen: ["qwen-coder-plus", "qwen-coder-turbo", "qwen-coder-lite"],
|
||||
ollama: ["gpt-oss:120b", "llama3.1", "gemma3", "deepseek-r1", "qwen3"],
|
||||
zai: ["glm-4.7", "glm-4.6", "glm-4.5", "glm-4.5-air", "glm-4-flash", "glm-4-flashx"],
|
||||
},
|
||||
apiKeys: {
|
||||
qwen: "",
|
||||
ollama: "",
|
||||
zai: "",
|
||||
},
|
||||
isProcessing: false,
|
||||
error: null,
|
||||
history: [],
|
||||
|
||||
setCurrentPrompt: (prompt) => set({ currentPrompt: prompt }),
|
||||
setEnhancedPrompt: (enhanced) => set({ enhancedPrompt: enhanced }),
|
||||
setPRD: (prd) => set({ prd }),
|
||||
@@ -81,7 +71,6 @@ const useStore = create<AppState>((set) => ({
|
||||
set((state) => ({
|
||||
apiKeys: { ...state.apiKeys, [provider]: key },
|
||||
})),
|
||||
setQwenTokens: (tokens) => set({ qwenTokens: tokens }),
|
||||
setProcessing: (processing) => set({ isProcessing: processing }),
|
||||
setError: (error) => set({ error }),
|
||||
addToHistory: (prompt) =>
|
||||
@@ -102,6 +91,7 @@ const useStore = create<AppState>((set) => ({
|
||||
enhancedPrompt: null,
|
||||
prd: null,
|
||||
actionPlan: null,
|
||||
isProcessing: false,
|
||||
error: null,
|
||||
}),
|
||||
}));
|
||||
|
||||
134
types/index.ts
134
types/index.ts
@@ -1,84 +1,8 @@
|
||||
export type ModelProvider = "qwen" | "ollama" | "zai";
|
||||
export type ModelProvider = "ollama" | "zai";
|
||||
|
||||
export interface ModelConfig {
|
||||
provider: ModelProvider;
|
||||
model: string;
|
||||
apiKey?: string;
|
||||
endpoint?: string;
|
||||
}
|
||||
|
||||
export interface PromptEnhancement {
|
||||
original: string;
|
||||
enhanced: string;
|
||||
quality: number;
|
||||
intent: string;
|
||||
patterns: string[];
|
||||
}
|
||||
|
||||
export interface PRD {
|
||||
id: string;
|
||||
title: string;
|
||||
overview: string;
|
||||
objectives: string[];
|
||||
userPersonas: UserPersona[];
|
||||
functionalRequirements: Requirement[];
|
||||
nonFunctionalRequirements: Requirement[];
|
||||
technicalArchitecture: string;
|
||||
successMetrics: string[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface UserPersona {
|
||||
name: string;
|
||||
description: string;
|
||||
goals: string[];
|
||||
painPoints: string[];
|
||||
}
|
||||
|
||||
export interface Requirement {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
priority: "high" | "medium" | "low";
|
||||
status: "pending" | "in-progress" | "completed";
|
||||
dependencies?: string[];
|
||||
}
|
||||
|
||||
export interface ActionPlan {
|
||||
id: string;
|
||||
prdId: string;
|
||||
tasks: Task[];
|
||||
frameworks: FrameworkRecommendation[];
|
||||
architecture: ArchitectureGuideline;
|
||||
estimatedDuration: string;
|
||||
createdAt: Date;
|
||||
rawContent?: string;
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
priority: "high" | "medium" | "low";
|
||||
estimatedHours: number;
|
||||
dependencies: string[];
|
||||
status: "pending" | "in-progress" | "completed";
|
||||
assignee?: string;
|
||||
}
|
||||
|
||||
export interface FrameworkRecommendation {
|
||||
category: string;
|
||||
recommendation: string;
|
||||
rationale: string;
|
||||
alternatives: string[];
|
||||
}
|
||||
|
||||
export interface ArchitectureGuideline {
|
||||
pattern: string;
|
||||
structure: string;
|
||||
technologies: string[];
|
||||
bestPractices: string[];
|
||||
export interface ChatMessage {
|
||||
role: "system" | "user" | "assistant";
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface APIResponse<T> {
|
||||
@@ -87,7 +11,51 @@ export interface APIResponse<T> {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
role: "system" | "user" | "assistant";
|
||||
content: string;
|
||||
export interface PromptEnhancement {
|
||||
id: string;
|
||||
originalPrompt: string;
|
||||
enhancedPrompt: string;
|
||||
provider: ModelProvider;
|
||||
model: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface PRD {
|
||||
id: string;
|
||||
title: string;
|
||||
overview: string;
|
||||
objectives: string[];
|
||||
userPersonas: string[];
|
||||
functionalRequirements: string[];
|
||||
nonFunctionalRequirements: string[];
|
||||
technicalArchitecture: string;
|
||||
successMetrics: string[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface ActionPlan {
|
||||
id: string;
|
||||
prdId: string;
|
||||
tasks: {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
priority: "high" | "medium" | "low";
|
||||
dependencies: string[];
|
||||
estimatedEffort: string;
|
||||
}[];
|
||||
frameworks: {
|
||||
name: string;
|
||||
reason: string;
|
||||
}[];
|
||||
architecture: {
|
||||
pattern: string;
|
||||
structure: string;
|
||||
technologies: string[];
|
||||
bestPractices: string[];
|
||||
};
|
||||
estimatedDuration: string;
|
||||
createdAt: Date;
|
||||
rawContent?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user