Files
PromptArch/lib/services/qwen-oauth.ts

218 lines
7.1 KiB
TypeScript

import type { ChatMessage, APIResponse } from "@/types";
export interface QwenOAuthConfig {
apiKey?: string;
accessToken?: string;
refreshToken?: string;
expiresAt?: number;
endpoint?: string;
clientId?: string;
redirectUri?: string;
}
export class QwenOAuthService {
private config: QwenOAuthConfig;
constructor(config: QwenOAuthConfig = {}) {
this.config = {
endpoint: config.endpoint || "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
apiKey: config.apiKey || process.env.QWEN_API_KEY,
accessToken: config.accessToken,
refreshToken: config.refreshToken,
expiresAt: config.expiresAt,
clientId: config.clientId || process.env.NEXT_PUBLIC_QWEN_CLIENT_ID,
redirectUri: config.redirectUri || (typeof window !== "undefined" ? window.location.origin : ""),
};
}
private getHeaders(): Record<string, string> {
const authHeader = this.config.accessToken
? `Bearer ${this.config.accessToken}`
: `Bearer ${this.config.apiKey}`;
return {
"Content-Type": "application/json",
"Authorization": authHeader,
};
}
isAuthenticated(): boolean {
return !!(this.config.apiKey || (this.config.accessToken && (!this.config.expiresAt || this.config.expiresAt > Date.now())));
}
getAccessToken(): string | null {
return this.config.accessToken || this.config.apiKey || null;
}
async authenticate(apiKey: string): Promise<APIResponse<string>> {
try {
this.config.apiKey = apiKey;
this.config.accessToken = undefined; // Clear OAuth token if API key is provided
return { success: true, data: "Authenticated successfully" };
} catch (error) {
console.error("Qwen authentication error:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Authentication failed",
};
}
}
setOAuthTokens(accessToken: string, refreshToken?: string, expiresIn?: number): void {
this.config.accessToken = accessToken;
if (refreshToken) this.config.refreshToken = refreshToken;
if (expiresIn) this.config.expiresAt = Date.now() + expiresIn * 1000;
}
getAuthorizationUrl(): string {
const baseUrl = "https://dashscope.console.aliyun.com/oauth/authorize"; // Placeholder URL
const params = new URLSearchParams({
client_id: this.config.clientId || "",
redirect_uri: this.config.redirectUri || "",
response_type: "code",
scope: "dashscope:chat",
});
return `${baseUrl}?${params.toString()}`;
}
async logout(): Promise<void> {
this.config.apiKey = undefined;
this.config.accessToken = undefined;
this.config.refreshToken = undefined;
this.config.expiresAt = undefined;
}
async chatCompletion(
messages: ChatMessage[],
model: string = "qwen-coder-plus",
stream: boolean = false
): Promise<APIResponse<string>> {
try {
if (!this.config.apiKey) {
throw new Error("API key is required. Please configure your Qwen API key in settings.");
}
console.log("[Qwen] API call:", { endpoint: this.config.endpoint, model, messages });
const response = await fetch(`${this.config.endpoint}/chat/completions`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify({
model,
messages,
stream,
}),
});
console.log("[Qwen] Response status:", response.status, response.statusText);
if (!response.ok) {
const errorText = await response.text();
console.error("[Qwen] Error response:", errorText);
throw new Error(`Chat completion failed (${response.status}): ${response.statusText} - ${errorText}`);
}
const data = await response.json();
console.log("[Qwen] Response data:", data);
if (data.choices && data.choices[0] && data.choices[0].message) {
return { success: true, data: data.choices[0].message.content };
} else {
return { success: false, error: "Unexpected response format" };
}
} catch (error) {
console.error("[Qwen] Chat completion error:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Chat completion failed",
};
}
}
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 || "qwen-coder-plus");
}
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 || "qwen-coder-plus");
}
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 || "qwen-coder-plus");
}
async listModels(): Promise<APIResponse<string[]>> {
const models = ["qwen-coder-plus", "qwen-coder-turbo", "qwen-coder-lite", "qwen-plus", "qwen-turbo", "qwen-max"];
return { success: true, data: models };
}
getAvailableModels(): string[] {
return ["qwen-coder-plus", "qwen-coder-turbo", "qwen-coder-lite", "qwen-plus", "qwen-turbo", "qwen-max"];
}
}
export default QwenOAuthService;