Compare commits
10 Commits
79204352fe
...
6b33913471
15
LICENSE
Normal file
15
LICENSE
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Roman | RyzenAdvanced
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
32
README.md
32
README.md
@@ -1,14 +1,19 @@
|
|||||||
# PromptArch: The Prompt Enhancer 🚀
|
# PromptArch: The Prompt Enhancer 🚀
|
||||||
|
|
||||||
> **Development Note**: This entire platform was developed exclusively using the [TRAE.AI IDE](https://trae.ai) powered by the elite [GLM 4.7 model](https://z.ai/subscribe?ic=R0K78RJKNW).
|
> **Development Note**: This entire platform was developed exclusively using [TRAE.AI IDE](https://trae.ai) powered by elite [GLM 4.7 model](https://z.ai/subscribe?ic=R0K78RJKNW).
|
||||||
> **Learn more about this architecture [here](https://z.ai/subscribe?ic=R0K78RJKNW).**
|
> **Learn more about this architecture [here](https://z.ai/subscribe?ic=R0K78RJKNW).**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> **Note**: This project is a specialized fork of [ClavixDev/Clavix](https://github.com/ClavixDev/Clavix), reimagined as a modern web-based platform for visual prompt engineering and product planning.
|
> **Fork Note**: This project is a specialized fork of [ClavixDev/Clavix](https://github.com/ClavixDev/Clavix), reimagined as a modern web-based platform for visual prompt engineering and product planning.
|
||||||
|
|
||||||
Transform vague ideas into production-ready prompts and PRDs. PromptArch is an elite AI orchestration platform designed for software architects and Vibe Coders.
|
Transform vague ideas into production-ready prompts and PRDs. PromptArch is an elite AI orchestration platform designed for software architects and Vibe Coders.
|
||||||
|
|
||||||
|
**Developed by [Roman | RyzenAdvanced](https://github.com/roman-ryzenadvanced)**
|
||||||
|
|
||||||
|
- 📦 **GitHub Repository**: [roman-ryzenadvanced/PromptArch-the-prompt-enhancer](https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer)
|
||||||
|
- 📮 **Telegram**: [@VibeCodePrompterSystem](https://t.me/VibeCodePrompterSystem)
|
||||||
|
|
||||||
## 🌟 Visual Overview
|
## 🌟 Visual Overview
|
||||||
|
|
||||||
### 🛠 Core Capabilities
|
### 🛠 Core Capabilities
|
||||||
@@ -56,21 +61,20 @@ Transform vague ideas into production-ready prompts and PRDs. PromptArch is an e
|
|||||||
- **Components**: [shadcn/ui](https://ui.shadcn.com/)
|
- **Components**: [shadcn/ui](https://ui.shadcn.com/)
|
||||||
- **Icons**: [Lucide React](https://lucide.dev/)
|
- **Icons**: [Lucide React](https://lucide.dev/)
|
||||||
|
|
||||||
## 🤝 Attribution
|
## 🤝 Attribution & Credits
|
||||||
|
|
||||||
This project is a visual and architectural evolution of the [Clavix](https://github.com/ClavixDev/Clavix) framework. While Clavix focuses on agentic-first Markdown templates, PromptArch provides a centralized web interface to execute these workflows with advanced model orchestration.
|
**Author**: [Roman | RyzenAdvanced](https://github.com/roman-ryzenadvanced)
|
||||||
|
- 📦 **GitHub**: [roman-ryzenadvanced/PromptArch-the-prompt-enhancer](https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer)
|
||||||
|
- 📮 **Telegram**: [@VibeCodePrompterSystem](https://t.me/VibeCodePrompterSystem)
|
||||||
|
|
||||||
Developed by **Roman | RyzenAdvanced**
|
**Forked from**: [ClavixDev/Clavix](https://github.com/ClavixDev/Clavix)
|
||||||
- GitHub: [roman-ryzenadvanced](https://github.com/roman-ryzenadvanced)
|
- This project is a visual and architectural evolution of the Clavix framework
|
||||||
- Telegram: [@VibeCodePrompterSystem](https://t.me/VibeCodePrompterSystem)
|
- Clavix focuses on agentic-first Markdown templates
|
||||||
|
- PromptArch provides a centralized web interface with advanced model orchestration
|
||||||
|
|
||||||
---
|
**Development Platform**: [TRAE.AI IDE](https://trae.ai) powered by elite [GLM 4.7 model](https://z.ai/subscribe?ic=R0K78RJKNW)
|
||||||
*100% Developed using GLM 4.7 model on TRAE.AI IDE.*
|
- 100% AI-assisted development using TRAE.AI's advanced coding capabilities
|
||||||
- **Styling**: TailwindCSS
|
- Learn more about the architecture [here](https://z.ai/subscribe?ic=R0K78RJKNW)
|
||||||
- **UI Components**: shadcn/ui + Radix UI
|
|
||||||
- **State Management**: Zustand
|
|
||||||
- **Forms**: React Hook Form + Zod
|
|
||||||
- **Icons**: Lucide React
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
70
app/api/qwen/chat/route.ts
Normal file
70
app/api/qwen/chat/route.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { createQwenHeaders } from "../constants";
|
||||||
|
|
||||||
|
const DEFAULT_QWEN_ENDPOINT = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1";
|
||||||
|
|
||||||
|
function normalizeEndpoint(raw?: string | null): string {
|
||||||
|
const trimmed = (raw || "").trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return DEFAULT_QWEN_ENDPOINT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.endsWith("/chat/completions")) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleaned = trimmed.replace(/\/$/, "");
|
||||||
|
return `${cleaned}/chat/completions`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json().catch(() => ({}));
|
||||||
|
const { endpoint, model, messages, stream } = body || {};
|
||||||
|
const authorization = request.headers.get("authorization") || body?.authorization;
|
||||||
|
|
||||||
|
if (!authorization) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Authorization header required" },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = normalizeEndpoint(endpoint);
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
...createQwenHeaders("application/json"),
|
||||||
|
Authorization: authorization,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model,
|
||||||
|
messages,
|
||||||
|
stream,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = await response.text();
|
||||||
|
if (!response.ok) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: payload || response.statusText || "Qwen chat failed" },
|
||||||
|
{ status: response.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(payload);
|
||||||
|
return NextResponse.json(data, { status: response.status });
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: payload || "Unexpected response format" },
|
||||||
|
{ status: 502 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "internal_server_error", message: error instanceof Error ? error.message : "Qwen chat failed" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,3 +4,19 @@ export const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/t
|
|||||||
export const QWEN_OAUTH_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
|
export const QWEN_OAUTH_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
|
||||||
export const QWEN_OAUTH_SCOPE = "openid profile email model.completion";
|
export const QWEN_OAUTH_SCOPE = "openid profile email model.completion";
|
||||||
export const QWEN_OAUTH_DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
export const QWEN_OAUTH_DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
||||||
|
|
||||||
|
export const QWEN_USER_AGENT =
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||||
|
|
||||||
|
export function createQwenHeaders(contentType?: string) {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"User-Agent": QWEN_USER_AGENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (contentType) {
|
||||||
|
headers["Content-Type"] = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
QWEN_OAUTH_CLIENT_ID,
|
QWEN_OAUTH_CLIENT_ID,
|
||||||
QWEN_OAUTH_DEVICE_CODE_ENDPOINT,
|
QWEN_OAUTH_DEVICE_CODE_ENDPOINT,
|
||||||
QWEN_OAUTH_SCOPE,
|
QWEN_OAUTH_SCOPE,
|
||||||
|
createQwenHeaders,
|
||||||
} from "../../constants";
|
} from "../../constants";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
@@ -17,12 +18,11 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[Qwen Device Auth] Calling ${QWEN_OAUTH_DEVICE_CODE_ENDPOINT}...`);
|
||||||
|
|
||||||
const response = await fetch(QWEN_OAUTH_DEVICE_CODE_ENDPOINT, {
|
const response = await fetch(QWEN_OAUTH_DEVICE_CODE_ENDPOINT, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: createQwenHeaders("application/x-www-form-urlencoded"),
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
client_id: QWEN_OAUTH_CLIENT_ID,
|
client_id: QWEN_OAUTH_CLIENT_ID,
|
||||||
scope: QWEN_OAUTH_SCOPE,
|
scope: QWEN_OAUTH_SCOPE,
|
||||||
@@ -32,18 +32,25 @@ export async function POST(request: NextRequest) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const payload = await response.text();
|
const payload = await response.text();
|
||||||
if (!response.ok) {
|
console.log(`[Qwen Device Auth] Response status: ${response.status}`);
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Device authorization failed", details: payload },
|
let data;
|
||||||
{ status: response.status }
|
try {
|
||||||
);
|
data = JSON.parse(payload);
|
||||||
|
} catch {
|
||||||
|
console.error(`[Qwen Device Auth] Failed to parse response: ${payload}`);
|
||||||
|
data = { error: payload || "Unknown error from Qwen" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json(JSON.parse(payload));
|
if (!response.ok) {
|
||||||
|
console.warn(`[Qwen Device Auth] Error response:`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(data, { status: response.status });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Qwen device authorization failed", error);
|
console.error("Qwen device authorization failed", error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Device authorization failed" },
|
{ error: "internal_server_error", message: error instanceof Error ? error.message : "Device authorization failed" },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { QWEN_OAUTH_CLIENT_ID, QWEN_OAUTH_TOKEN_ENDPOINT } from "../../constants";
|
import {
|
||||||
|
QWEN_OAUTH_CLIENT_ID,
|
||||||
|
QWEN_OAUTH_TOKEN_ENDPOINT,
|
||||||
|
createQwenHeaders,
|
||||||
|
} from "../../constants";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -15,10 +19,7 @@ export async function POST(request: NextRequest) {
|
|||||||
|
|
||||||
const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
|
const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: createQwenHeaders("application/x-www-form-urlencoded"),
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
refresh_token,
|
refresh_token,
|
||||||
@@ -27,18 +28,18 @@ export async function POST(request: NextRequest) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const payload = await response.text();
|
const payload = await response.text();
|
||||||
if (!response.ok) {
|
let data;
|
||||||
return NextResponse.json(
|
try {
|
||||||
{ error: "Token refresh failed", details: payload },
|
data = JSON.parse(payload);
|
||||||
{ status: response.status }
|
} catch {
|
||||||
);
|
data = { error: payload || "Unknown error from Qwen" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json(JSON.parse(payload));
|
return NextResponse.json(data, { status: response.status });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Qwen token refresh failed", error);
|
console.error("Qwen token refresh failed", error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Token refresh failed" },
|
{ error: "internal_server_error", message: error instanceof Error ? error.message : "Token refresh failed" },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
QWEN_OAUTH_CLIENT_ID,
|
QWEN_OAUTH_CLIENT_ID,
|
||||||
QWEN_OAUTH_DEVICE_GRANT_TYPE,
|
QWEN_OAUTH_DEVICE_GRANT_TYPE,
|
||||||
QWEN_OAUTH_TOKEN_ENDPOINT,
|
QWEN_OAUTH_TOKEN_ENDPOINT,
|
||||||
|
createQwenHeaders,
|
||||||
} from "../../constants";
|
} from "../../constants";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
@@ -17,12 +18,11 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[Qwen Token Poll] Calling ${QWEN_OAUTH_TOKEN_ENDPOINT} for device_code: ${device_code.slice(0, 8)}...`);
|
||||||
|
|
||||||
const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
|
const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: createQwenHeaders("application/x-www-form-urlencoded"),
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
grant_type: QWEN_OAUTH_DEVICE_GRANT_TYPE,
|
grant_type: QWEN_OAUTH_DEVICE_GRANT_TYPE,
|
||||||
client_id: QWEN_OAUTH_CLIENT_ID,
|
client_id: QWEN_OAUTH_CLIENT_ID,
|
||||||
@@ -32,18 +32,25 @@ export async function POST(request: NextRequest) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const payload = await response.text();
|
const payload = await response.text();
|
||||||
if (!response.ok) {
|
console.log(`[Qwen Token Poll] Response status: ${response.status}`);
|
||||||
return NextResponse.json(
|
|
||||||
{ error: "Token poll failed", details: payload },
|
let data;
|
||||||
{ status: response.status }
|
try {
|
||||||
);
|
data = JSON.parse(payload);
|
||||||
|
} catch {
|
||||||
|
console.error(`[Qwen Token Poll] Failed to parse response: ${payload}`);
|
||||||
|
data = { error: payload || "Unknown error from Qwen" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json(JSON.parse(payload));
|
if (data.error && data.error !== "authorization_pending") {
|
||||||
|
console.warn(`[Qwen Token Poll] Error in response:`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(data, { status: response.status });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Qwen token poll failed", error);
|
console.error("Qwen token poll failed", error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Token poll failed" },
|
{ error: "internal_server_error", message: error instanceof Error ? error.message : "Token poll failed" },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { QWEN_OAUTH_BASE_URL } from "../constants";
|
import { QWEN_OAUTH_BASE_URL, createQwenHeaders } from "../constants";
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -14,16 +14,20 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const userResponse = await fetch(`${QWEN_OAUTH_BASE_URL}/api/v1/user`, {
|
const userResponse = await fetch(`${QWEN_OAUTH_BASE_URL}/api/v1/user`, {
|
||||||
headers: {
|
headers: {
|
||||||
|
...createQwenHeaders(),
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!userResponse.ok) {
|
if (!userResponse.ok) {
|
||||||
const errorText = await userResponse.text();
|
const errorText = await userResponse.text();
|
||||||
return NextResponse.json(
|
let errorData;
|
||||||
{ error: "Failed to fetch user info", details: errorText },
|
try {
|
||||||
{ status: userResponse.status }
|
errorData = JSON.parse(errorText);
|
||||||
);
|
} catch {
|
||||||
|
errorData = { error: errorText || "Failed to fetch user info" };
|
||||||
|
}
|
||||||
|
return NextResponse.json(errorData, { status: userResponse.status });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userData = await userResponse.json();
|
const userData = await userResponse.json();
|
||||||
@@ -31,7 +35,7 @@ export async function GET(request: NextRequest) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Qwen user info failed", error);
|
console.error("Qwen user info failed", error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Failed to fetch user info" },
|
{ error: "internal_server_error", message: error instanceof Error ? error.message : "Failed to fetch user info" },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
10
app/page.tsx
10
app/page.tsx
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Sidebar from "@/components/Sidebar";
|
import Sidebar from "@/components/Sidebar";
|
||||||
import type { View } from "@/components/Sidebar";
|
import type { View } from "@/components/Sidebar";
|
||||||
import PromptEnhancer from "@/components/PromptEnhancer";
|
import PromptEnhancer from "@/components/PromptEnhancer";
|
||||||
@@ -9,10 +9,16 @@ import ActionPlanGenerator from "@/components/ActionPlanGenerator";
|
|||||||
import UXDesignerPrompt from "@/components/UXDesignerPrompt";
|
import UXDesignerPrompt from "@/components/UXDesignerPrompt";
|
||||||
import HistoryPanel from "@/components/HistoryPanel";
|
import HistoryPanel from "@/components/HistoryPanel";
|
||||||
import SettingsPanel from "@/components/SettingsPanel";
|
import SettingsPanel from "@/components/SettingsPanel";
|
||||||
|
import modelAdapter from "@/lib/services/adapter-instance";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [currentView, setCurrentView] = useState<View>("enhance");
|
const [currentView, setCurrentView] = useState<View>("enhance");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("[Home] Initializing Qwen OAuth service on client...");
|
||||||
|
modelAdapter["qwenService"]["initialize"]?.();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
switch (currentView) {
|
switch (currentView) {
|
||||||
case "enhance":
|
case "enhance":
|
||||||
@@ -35,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) {
|
||||||
@@ -71,7 +71,9 @@ export default function ActionPlanGenerator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = apiKeys[selectedProvider];
|
const apiKey = apiKeys[selectedProvider];
|
||||||
if (!apiKey || !apiKey.trim()) {
|
const isQwenOAuth = selectedProvider === "qwen" && modelAdapter.hasQwenAuth();
|
||||||
|
|
||||||
|
if (!isQwenOAuth && (!apiKey || !apiKey.trim())) {
|
||||||
setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`);
|
setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,9 +81,13 @@ export default function ActionPlanGenerator() {
|
|||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
console.log("[ActionPlanGenerator] Starting action plan generation...", { selectedProvider, selectedModel, hasQwenAuth: modelAdapter.hasQwenAuth() });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await modelAdapter.generateActionPlan(currentPrompt, selectedProvider, selectedModel);
|
const result = await modelAdapter.generateActionPlan(currentPrompt, selectedProvider, selectedModel);
|
||||||
|
|
||||||
|
console.log("[ActionPlanGenerator] Generation result:", result);
|
||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
const newPlan = {
|
const newPlan = {
|
||||||
id: Math.random().toString(36).substr(2, 9),
|
id: Math.random().toString(36).substr(2, 9),
|
||||||
@@ -100,9 +106,11 @@ export default function ActionPlanGenerator() {
|
|||||||
};
|
};
|
||||||
setActionPlan(newPlan);
|
setActionPlan(newPlan);
|
||||||
} else {
|
} else {
|
||||||
|
console.error("[ActionPlanGenerator] Generation failed:", result.error);
|
||||||
setError(result.error || "Failed to generate action plan");
|
setError(result.error || "Failed to generate action plan");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("[ActionPlanGenerator] Generation error:", err);
|
||||||
setError(err instanceof Error ? err.message : "An error occurred");
|
setError(err instanceof Error ? err.message : "An error occurred");
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -118,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>
|
||||||
@@ -148,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}>
|
||||||
@@ -163,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
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -201,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>
|
||||||
@@ -246,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) {
|
||||||
@@ -78,7 +78,9 @@ export default function PRDGenerator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = apiKeys[selectedProvider];
|
const apiKey = apiKeys[selectedProvider];
|
||||||
if (!apiKey || !apiKey.trim()) {
|
const isQwenOAuth = selectedProvider === "qwen" && modelAdapter.hasQwenAuth();
|
||||||
|
|
||||||
|
if (!isQwenOAuth && (!apiKey || !apiKey.trim())) {
|
||||||
setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`);
|
setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,9 +88,13 @@ export default function PRDGenerator() {
|
|||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
console.log("[PRDGenerator] Starting PRD generation...", { selectedProvider, selectedModel, hasQwenAuth: modelAdapter.hasQwenAuth() });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await modelAdapter.generatePRD(currentPrompt, selectedProvider, selectedModel);
|
const result = await modelAdapter.generatePRD(currentPrompt, selectedProvider, selectedModel);
|
||||||
|
|
||||||
|
console.log("[PRDGenerator] Generation result:", result);
|
||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
const newPRD = {
|
const newPRD = {
|
||||||
id: Math.random().toString(36).substr(2, 9),
|
id: Math.random().toString(36).substr(2, 9),
|
||||||
@@ -105,9 +111,11 @@ export default function PRDGenerator() {
|
|||||||
};
|
};
|
||||||
setPRD(newPRD);
|
setPRD(newPRD);
|
||||||
} else {
|
} else {
|
||||||
|
console.error("[PRDGenerator] Generation failed:", result.error);
|
||||||
setError(result.error || "Failed to generate PRD");
|
setError(result.error || "Failed to generate PRD");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("[PRDGenerator] Generation error:", err);
|
||||||
setError(err instanceof Error ? err.message : "An error occurred");
|
setError(err instanceof Error ? err.message : "An error occurred");
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -132,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>
|
||||||
@@ -162,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}>
|
||||||
@@ -177,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
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -215,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) {
|
||||||
@@ -71,7 +71,9 @@ export default function PromptEnhancer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = apiKeys[selectedProvider];
|
const apiKey = apiKeys[selectedProvider];
|
||||||
if (!apiKey || !apiKey.trim()) {
|
const isQwenOAuth = selectedProvider === "qwen" && modelAdapter.hasQwenAuth();
|
||||||
|
|
||||||
|
if (!isQwenOAuth && (!apiKey || !apiKey.trim())) {
|
||||||
setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`);
|
setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,15 +81,21 @@ export default function PromptEnhancer() {
|
|||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
console.log("[PromptEnhancer] Starting enhancement...", { selectedProvider, selectedModel, hasQwenAuth: modelAdapter.hasQwenAuth() });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await modelAdapter.enhancePrompt(currentPrompt, selectedProvider, selectedModel);
|
const result = await modelAdapter.enhancePrompt(currentPrompt, selectedProvider, selectedModel);
|
||||||
|
|
||||||
|
console.log("[PromptEnhancer] Enhancement result:", result);
|
||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
setEnhancedPrompt(result.data);
|
setEnhancedPrompt(result.data);
|
||||||
} else {
|
} else {
|
||||||
|
console.error("[PromptEnhancer] Enhancement failed:", result.error);
|
||||||
setError(result.error || "Failed to enhance prompt");
|
setError(result.error || "Failed to enhance prompt");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("[PromptEnhancer] Enhancement error:", err);
|
||||||
setError(err instanceof Error ? err.message : "An error occurred");
|
setError(err instanceof Error ? err.message : "An error occurred");
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -109,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}
|
||||||
@@ -131,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"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -142,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}>
|
||||||
@@ -157,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);
|
||||||
@@ -77,6 +77,7 @@ export default function SettingsPanel() {
|
|||||||
try {
|
try {
|
||||||
const token = await modelAdapter.startQwenOAuth();
|
const token = await modelAdapter.startQwenOAuth();
|
||||||
setQwenTokens(token);
|
setQwenTokens(token);
|
||||||
|
modelAdapter.updateQwenTokens(token);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Qwen OAuth failed", error);
|
console.error("Qwen OAuth failed", error);
|
||||||
window.alert(
|
window.alert(
|
||||||
@@ -92,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">
|
||||||
@@ -115,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/"
|
||||||
@@ -146,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">
|
||||||
@@ -175,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"
|
||||||
@@ -205,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">
|
||||||
@@ -215,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"
|
||||||
@@ -244,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>
|
||||||
))}
|
))}
|
||||||
@@ -291,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 } 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,78 +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);
|
||||||
<h1 className="flex items-center gap-2 text-xl font-bold">
|
};
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
|
||||||
PA
|
const SidebarContent = () => (
|
||||||
</div>
|
<>
|
||||||
PromptArch
|
<div className="border-b p-4 lg:p-6">
|
||||||
</h1>
|
<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-lg lg:text-xl font-bold hover:opacity-80 transition-opacity">
|
||||||
|
<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
|
||||||
|
</div>
|
||||||
|
PromptArch
|
||||||
|
</h1>
|
||||||
|
</a>
|
||||||
|
<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 w-3 lg:h-3.5 lg:w-3.5" />
|
||||||
|
<span>View on GitHub</span>
|
||||||
|
</a>
|
||||||
|
<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>
|
||||||
|
</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/Custom-Engineered-Agents-and-Tools-for-Vibe-Coders" 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) {
|
||||||
@@ -70,7 +70,9 @@ export default function UXDesignerPrompt() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const apiKey = apiKeys[selectedProvider];
|
const apiKey = apiKeys[selectedProvider];
|
||||||
if (!apiKey || !apiKey.trim()) {
|
const isQwenOAuth = selectedProvider === "qwen" && modelAdapter.hasQwenAuth();
|
||||||
|
|
||||||
|
if (!isQwenOAuth && (!apiKey || !apiKey.trim())) {
|
||||||
setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`);
|
setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -79,16 +81,22 @@ export default function UXDesignerPrompt() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
setGeneratedPrompt(null);
|
setGeneratedPrompt(null);
|
||||||
|
|
||||||
|
console.log("[UXDesignerPrompt] Starting generation...", { selectedProvider, selectedModel, hasQwenAuth: modelAdapter.hasQwenAuth() });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await modelAdapter.generateUXDesignerPrompt(currentPrompt, selectedProvider, selectedModel);
|
const result = await modelAdapter.generateUXDesignerPrompt(currentPrompt, selectedProvider, selectedModel);
|
||||||
|
|
||||||
|
console.log("[UXDesignerPrompt] Generation result:", result);
|
||||||
|
|
||||||
if (result.success && result.data) {
|
if (result.success && result.data) {
|
||||||
setGeneratedPrompt(result.data);
|
setGeneratedPrompt(result.data);
|
||||||
setEnhancedPrompt(result.data);
|
setEnhancedPrompt(result.data);
|
||||||
} else {
|
} else {
|
||||||
|
console.error("[UXDesignerPrompt] Generation failed:", result.error);
|
||||||
setError(result.error || "Failed to generate UX designer prompt");
|
setError(result.error || "Failed to generate UX designer prompt");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error("[UXDesignerPrompt] Generation error:", err);
|
||||||
setError(err instanceof Error ? err.message : "An error occurred");
|
setError(err instanceof Error ? err.message : "An error occurred");
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -111,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}
|
||||||
@@ -133,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"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -144,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}>
|
||||||
@@ -159,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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,4 +2,6 @@ import ModelAdapter from "./model-adapter";
|
|||||||
|
|
||||||
const adapter = new ModelAdapter();
|
const adapter = new ModelAdapter();
|
||||||
|
|
||||||
|
adapter["qwenService"]["initialize"]?.();
|
||||||
|
|
||||||
export default adapter;
|
export default adapter;
|
||||||
|
|||||||
@@ -70,6 +70,23 @@ export class ModelAdapter {
|
|||||||
return this.qwenService.getTokenInfo();
|
return this.qwenService.getTokenInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasQwenAuth(): boolean {
|
||||||
|
return this.qwenService.hasOAuthToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
private isProviderAuthenticated(provider: ModelProvider): boolean {
|
||||||
|
switch (provider) {
|
||||||
|
case "qwen":
|
||||||
|
return this.hasQwenAuth() || this.qwenService.hasApiKey();
|
||||||
|
case "ollama":
|
||||||
|
return this.ollamaService.hasAuth();
|
||||||
|
case "zai":
|
||||||
|
return this.zaiService.hasAuth();
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private buildFallbackProviders(...providers: ModelProvider[]): ModelProvider[] {
|
private buildFallbackProviders(...providers: ModelProvider[]): ModelProvider[] {
|
||||||
const seen = new Set<ModelProvider>();
|
const seen = new Set<ModelProvider>();
|
||||||
return providers.filter((provider) => {
|
return providers.filter((provider) => {
|
||||||
@@ -85,13 +102,29 @@ export class ModelAdapter {
|
|||||||
operation: (service: any) => Promise<APIResponse<T>>,
|
operation: (service: any) => Promise<APIResponse<T>>,
|
||||||
providers: ModelProvider[]
|
providers: ModelProvider[]
|
||||||
): Promise<APIResponse<T>> {
|
): Promise<APIResponse<T>> {
|
||||||
|
console.log("[ModelAdapter] Attempting providers in order:", providers);
|
||||||
|
let lastError: string | null = null;
|
||||||
|
|
||||||
for (const provider of providers) {
|
for (const provider of providers) {
|
||||||
try {
|
try {
|
||||||
|
console.log(`[ModelAdapter] Checking authentication for ${provider}...`);
|
||||||
|
|
||||||
|
if (!this.isProviderAuthenticated(provider)) {
|
||||||
|
console.log(`[ModelAdapter] Provider ${provider} is not authenticated, skipping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let service: any;
|
let service: any;
|
||||||
|
|
||||||
|
console.log(`[ModelAdapter] Trying provider: ${provider}`);
|
||||||
|
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case "qwen":
|
case "qwen":
|
||||||
service = this.qwenService;
|
service = this.qwenService;
|
||||||
|
console.log("[ModelAdapter] Qwen service:", {
|
||||||
|
hasApiKey: !!this.qwenService["apiKey"],
|
||||||
|
hasToken: !!this.qwenService.getTokenInfo()?.accessToken
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "ollama":
|
case "ollama":
|
||||||
service = this.ollamaService;
|
service = this.ollamaService;
|
||||||
@@ -102,17 +135,30 @@ export class ModelAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await operation(service);
|
const result = await operation(service);
|
||||||
|
console.log(`[ModelAdapter] Provider ${provider} result:`, result);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
console.log(`[ModelAdapter] Success with provider: ${provider}`);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
lastError = result.error;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error with ${provider}:`, error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
console.error(`[ModelAdapter] Error with ${provider}:`, errorMessage);
|
||||||
|
lastError = errorMessage || lastError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalError = lastError
|
||||||
|
? `All providers failed: ${lastError}`
|
||||||
|
: "All providers failed. Please configure API key in Settings";
|
||||||
|
console.error(`[ModelAdapter] ${finalError}`);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "All providers failed",
|
error: finalError,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ export class OllamaCloudService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasAuth(): boolean {
|
||||||
|
return !!this.config.apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
private ensureApiKey(): string {
|
private ensureApiKey(): string {
|
||||||
if (this.config.apiKey) {
|
if (this.config.apiKey) {
|
||||||
return this.config.apiKey;
|
return this.config.apiKey;
|
||||||
@@ -158,6 +162,146 @@ export class OllamaCloudService {
|
|||||||
getAvailableModels(): string[] {
|
getAvailableModels(): string[] {
|
||||||
return this.availableModels.length > 0 ? this.availableModels : DEFAULT_MODELS;
|
return this.availableModels.length > 0 ? this.availableModels : DEFAULT_MODELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async enhancePrompt(prompt: string, model?: string): Promise<APIResponse<string>> {
|
||||||
|
const systemMessage: ChatMessage = {
|
||||||
|
role: "system",
|
||||||
|
content: `You are an expert prompt engineer. Your task is to enhance user prompts to make them more precise, actionable, and effective for AI coding agents.
|
||||||
|
|
||||||
|
Apply these principles:
|
||||||
|
1. Add specific context about project and requirements
|
||||||
|
2. Clarify constraints and preferences
|
||||||
|
3. Define expected output format clearly
|
||||||
|
4. Include edge cases and error handling requirements
|
||||||
|
5. Specify testing and validation criteria
|
||||||
|
|
||||||
|
Return ONLY the enhanced prompt, no explanations or extra text.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userMessage: ChatMessage = {
|
||||||
|
role: "user",
|
||||||
|
content: `Enhance this prompt for an AI coding agent:\n\n${prompt}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b");
|
||||||
|
}
|
||||||
|
|
||||||
|
async generatePRD(idea: string, model?: string): Promise<APIResponse<string>> {
|
||||||
|
const systemMessage: ChatMessage = {
|
||||||
|
role: "system",
|
||||||
|
content: `You are an expert product manager and technical architect. Generate a comprehensive Product Requirements Document (PRD) based on user's idea.
|
||||||
|
|
||||||
|
Structure your PRD with these sections:
|
||||||
|
1. Overview & Objectives
|
||||||
|
2. User Personas & Use Cases
|
||||||
|
3. Functional Requirements (prioritized)
|
||||||
|
4. Non-functional Requirements
|
||||||
|
5. Technical Architecture Recommendations
|
||||||
|
6. Success Metrics & KPIs
|
||||||
|
|
||||||
|
Use clear, specific language suitable for development teams.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userMessage: ChatMessage = {
|
||||||
|
role: "user",
|
||||||
|
content: `Generate a PRD for this idea:\n\n${idea}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b");
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateActionPlan(prd: string, model?: string): Promise<APIResponse<string>> {
|
||||||
|
const systemMessage: ChatMessage = {
|
||||||
|
role: "system",
|
||||||
|
content: `You are an expert technical lead and project manager. Generate a detailed action plan based on PRD.
|
||||||
|
|
||||||
|
Structure of action plan with:
|
||||||
|
1. Task breakdown with priorities (High/Medium/Low)
|
||||||
|
2. Dependencies between tasks
|
||||||
|
3. Estimated effort for each task
|
||||||
|
4. Recommended frameworks and technologies
|
||||||
|
5. Architecture guidelines and best practices
|
||||||
|
|
||||||
|
Include specific recommendations for:
|
||||||
|
- Frontend frameworks
|
||||||
|
- Backend architecture
|
||||||
|
- Database choices
|
||||||
|
- Authentication/authorization
|
||||||
|
- Deployment strategy`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userMessage: ChatMessage = {
|
||||||
|
role: "user",
|
||||||
|
content: `Generate an action plan based on this PRD:\n\n${prd}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b");
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateUXDesignerPrompt(appDescription: string, model?: string): Promise<APIResponse<string>> {
|
||||||
|
const systemMessage: ChatMessage = {
|
||||||
|
role: "system",
|
||||||
|
content: `You are a world-class UX/UI designer with deep expertise in human-centered design principles, user research, interaction design, visual design systems, and modern design tools (Figma, Sketch, Adobe XD).
|
||||||
|
|
||||||
|
Your task is to create an exceptional, detailed prompt for generating best possible UX design for a given app description.
|
||||||
|
|
||||||
|
Generate a comprehensive UX design prompt that includes:
|
||||||
|
|
||||||
|
1. USER RESEARCH & PERSONAS
|
||||||
|
- Primary target users and their motivations
|
||||||
|
- User pain points and needs
|
||||||
|
- User journey maps
|
||||||
|
- Persona archetypes with demographics and goals
|
||||||
|
|
||||||
|
2. INFORMATION ARCHITECTURE
|
||||||
|
- Content hierarchy and organization
|
||||||
|
- Navigation structure and patterns
|
||||||
|
- User flows and key pathways
|
||||||
|
- Site map or app structure
|
||||||
|
|
||||||
|
3. VISUAL DESIGN SYSTEM
|
||||||
|
- Color palette recommendations (primary, secondary, accent, neutral)
|
||||||
|
- Typography hierarchy and font pairings
|
||||||
|
- Component library approach
|
||||||
|
- Spacing, sizing, and layout grids
|
||||||
|
- Iconography style and set
|
||||||
|
|
||||||
|
4. INTERACTION DESIGN
|
||||||
|
- Micro-interactions and animations
|
||||||
|
- Gesture patterns for touch interfaces
|
||||||
|
- Loading states and empty states
|
||||||
|
- Error handling and feedback mechanisms
|
||||||
|
- Accessibility considerations (WCAG compliance)
|
||||||
|
|
||||||
|
5. KEY SCREENS & COMPONENTS
|
||||||
|
- Core screens that need detailed design
|
||||||
|
- Critical components (buttons, forms, cards, navigation)
|
||||||
|
- Data visualization needs
|
||||||
|
- Responsive design requirements (mobile, tablet, desktop)
|
||||||
|
|
||||||
|
6. DESIGN DELIVERABLES
|
||||||
|
- Wireframes vs. high-fidelity mockups
|
||||||
|
- Design system documentation needs
|
||||||
|
- Prototyping requirements
|
||||||
|
- Handoff specifications for developers
|
||||||
|
|
||||||
|
7. COMPETITIVE INSIGHTS
|
||||||
|
- Design patterns from successful apps in this category
|
||||||
|
- Opportunities to differentiate
|
||||||
|
- Modern design trends to consider
|
||||||
|
|
||||||
|
The output should be a detailed, actionable prompt that a designer or AI image generator can use to create world-class UX designs.
|
||||||
|
|
||||||
|
Make's prompt specific, inspiring, and comprehensive. Use professional UX terminology.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const userMessage: ChatMessage = {
|
||||||
|
role: "user",
|
||||||
|
content: `Create a BEST EVER UX design prompt for this app:\n\n${appDescription}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OllamaCloudService;
|
export default OllamaCloudService;
|
||||||
|
|||||||
@@ -68,10 +68,20 @@ export class QwenOAuthService {
|
|||||||
this.apiKey = apiKey;
|
this.apiKey = apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasApiKey(): boolean {
|
||||||
|
return !!this.apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasOAuthToken(): boolean {
|
||||||
|
return !!this.getTokenInfo()?.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build default headers for Qwen completions (includes OAuth token refresh).
|
* Build default headers for Qwen completions (includes OAuth token refresh).
|
||||||
*/
|
*/
|
||||||
private async getRequestHeaders(): Promise<Record<string, string>> {
|
private async getRequestHeaders(): Promise<Record<string, string>> {
|
||||||
|
console.log("[QwenOAuth] Getting request headers...");
|
||||||
|
|
||||||
const token = await this.getValidToken();
|
const token = await this.getValidToken();
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -79,14 +89,17 @@ export class QwenOAuthService {
|
|||||||
|
|
||||||
if (token?.accessToken) {
|
if (token?.accessToken) {
|
||||||
headers["Authorization"] = `Bearer ${token.accessToken}`;
|
headers["Authorization"] = `Bearer ${token.accessToken}`;
|
||||||
|
console.log("[QwenOAuth] Using OAuth token for authorization");
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.apiKey) {
|
if (this.apiKey) {
|
||||||
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
||||||
|
console.log("[QwenOAuth] Using API key for authorization");
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error("[QwenOAuth] No OAuth token or API key available");
|
||||||
throw new Error("Please configure a Qwen API key or authenticate via OAuth.");
|
throw new Error("Please configure a Qwen API key or authenticate via OAuth.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +109,11 @@ export class QwenOAuthService {
|
|||||||
private getEffectiveEndpoint(): string {
|
private getEffectiveEndpoint(): string {
|
||||||
const resourceUrl = this.token?.resourceUrl;
|
const resourceUrl = this.token?.resourceUrl;
|
||||||
if (resourceUrl) {
|
if (resourceUrl) {
|
||||||
return this.normalizeResourceUrl(resourceUrl);
|
const normalized = this.normalizeResourceUrl(resourceUrl);
|
||||||
|
console.log("[Qwen] Using resource URL:", normalized);
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
|
console.log("[Qwen] Using default endpoint:", this.endpoint);
|
||||||
return this.endpoint;
|
return this.endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +125,12 @@ export class QwenOAuthService {
|
|||||||
|
|
||||||
const withProtocol = trimmed.startsWith("http") ? trimmed : `https://${trimmed}`;
|
const withProtocol = trimmed.startsWith("http") ? trimmed : `https://${trimmed}`;
|
||||||
const cleaned = withProtocol.replace(/\/$/, "");
|
const cleaned = withProtocol.replace(/\/$/, "");
|
||||||
return cleaned.endsWith("/v1") ? cleaned : `${cleaned}/v1`;
|
|
||||||
|
if (cleaned.endsWith("/v1") || cleaned.endsWith("/compatible-mode/v1")) {
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${cleaned}/v1`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private hydrateTokens() {
|
private hydrateTokens() {
|
||||||
@@ -132,6 +153,7 @@ export class QwenOAuthService {
|
|||||||
|
|
||||||
private getStoredToken(): QwenOAuthToken | null {
|
private getStoredToken(): QwenOAuthToken | null {
|
||||||
this.hydrateTokens();
|
this.hydrateTokens();
|
||||||
|
console.log("[QwenOAuth] Retrieved stored token:", this.token ? { hasAccessToken: !!this.token.accessToken, expiresAt: this.token.expiresAt } : null);
|
||||||
return this.token;
|
return this.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,8 +251,18 @@ export class QwenOAuthService {
|
|||||||
this.storageHydrated = true;
|
this.storageHydrated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the service and hydrate tokens from storage.
|
||||||
|
*/
|
||||||
|
initialize(): void {
|
||||||
|
console.log("[QwenOAuth] Initializing service...");
|
||||||
|
this.hydrateTokens();
|
||||||
|
}
|
||||||
|
|
||||||
getTokenInfo(): QwenOAuthToken | null {
|
getTokenInfo(): QwenOAuthToken | null {
|
||||||
return this.getStoredToken();
|
this.hydrateTokens();
|
||||||
|
console.log("[QwenOAuth] getTokenInfo called, returning:", this.token ? { hasAccessToken: !!this.token.accessToken, expiresAt: this.token.expiresAt } : null);
|
||||||
|
return this.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -353,12 +385,30 @@ export class QwenOAuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private parseTokenResponse(data: any): QwenOAuthToken {
|
private parseTokenResponse(data: any): QwenOAuthToken {
|
||||||
return {
|
console.log("[QwenOAuth] Token response received:", data);
|
||||||
|
|
||||||
|
const token: QwenOAuthToken = {
|
||||||
accessToken: data.access_token,
|
accessToken: data.access_token,
|
||||||
refreshToken: data.refresh_token,
|
refreshToken: data.refresh_token,
|
||||||
resourceUrl: data.resource_url,
|
|
||||||
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
|
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (data.resource_url) {
|
||||||
|
token.resourceUrl = data.resource_url;
|
||||||
|
console.log("[QwenOAuth] Using resource_url from response:", data.resource_url);
|
||||||
|
} else if (data.endpoint) {
|
||||||
|
token.resourceUrl = data.endpoint;
|
||||||
|
console.log("[QwenOAuth] Using endpoint from response:", data.endpoint);
|
||||||
|
} else if (data.resource_server) {
|
||||||
|
token.resourceUrl = `https://${data.resource_server}/compatible-mode/v1`;
|
||||||
|
console.log("[QwenOAuth] Using resource_server from response:", data.resource_server);
|
||||||
|
} else {
|
||||||
|
console.log("[QwenOAuth] No resource_url/endpoint in response, will use default Qwen endpoint");
|
||||||
|
console.log("[QwenOAuth] Available fields in response:", Object.keys(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[QwenOAuth] Parsed token:", { hasAccessToken: !!token.accessToken, hasRefreshToken: !!token.refreshToken, hasResourceUrl: !!token.resourceUrl, expiresAt: token.expiresAt });
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -390,17 +440,24 @@ export class QwenOAuthService {
|
|||||||
|
|
||||||
async chatCompletion(
|
async chatCompletion(
|
||||||
messages: ChatMessage[],
|
messages: ChatMessage[],
|
||||||
model: string = "qwen-coder-plus",
|
model: string = "coder-model",
|
||||||
stream: boolean = false
|
stream: boolean = false
|
||||||
): Promise<APIResponse<string>> {
|
): Promise<APIResponse<string>> {
|
||||||
try {
|
try {
|
||||||
const headers = await this.getRequestHeaders();
|
const headers = await this.getRequestHeaders();
|
||||||
const url = `${this.getEffectiveEndpoint()}/chat/completions`;
|
const baseUrl = this.getEffectiveEndpoint();
|
||||||
|
const url = `${this.oauthBaseUrl}/chat`;
|
||||||
|
|
||||||
|
console.log("[Qwen] Chat completion request:", { url, model, hasAuth: !!headers.Authorization });
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: headers.Authorization || "",
|
||||||
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
endpoint: baseUrl,
|
||||||
model,
|
model,
|
||||||
messages,
|
messages,
|
||||||
stream,
|
stream,
|
||||||
@@ -409,6 +466,7 @@ export class QwenOAuthService {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
|
console.error("[Qwen] Chat completion failed:", response.status, response.statusText, errorText);
|
||||||
throw new Error(`Chat completion failed (${response.status}): ${response.statusText} - ${errorText}`);
|
throw new Error(`Chat completion failed (${response.status}): ${response.statusText} - ${errorText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +505,7 @@ Return ONLY the enhanced prompt, no explanations or extra text.`,
|
|||||||
content: `Enhance this prompt for an AI coding agent:\n\n${prompt}`,
|
content: `Enhance this prompt for an AI coding agent:\n\n${prompt}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.chatCompletion([systemMessage, userMessage], model || "qwen-coder-plus");
|
return this.chatCompletion([systemMessage, userMessage], model || "coder-model");
|
||||||
}
|
}
|
||||||
|
|
||||||
async generatePRD(idea: string, model?: string): Promise<APIResponse<string>> {
|
async generatePRD(idea: string, model?: string): Promise<APIResponse<string>> {
|
||||||
@@ -471,7 +529,7 @@ Use clear, specific language suitable for development teams.`,
|
|||||||
content: `Generate a PRD for this idea:\n\n${idea}`,
|
content: `Generate a PRD for this idea:\n\n${idea}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.chatCompletion([systemMessage, userMessage], model || "qwen-coder-plus");
|
return this.chatCompletion([systemMessage, userMessage], model || "coder-model");
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateActionPlan(prd: string, model?: string): Promise<APIResponse<string>> {
|
async generateActionPlan(prd: string, model?: string): Promise<APIResponse<string>> {
|
||||||
@@ -499,7 +557,7 @@ Include specific recommendations for:
|
|||||||
content: `Generate an action plan based on this PRD:\n\n${prd}`,
|
content: `Generate an action plan based on this PRD:\n\n${prd}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.chatCompletion([systemMessage, userMessage], model || "qwen-coder-plus");
|
return this.chatCompletion([systemMessage, userMessage], model || "coder-model");
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateUXDesignerPrompt(appDescription: string, model?: string): Promise<APIResponse<string>> {
|
async generateUXDesignerPrompt(appDescription: string, model?: string): Promise<APIResponse<string>> {
|
||||||
@@ -564,29 +622,19 @@ Make's prompt specific, inspiring, and comprehensive. Use professional UX termin
|
|||||||
content: `Create a BEST EVER UX design prompt for this app:\n\n${appDescription}`,
|
content: `Create a BEST EVER UX design prompt for this app:\n\n${appDescription}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.chatCompletion([systemMessage, userMessage], model || "qwen-coder-plus");
|
return this.chatCompletion([systemMessage, userMessage], model || "coder-model");
|
||||||
}
|
}
|
||||||
|
|
||||||
async listModels(): Promise<APIResponse<string[]>> {
|
async listModels(): Promise<APIResponse<string[]>> {
|
||||||
const models = [
|
const models = [
|
||||||
"qwen-coder-plus",
|
"coder-model",
|
||||||
"qwen-coder-turbo",
|
|
||||||
"qwen-coder-lite",
|
|
||||||
"qwen-plus",
|
|
||||||
"qwen-turbo",
|
|
||||||
"qwen-max",
|
|
||||||
];
|
];
|
||||||
return { success: true, data: models };
|
return { success: true, data: models };
|
||||||
}
|
}
|
||||||
|
|
||||||
getAvailableModels(): string[] {
|
getAvailableModels(): string[] {
|
||||||
return [
|
return [
|
||||||
"qwen-coder-plus",
|
"coder-model",
|
||||||
"qwen-coder-turbo",
|
|
||||||
"qwen-coder-lite",
|
|
||||||
"qwen-plus",
|
|
||||||
"qwen-turbo",
|
|
||||||
"qwen-max",
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export class ZaiPlanService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasAuth(): boolean {
|
||||||
|
return !!this.config.apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
private getHeaders(): Record<string, string> {
|
private getHeaders(): Record<string, string> {
|
||||||
return {
|
return {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -46,12 +46,12 @@ const useStore = create<AppState>((set) => ({
|
|||||||
actionPlan: null,
|
actionPlan: null,
|
||||||
selectedProvider: "qwen",
|
selectedProvider: "qwen",
|
||||||
selectedModels: {
|
selectedModels: {
|
||||||
qwen: "qwen-coder-plus",
|
qwen: "coder-model",
|
||||||
ollama: "gpt-oss:120b",
|
ollama: "gpt-oss:120b",
|
||||||
zai: "glm-4.7",
|
zai: "glm-4.7",
|
||||||
},
|
},
|
||||||
availableModels: {
|
availableModels: {
|
||||||
qwen: ["qwen-coder-plus", "qwen-coder-turbo", "qwen-coder-lite"],
|
qwen: ["coder-model"],
|
||||||
ollama: ["gpt-oss:120b", "llama3.1", "gemma3", "deepseek-r1", "qwen3"],
|
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"],
|
zai: ["glm-4.7", "glm-4.6", "glm-4.5", "glm-4.5-air", "glm-4-flash", "glm-4-flashx"],
|
||||||
},
|
},
|
||||||
|
|||||||
379
package-lock.json
generated
379
package-lock.json
generated
@@ -17,7 +17,7 @@
|
|||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "^15.0.3",
|
"eslint-config-next": "^15.0.3",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"next": "^15.0.3",
|
"next": "^16.1.1",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@@ -50,17 +50,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/core": {
|
|
||||||
"version": "1.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
|
|
||||||
"integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/wasi-threads": "1.1.0",
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@emnapi/runtime": {
|
"node_modules/@emnapi/runtime": {
|
||||||
"version": "1.7.1",
|
"version": "1.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
|
||||||
@@ -71,16 +60,6 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emnapi/wasi-threads": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.9.0",
|
"version": "4.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
|
||||||
@@ -764,22 +743,10 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/wasm-runtime": {
|
|
||||||
"version": "0.2.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
|
||||||
"integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emnapi/core": "^1.4.3",
|
|
||||||
"@emnapi/runtime": "^1.4.3",
|
|
||||||
"@tybys/wasm-util": "^0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.5.9",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.1.tgz",
|
||||||
"integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==",
|
"integrity": "sha512-3oxyM97Sr2PqiVyMyrZUtrtM3jqqFxOQJVuKclDsgj/L728iZt/GyslkN4NwarledZATCenbk4Offjk1hQmaAA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
@@ -792,9 +759,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.1.tgz",
|
||||||
"integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==",
|
"integrity": "sha512-JS3m42ifsVSJjSTzh27nW+Igfha3NdBOFScr9C80hHGrWx55pTrVL23RJbqir7k7/15SKlrLHhh/MQzqBBYrQA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -808,9 +775,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.1.tgz",
|
||||||
"integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==",
|
"integrity": "sha512-hbyKtrDGUkgkyQi1m1IyD3q4I/3m9ngr+V93z4oKHrPcmxwNL5iMWORvLSGAf2YujL+6HxgVvZuCYZfLfb4bGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -824,9 +791,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.1.tgz",
|
||||||
"integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==",
|
"integrity": "sha512-/fvHet+EYckFvRLQ0jPHJCUI5/B56+2DpI1xDSvi80r/3Ez+Eaa2Yq4tJcRTaB1kqj/HrYKn8Yplm9bNoMJpwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -840,9 +807,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.1.tgz",
|
||||||
"integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==",
|
"integrity": "sha512-MFHrgL4TXNQbBPzkKKur4Fb5ICEJa87HM7fczFs2+HWblM7mMLdco3dvyTI+QmLBU9xgns/EeeINSZD6Ar+oLg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -856,9 +823,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.1.tgz",
|
||||||
"integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==",
|
"integrity": "sha512-20bYDfgOQAPUkkKBnyP9PTuHiJGM7HzNBbuqmD0jiFVZ0aOldz+VnJhbxzjcSabYsnNjMPsE0cyzEudpYxsrUQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -872,9 +839,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.1.tgz",
|
||||||
"integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==",
|
"integrity": "sha512-9pRbK3M4asAHQRkwaXwu601oPZHghuSC8IXNENgbBSyImHv/zY4K5udBusgdHkvJ/Tcr96jJwQYOll0qU8+fPA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -888,9 +855,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.1.tgz",
|
||||||
"integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==",
|
"integrity": "sha512-bdfQkggaLgnmYrFkSQfsHfOhk/mCYmjnrbRCGgkMcoOBZ4n+TRRSLmT/CU5SATzlBJ9TpioUyBW/vWFXTqQRiA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -904,9 +871,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "15.5.7",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.1.tgz",
|
||||||
"integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==",
|
"integrity": "sha512-Ncwbw2WJ57Al5OX0k4chM68DKhEPlrXBaSXDCi2kPi5f4d8b3ejr3RRJGfKBLrn2YJL5ezNS7w2TZLHSti8CMw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1032,16 +999,6 @@
|
|||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tybys/wasm-util": {
|
|
||||||
"version": "0.10.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
|
||||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/d3-array": {
|
"node_modules/@types/d3-array": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||||
@@ -1467,243 +1424,6 @@
|
|||||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/@unrs/resolver-binding-android-arm-eabi": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-android-arm64": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"android"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-darwin-arm64": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-darwin-x64": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-freebsd-x64": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"freebsd"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-arm64-musl": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
|
|
||||||
"cpu": [
|
|
||||||
"ppc64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
|
|
||||||
"cpu": [
|
|
||||||
"riscv64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
|
|
||||||
"cpu": [
|
|
||||||
"riscv64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
|
|
||||||
"cpu": [
|
|
||||||
"s390x"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-x64-gnu": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-linux-x64-musl": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-wasm32-wasi": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
|
|
||||||
"cpu": [
|
|
||||||
"wasm32"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@napi-rs/wasm-runtime": "^0.2.11"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
|
|
||||||
"version": "1.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
|
|
||||||
"integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
|
|
||||||
"cpu": [
|
|
||||||
"ia32"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"node_modules/@unrs/resolver-binding-win32-x64-msvc": {
|
"node_modules/@unrs/resolver-binding-win32-x64-msvc": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
|
||||||
@@ -3499,20 +3219,6 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
|
||||||
"version": "2.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -5560,13 +5266,14 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.5.9",
|
"version": "16.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-16.1.1.tgz",
|
||||||
"integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==",
|
"integrity": "sha512-QI+T7xrxt1pF6SQ/JYFz95ro/mg/1Znk5vBebsWwbpejj1T0A23hO7GYEaVac9QUOT2BIMiuzm0L99ooq7k0/w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.5.9",
|
"@next/env": "16.1.1",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
|
"baseline-browser-mapping": "^2.8.3",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
"postcss": "8.4.31",
|
"postcss": "8.4.31",
|
||||||
"styled-jsx": "5.1.6"
|
"styled-jsx": "5.1.6"
|
||||||
@@ -5575,18 +5282,18 @@
|
|||||||
"next": "dist/bin/next"
|
"next": "dist/bin/next"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
"node": ">=20.9.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "15.5.7",
|
"@next/swc-darwin-arm64": "16.1.1",
|
||||||
"@next/swc-darwin-x64": "15.5.7",
|
"@next/swc-darwin-x64": "16.1.1",
|
||||||
"@next/swc-linux-arm64-gnu": "15.5.7",
|
"@next/swc-linux-arm64-gnu": "16.1.1",
|
||||||
"@next/swc-linux-arm64-musl": "15.5.7",
|
"@next/swc-linux-arm64-musl": "16.1.1",
|
||||||
"@next/swc-linux-x64-gnu": "15.5.7",
|
"@next/swc-linux-x64-gnu": "16.1.1",
|
||||||
"@next/swc-linux-x64-musl": "15.5.7",
|
"@next/swc-linux-x64-musl": "16.1.1",
|
||||||
"@next/swc-win32-arm64-msvc": "15.5.7",
|
"@next/swc-win32-arm64-msvc": "16.1.1",
|
||||||
"@next/swc-win32-x64-msvc": "15.5.7",
|
"@next/swc-win32-x64-msvc": "16.1.1",
|
||||||
"sharp": "^0.34.3"
|
"sharp": "^0.34.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opentelemetry/api": "^1.1.0",
|
"@opentelemetry/api": "^1.1.0",
|
||||||
|
|||||||
24
package.json
24
package.json
@@ -17,7 +17,7 @@
|
|||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "^15.0.3",
|
"eslint-config-next": "^15.0.3",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"next": "^15.0.3",
|
"next": "^16.1.1",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
@@ -37,7 +37,23 @@
|
|||||||
"@types/react": "^19.0.1",
|
"@types/react": "^19.0.1",
|
||||||
"@types/react-dom": "^19.0.2"
|
"@types/react-dom": "^19.0.2"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [
|
||||||
"author": "",
|
"ai",
|
||||||
"license": "ISC"
|
"prompt-engineering",
|
||||||
|
"prd-generator",
|
||||||
|
"nextjs",
|
||||||
|
"qwen",
|
||||||
|
"ollama",
|
||||||
|
"zai"
|
||||||
|
],
|
||||||
|
"author": "Roman | RyzenAdvanced <https://github.com/roman-ryzenadvanced>",
|
||||||
|
"license": "ISC",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/roman-ryzenadvanced/PromptArch-the-prompt-enhancer#readme"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "ES2017",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -11,7 +15,7 @@
|
|||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react-jsx",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -19,9 +23,19 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
"framework": "nextjs",
|
"framework": "nextjs",
|
||||||
"devCommand": "npm run dev",
|
"devCommand": "npm run dev",
|
||||||
"env": {
|
"env": {
|
||||||
"NEXT_PUBLIC_SITE_URL": {
|
"NEXT_PUBLIC_SITE_URL": "https://traetlzlxn2t.vercel.app"
|
||||||
"description": "The production URL of your app (e.g., https://your-app.vercel.app)",
|
|
||||||
"value": "https://your-app.vercel.app"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user