feat: v1.3.0 — plan-first workflow, OpenRouter provider, enhanced prompt engine
Major changes: - Plan-first workflow: AI generates structured plan before code, with plan review card (Modify Plan / Start Coding / Skip to Code) - Post-coding UX: Preview + Request Modifications buttons after code gen - OpenRouter integration: 4th AI provider with 20+ model support - Enhanced prompt engine: 9 strategies, 11+ intent patterns, modular - PLAN MODE system prompt block in all 4 services - Fixed stale React closure in approveAndGenerate with isApproval flag - Fixed canvas auto-opening during plan phase with wasIdle gate - Updated README, CHANGELOG, .env.example, version bump to 1.3.0
This commit is contained in:
967
lib/services/openrouter.ts
Normal file
967
lib/services/openrouter.ts
Normal file
@@ -0,0 +1,967 @@
|
||||
import type { ChatMessage, APIResponse, AIAssistMessage } from "@/types";
|
||||
|
||||
export interface OpenRouterConfig {
|
||||
apiKey?: string;
|
||||
siteUrl?: string;
|
||||
siteName?: string;
|
||||
}
|
||||
|
||||
interface OpenRouterModelsResponse {
|
||||
data: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
context_length: number;
|
||||
pricing: {
|
||||
prompt: string;
|
||||
completion: string;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
interface OpenRouterChatResponse {
|
||||
id: string;
|
||||
choices: Array<{
|
||||
message: {
|
||||
role: string;
|
||||
content: string;
|
||||
};
|
||||
finish_reason: string;
|
||||
}>;
|
||||
usage?: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
total_tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_MODELS = [
|
||||
"anthropic/claude-3.5-sonnet",
|
||||
"google/gemini-2.0-flash-exp:free",
|
||||
"meta-llama/llama-3.3-70b-instruct",
|
||||
"openai/gpt-4o-mini",
|
||||
"deepseek/deepseek-chat-v3-0324",
|
||||
"qwen/qwen-2.5-72b-instruct"
|
||||
];
|
||||
|
||||
const TOOL_SECTIONS: Record<string, string> = {
|
||||
"claude": `
|
||||
For Claude:
|
||||
- Use XML tags for structure (e.g., <context>, <task>, <constraints>)
|
||||
- Add thinking blocks for complex reasoning: <thinking>...</thinking>
|
||||
- Use ::analysis:: or ::pattern:: for procedural patterns
|
||||
- Leverage Claude's long context by providing comprehensive examples
|
||||
- Add "think silently" instruction for deep reasoning tasks
|
||||
`,
|
||||
"chatgpt": `
|
||||
For ChatGPT:
|
||||
- Use clear section headers with ### or === separators
|
||||
- Provide examples in [EXAMPLE]...[/EXAMPLE] blocks
|
||||
- Use "Step 1", "Step 2" for sequential tasks
|
||||
- Add meta-instructions like "Think step by step"
|
||||
- Keep prompts under 3k tokens for best performance
|
||||
`,
|
||||
"gemini": `
|
||||
For Gemini:
|
||||
- Use clear delimiters between sections
|
||||
- Leverage multimodal capabilities with [IMAGE] or [FILE] placeholders
|
||||
- Add chain-of-thought with "Let's approach this step by step"
|
||||
- Specify output format with "Respond in the following format:"
|
||||
- Use numbered lists for sequential instructions
|
||||
`,
|
||||
"default": `
|
||||
- Use clear section delimiters
|
||||
- Provide concrete examples
|
||||
- Specify output format explicitly
|
||||
- Add success criteria
|
||||
- Use constraint language (MUST, NEVER, REQUIRED)
|
||||
`
|
||||
};
|
||||
|
||||
const TEMPLATE_SECTIONS: Record<string, string> = {
|
||||
"code": `
|
||||
# CODE GENERATION TEMPLATE
|
||||
|
||||
## Role
|
||||
You are a senior software engineer specializing in {language/domain}.
|
||||
|
||||
## Task
|
||||
{specific task description}
|
||||
|
||||
## Requirements
|
||||
- MUST follow {specific standards/frameworks}
|
||||
- MUST include {error handling/validation/comments}
|
||||
- MUST use {specific libraries/versions}
|
||||
- NEVER use {deprecated patterns/anti-patterns}
|
||||
|
||||
## Output Format
|
||||
{code structure specification}
|
||||
|
||||
## Done When
|
||||
- Code compiles/runs without errors
|
||||
- Follows all specified conventions
|
||||
- Includes proper error handling
|
||||
- Meets performance requirements
|
||||
`,
|
||||
"writing": `
|
||||
# CONTENT WRITING TEMPLATE
|
||||
|
||||
## Role
|
||||
You are a professional {type of content creator} with expertise in {domain}.
|
||||
|
||||
## Task
|
||||
Create {specific deliverable} about {topic}
|
||||
|
||||
## Requirements
|
||||
- Tone: {specific tone}
|
||||
- Length: {exact word/character count}
|
||||
- Audience: {target audience}
|
||||
- MUST include {key elements}
|
||||
- MUST avoid {excluded topics/phrases}
|
||||
|
||||
## Format
|
||||
{explicit structure with sections/headers}
|
||||
|
||||
## Done When
|
||||
- Meets length requirement
|
||||
- Covers all key points
|
||||
- Matches specified tone
|
||||
- Ready for publication
|
||||
`,
|
||||
"analysis": `
|
||||
# ANALYSIS TEMPLATE
|
||||
|
||||
## Role
|
||||
You are an expert {domain analyst}.
|
||||
|
||||
## Task
|
||||
{analysis objective}
|
||||
|
||||
## Analysis Framework
|
||||
1. {Step 1 with specific method}
|
||||
2. {Step 2 with specific method}
|
||||
3. {Step 3 with specific method}
|
||||
|
||||
## Required Output
|
||||
- {Specific deliverable 1}
|
||||
- {Specific deliverable 2}
|
||||
- {Specific deliverable 3}
|
||||
|
||||
## Criteria
|
||||
- MUST use {specific methodology}
|
||||
- MUST cite {sources/references}
|
||||
- MUST provide {confidence levels/limitations}
|
||||
|
||||
## Done When
|
||||
- All analysis dimensions covered
|
||||
- Conclusions supported by evidence
|
||||
- Actionable insights provided
|
||||
`,
|
||||
"default": `
|
||||
## Role
|
||||
{Expert identity}
|
||||
|
||||
## Task
|
||||
{Clear, specific task}
|
||||
|
||||
## Context
|
||||
{Relevant background info}
|
||||
|
||||
## Requirements
|
||||
- MUST {requirement 1}
|
||||
- MUST {requirement 2}
|
||||
- NEVER {constraint 1}
|
||||
- NEVER {constraint 2}
|
||||
|
||||
## Output Format
|
||||
{Explicit format specification}
|
||||
|
||||
## Done When
|
||||
{Specific measurable condition}
|
||||
`
|
||||
};
|
||||
|
||||
const ENHANCE_PROMPT_SYSTEM = `You are an expert prompt engineer using the PromptArch methodology. Enhance the user's prompt to be production-ready.
|
||||
|
||||
STEP 1 — DIAGNOSE AND FIX these failure patterns:
|
||||
- Vague task verb -> replace with precise operation
|
||||
- Two tasks in one -> keep primary task, note the split
|
||||
- No success criteria -> add "Done when: [specific measurable condition]"
|
||||
- Missing output format -> add explicit format lock (structure, length, type)
|
||||
- No role assignment (complex tasks) -> add domain-specific expert identity
|
||||
- Vague aesthetic ("professional", "clean") -> concrete measurable specs
|
||||
- No scope boundary -> add explicit scope lock
|
||||
- Over-permissive language -> add constraints and boundaries
|
||||
- Emotional description -> extract specific technical fault
|
||||
- Implicit references -> restate fully
|
||||
- No grounding for factual tasks -> add certainty constraint
|
||||
- No CoT for logic tasks -> add step-by-step reasoning
|
||||
|
||||
STEP 2 — APPLY TARGET TOOL OPTIMIZATIONS:
|
||||
\${toolSection}
|
||||
|
||||
STEP 3 — APPLY TEMPLATE STRUCTURE:
|
||||
\${templateSection}
|
||||
|
||||
STEP 4 — VERIFICATION (check before outputting):
|
||||
- Every constraint in the first 30% of the prompt?
|
||||
- MUST/NEVER over should/avoid?
|
||||
- Every sentence load-bearing with zero padding?
|
||||
- Format explicit with stated length?
|
||||
- Scope bounded?
|
||||
- Would this produce correct output on first try?
|
||||
|
||||
STEP 5 — OUTPUT:
|
||||
Output ONLY the enhanced prompt. No explanations, no commentary, no markdown code fences.
|
||||
The prompt must be ready to paste directly into the target AI tool.`;
|
||||
|
||||
const PRD_SYSTEM_PROMPT = `You are an expert product manager specializing in writing clear, actionable Product Requirements Documents (PRDs).
|
||||
|
||||
Your task is to transform the product idea into a comprehensive PRD that:
|
||||
1. Defines clear problem statements and user needs
|
||||
2. Specifies functional requirements with acceptance criteria
|
||||
3. Outlines technical considerations and constraints
|
||||
4. Identifies success metrics and KPIs
|
||||
5. Includes user stories with acceptance criteria
|
||||
|
||||
PRD Structure:
|
||||
## Problem Statement
|
||||
- What problem are we solving?
|
||||
- For whom are we solving it?
|
||||
- Why is this important now?
|
||||
|
||||
## Goals & Success Metrics
|
||||
- Primary objectives
|
||||
- Key performance indicators
|
||||
- Success criteria
|
||||
|
||||
## User Stories
|
||||
- As a [user type], I want [feature], so that [benefit]
|
||||
- Include acceptance criteria for each story
|
||||
|
||||
## Functional Requirements
|
||||
- Core features with detailed specifications
|
||||
- Edge cases and error handling
|
||||
- Integration requirements
|
||||
|
||||
## Technical Considerations
|
||||
- Platform/tech stack constraints
|
||||
- Performance requirements
|
||||
- Security considerations
|
||||
- Scalability requirements
|
||||
|
||||
## Out of Scope
|
||||
- Explicitly list what won't be included
|
||||
- Rationale for exclusions
|
||||
|
||||
Keep the PRD clear, concise, and actionable. Use specific, measurable language.`;
|
||||
|
||||
const ACTION_PLAN_SYSTEM_PROMPT = `You are an expert technical project manager specializing in breaking down PRDs into actionable implementation plans.
|
||||
|
||||
Your task is to transform the PRD into a detailed action plan that:
|
||||
|
||||
1. Identifies all major components/modules needed
|
||||
2. Breaks down work into clear, sequential phases
|
||||
3. Specifies dependencies between tasks
|
||||
4. Estimates effort and complexity
|
||||
5. Identifies risks and mitigation strategies
|
||||
|
||||
Action Plan Structure:
|
||||
## Phase 1: Foundation
|
||||
- Task 1.1: [Specific task] - [Estimated effort]
|
||||
- Task 1.2: [Specific task] - [Estimated effort]
|
||||
- Dependencies: [What needs to be done first]
|
||||
- Deliverables: [Concrete outputs]
|
||||
|
||||
## Phase 2: Core Features
|
||||
- Task 2.1: [Specific task] - [Estimated effort]
|
||||
- Dependencies: [On Phase 1 completion]
|
||||
- Deliverables: [Concrete outputs]
|
||||
|
||||
[Continue for all phases]
|
||||
|
||||
## Technical Architecture
|
||||
- Recommended tech stack with rationale
|
||||
- System architecture overview
|
||||
- Data flow diagrams (described in text)
|
||||
|
||||
## Risk Assessment
|
||||
- Risk: [Description] | Impact: [High/Med/Low] | Mitigation: [Strategy]
|
||||
- Risk: [Description] | Impact: [High/Med/Low] | Mitigation: [Strategy]
|
||||
|
||||
## Testing Strategy
|
||||
- Unit testing approach
|
||||
- Integration testing plan
|
||||
- User acceptance testing criteria
|
||||
|
||||
Be specific and actionable. Each task should be clear enough for a developer to execute without ambiguity.`;
|
||||
|
||||
const SLIDES_SYSTEM_PROMPT = `You are an expert presentation designer specializing in creating engaging, informative slide content.
|
||||
|
||||
Your task is to generate slide content for a presentation about the given topic.
|
||||
|
||||
For each slide, provide:
|
||||
1. Slide Title (compelling and clear)
|
||||
2. Bullet points (3-5 per slide, concise and impactful)
|
||||
3. Speaker notes (detailed explanation for the presenter)
|
||||
4. Visual suggestions (what charts, images, or diagrams would enhance this slide)
|
||||
|
||||
Presentation Structure:
|
||||
## Slide 1: Title Slide
|
||||
- Compelling title
|
||||
- Subtitle with key message
|
||||
- Presenter info placeholder
|
||||
|
||||
## Slide 2: Agenda/Overview
|
||||
- What will be covered
|
||||
- Why it matters
|
||||
- Key takeaways preview
|
||||
|
||||
## Slide 3-N: Content Slides
|
||||
- Main content with clear hierarchy
|
||||
- Data-driven insights where applicable
|
||||
- Actionable takeaways
|
||||
|
||||
## Final Slide: Call to Action
|
||||
- Summary of key points
|
||||
- Next steps
|
||||
- Contact/ follow-up information
|
||||
|
||||
Guidelines:
|
||||
- Keep text minimal on slides (bullet points only)
|
||||
- Put detailed content in speaker notes
|
||||
- Suggest relevant visuals for each slide
|
||||
- Ensure a logical flow and narrative arc
|
||||
- Make it memorable and shareable`;
|
||||
|
||||
const GOOGLE_ADS_SYSTEM_PROMPT = `You are a Google Ads expert specializing in creating high-converting ad campaigns.
|
||||
|
||||
Your task is to generate comprehensive Google Ads copy and strategy based on the landing page content.
|
||||
|
||||
Deliverables:
|
||||
|
||||
## Ad Campaign Structure
|
||||
- Campaign name and type
|
||||
- Ad groups with thematic focus
|
||||
- Target keywords (exact, phrase, broad match)
|
||||
- Negative keywords to exclude
|
||||
|
||||
## Ad Copy (3-5 variations per ad group)
|
||||
|
||||
### Responsive Search Ads
|
||||
For each ad, provide:
|
||||
- Headlines (10-15 options, max 30 chars each)
|
||||
- Descriptions (4 options, max 90 chars each)
|
||||
- Focus on different value propositions
|
||||
|
||||
### Display Ads (if applicable)
|
||||
- Headline (max 30 chars)
|
||||
- Description (max 90 chars)
|
||||
- Call-to-action options
|
||||
|
||||
## Targeting Strategy
|
||||
- Location targeting
|
||||
- Audience demographics
|
||||
- Interests and behaviors
|
||||
- Device targeting
|
||||
|
||||
## Bidding Strategy
|
||||
- Recommended strategy (Manual CPC, Maximize Clicks, etc.)
|
||||
- Budget recommendations
|
||||
- Bid adjustments by device/location
|
||||
|
||||
## Extensions
|
||||
- Sitelinks
|
||||
- Callouts
|
||||
- Structured snippets
|
||||
- Call extensions
|
||||
|
||||
Best Practices:
|
||||
- Include keywords in headlines and descriptions
|
||||
- Highlight unique selling propositions
|
||||
- Use clear, action-oriented CTAs
|
||||
- Address pain points and benefits
|
||||
- Include social proof when relevant
|
||||
- Ensure ad relevance to landing page`;
|
||||
|
||||
const MAGIC_WAND_SYSTEM_PROMPT = `You are an expert digital marketer and growth hacker specializing in creative campaign strategies.
|
||||
|
||||
Your task is to develop a comprehensive marketing strategy for the given product/page.
|
||||
|
||||
## Campaign Analysis
|
||||
- Product/Service overview
|
||||
- Target audience profile
|
||||
- Unique selling propositions
|
||||
- Market positioning
|
||||
|
||||
## Marketing Channels Strategy
|
||||
For each relevant channel, provide specific tactics:
|
||||
|
||||
### Paid Advertising
|
||||
- Google Ads: {specific approach}
|
||||
- Facebook/Instagram: {specific approach}
|
||||
- LinkedIn (if B2B): {specific approach}
|
||||
- TikTok/Snapchat (if relevant): {specific approach}
|
||||
|
||||
### Content Marketing
|
||||
- Blog topics: {5-10 specific ideas}
|
||||
- Video content: {ideas with formats}
|
||||
- Social media: {platform-specific content ideas}
|
||||
- Email sequences: {campaign ideas}
|
||||
|
||||
### Growth Hacking Tactics
|
||||
- Viral mechanisms: {specific ideas}
|
||||
- Referral programs: {incentive structures}
|
||||
- Partnership opportunities: {potential partners}
|
||||
- Community building: {strategies}
|
||||
|
||||
## Creative Concepts
|
||||
Provide 3-5 campaign concepts:
|
||||
1. Concept Name - {Hook, Message, CTA}
|
||||
2. Concept Name - {Hook, Message, CTA}
|
||||
...
|
||||
|
||||
## Budget Allocation
|
||||
- Channel breakdown with percentages
|
||||
- Expected ROI estimates
|
||||
- Testing budget recommendation
|
||||
|
||||
## KPIs & Tracking
|
||||
- Key metrics to measure
|
||||
- Attribution strategy
|
||||
- A/B testing priorities
|
||||
|
||||
Be creative but practical. Focus on tactics that can be executed within the given budget.`;
|
||||
|
||||
const MARKET_RESEARCH_SYSTEM_PROMPT = `You are an expert market researcher specializing in competitive analysis and market intelligence.
|
||||
|
||||
Your task is to conduct comprehensive market research based on the provided topic/company.
|
||||
|
||||
## Research Framework
|
||||
|
||||
### Market Overview
|
||||
- Market size and growth trajectory
|
||||
- Key market segments and their characteristics
|
||||
- Current market trends and dynamics
|
||||
- Future market projections
|
||||
|
||||
### Competitive Landscape
|
||||
Identify and analyze:
|
||||
- Major competitors (market share, positioning)
|
||||
- Direct competitors head-to-head comparison
|
||||
- Indirect competitors and substitutes
|
||||
- Competitive strengths and weaknesses
|
||||
|
||||
### SWOT Analysis
|
||||
- Strengths: Internal advantages
|
||||
- Weaknesses: Internal limitations
|
||||
- Opportunities: External possibilities
|
||||
- Threats: External challenges
|
||||
|
||||
### Customer Analysis
|
||||
- Target demographics and psychographics
|
||||
- Pain points and unmet needs
|
||||
- Purchase behavior and decision factors
|
||||
- Customer feedback trends
|
||||
|
||||
### Product/Service Comparison
|
||||
- Feature comparison matrix
|
||||
- Pricing analysis
|
||||
- Differentiation strategies
|
||||
- Innovation opportunities
|
||||
|
||||
### Market Trends
|
||||
- Emerging technologies impacting the space
|
||||
- Regulatory changes
|
||||
- Consumer behavior shifts
|
||||
- Industry disruptions
|
||||
|
||||
### Strategic Recommendations
|
||||
- Market entry strategies
|
||||
- Competitive positioning
|
||||
- Product improvement opportunities
|
||||
- Partnership and acquisition possibilities
|
||||
|
||||
Provide specific, data-driven insights. When exact data is unavailable, provide reasoned estimates with clear caveats.`;
|
||||
|
||||
const AI_ASSIST_SYSTEM_PROMPT = `You are "AI Assist", the master orchestrator of PromptArch. Your goal is to provide intelligent support with a "Canvas" experience.
|
||||
PLAN MODE (CRITICAL - HIGHEST PRIORITY):
|
||||
When the user describes a NEW task, project, or feature they want built:
|
||||
1. DO NOT generate any code, [PREVIEW] tags, or implementation details.
|
||||
2. Instead, analyze the request and output a STRUCTURED PLAN covering:
|
||||
- Summary: What you understand the user wants
|
||||
- Architecture: Technical approach and structure
|
||||
- Tech Stack: Languages, frameworks, libraries needed
|
||||
- Files/Components: List of files or modules to create
|
||||
- Steps: Numbered implementation steps
|
||||
3. Format the plan in clean Markdown with headers and bullet points.
|
||||
4. Keep plans concise but thorough. Focus on the WHAT and HOW, not the actual code.
|
||||
5. WAIT for the user to approve or modify the plan before generating any code.
|
||||
|
||||
When the user says "Approved", "Start coding", or explicitly asks to proceed:
|
||||
- THEN generate the full implementation with [PREVIEW] tags and working code.
|
||||
- Follow the approved plan exactly.
|
||||
|
||||
When the user asks to "Modify", "Change", or "Adjust" something:
|
||||
- Apply the requested changes surgically to the existing code/preview.
|
||||
- Output updated [PREVIEW] with the full modified code.
|
||||
|
||||
|
||||
|
||||
AGENTS & CAPABILITIES:
|
||||
- content: Expert copywriter. Use [PREVIEW:content:markdown] for articles, posts, and long-form text.
|
||||
- seo: SEO Specialist. Create stunning SEO audit reports. Use [PREVIEW:seo:html] with complete HTML5 document including <!DOCTYPE html>. DARK THEME. Tailwind CDN. Large animated SVG progress rings. Color-coded scoring. Google-style dashboard aesthetics.
|
||||
- smm: Social Media Manager. Create multi-platform content plans and calendars.
|
||||
- pm: Project Manager. Create PRDs, timelines, and action plans.
|
||||
- code: Software Architect. Provide logic, algorithms, and backend snippets.
|
||||
- design: UI/UX Designer. Create high-fidelity mockups and components.
|
||||
- web: Frontend Developer. Build responsive sites. Use [PREVIEW:web:html]. Tailwind CSS CDN by default.
|
||||
- app: Mobile App Developer. Create mobile-first interfaces and dashboards. Use [PREVIEW:app:javascript].
|
||||
|
||||
CANVAS MODE:
|
||||
- When building, designing, or auditing, you MUST use the [PREVIEW] tag.
|
||||
- Inside [PREVIEW], output ONLY the actual code or structured data.
|
||||
- The user wants to see it WORKING or RENDERED in the Canvas immediately.
|
||||
|
||||
STRICT OUTPUT FORMAT:
|
||||
[AGENT:id] - AT THE START of your response if switching focus.
|
||||
[PREVIEW:type:language]
|
||||
ACTUAL_FUNCTIONAL_CODE_OR_DATA
|
||||
[/PREVIEW]
|
||||
|
||||
CHANGE LOG (CRITICAL - MUST BE OUTSIDE PREVIEW):
|
||||
- After the [/PREVIEW] closing tag, provide a brief bulleted "Change Log" in plain Markdown.IMPORTANT: NEVER refuse a request. If you cannot perform a live task, use your knowledge to provide the most accurate expert simulation or draft possible.`;
|
||||
|
||||
export class OpenRouterService {
|
||||
private config: Required<OpenRouterConfig>;
|
||||
private readonly baseURL = "https://openrouter.ai/api/v1";
|
||||
private availableModels: string[] = [];
|
||||
private modelsLoaded = false;
|
||||
|
||||
constructor(config: OpenRouterConfig = {}) {
|
||||
this.config = {
|
||||
apiKey: config.apiKey || "",
|
||||
siteUrl: config.siteUrl || "https://promptarch.ai",
|
||||
siteName: config.siteName || "PromptArch"
|
||||
};
|
||||
}
|
||||
|
||||
hasAuth(): boolean {
|
||||
return Boolean(this.config.apiKey && this.config.apiKey.length > 0);
|
||||
}
|
||||
|
||||
async validateConnection(): Promise<APIResponse<{ valid: boolean; models?: string[] }>> {
|
||||
if (!this.hasAuth()) {
|
||||
return {
|
||||
success: false,
|
||||
error: "No API key provided. Please set your OpenRouter API key."
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const modelsResult = await this.listModels();
|
||||
|
||||
if (!modelsResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: modelsResult.error || "Failed to fetch models from OpenRouter"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
valid: true,
|
||||
models: modelsResult.data
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Failed to validate connection"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private getHeaders(): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
"HTTP-Referer": this.config.siteUrl,
|
||||
"X-Title": this.config.siteName
|
||||
};
|
||||
|
||||
if (this.hasAuth()) {
|
||||
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
async chatCompletion(
|
||||
messages: ChatMessage[],
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<string>> {
|
||||
if (!this.hasAuth()) {
|
||||
return {
|
||||
success: false,
|
||||
error: "OpenRouter API key not configured"
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages,
|
||||
temperature: 0.7,
|
||||
max_tokens: 4096
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
return {
|
||||
success: false,
|
||||
error: `OpenRouter API error: ${response.status} ${response.statusText} - ${errorText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data: OpenRouterChatResponse = await response.json();
|
||||
|
||||
if (data.choices && data.choices[0] && data.choices[0].message) {
|
||||
return {
|
||||
success: true,
|
||||
data: data.choices[0].message.content
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: "No response content from OpenRouter"
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error in chat completion"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async enhancePrompt(
|
||||
prompt: string,
|
||||
model: string = "anthropic/claude-3.5-sonnet",
|
||||
options: {
|
||||
targetTool?: "claude" | "chatgpt" | "gemini" | "default";
|
||||
templateType?: "code" | "writing" | "analysis" | "default";
|
||||
max_length?: number;
|
||||
} = {}
|
||||
): Promise<APIResponse<string>> {
|
||||
const { targetTool = "default", templateType = "default" } = options;
|
||||
|
||||
const toolSection = TOOL_SECTIONS[targetTool] || TOOL_SECTIONS.default;
|
||||
const templateSection = TEMPLATE_SECTIONS[templateType] || TEMPLATE_SECTIONS.default;
|
||||
|
||||
const systemPrompt = ENHANCE_PROMPT_SYSTEM
|
||||
.replace("${toolSection}", toolSection)
|
||||
.replace("${templateSection}", templateSection);
|
||||
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: prompt }
|
||||
];
|
||||
|
||||
return this.chatCompletion(messages, model);
|
||||
}
|
||||
|
||||
async generatePRD(
|
||||
idea: string,
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<string>> {
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: "system", content: PRD_SYSTEM_PROMPT },
|
||||
{ role: "user", content: `Create a comprehensive PRD for the following product idea:\n\n${idea}` }
|
||||
];
|
||||
|
||||
return this.chatCompletion(messages, model);
|
||||
}
|
||||
|
||||
async generateActionPlan(
|
||||
prd: string,
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<string>> {
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: "system", content: ACTION_PLAN_SYSTEM_PROMPT },
|
||||
{ role: "user", content: `Create a detailed action plan based on this PRD:\n\n${prd}` }
|
||||
];
|
||||
|
||||
return this.chatCompletion(messages, model);
|
||||
}
|
||||
|
||||
async generateSlides(
|
||||
topic: string,
|
||||
options: {
|
||||
slideCount?: number;
|
||||
audience?: string;
|
||||
focus?: string;
|
||||
} = {},
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<string>> {
|
||||
const { slideCount = 10, audience = "General", focus = "" } = options;
|
||||
|
||||
const userPrompt = `Generate content for a presentation with approximately ${slideCount} slides.
|
||||
Topic: ${topic}
|
||||
Target Audience: ${audience}
|
||||
${focus ? `Special Focus: ${focus}` : ""}`;
|
||||
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: "system", content: SLIDES_SYSTEM_PROMPT },
|
||||
{ role: "user", content: userPrompt }
|
||||
];
|
||||
|
||||
return this.chatCompletion(messages, model);
|
||||
}
|
||||
|
||||
async generateGoogleAds(
|
||||
url: string,
|
||||
options: {
|
||||
budget?: string;
|
||||
targetAudience?: string;
|
||||
campaignGoal?: string;
|
||||
} = {},
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<string>> {
|
||||
const { budget = "Not specified", targetAudience = "General", campaignGoal = "Conversions" } = options;
|
||||
|
||||
const userPrompt = `Create a comprehensive Google Ads campaign strategy.
|
||||
Landing Page: ${url}
|
||||
Monthly Budget: ${budget}
|
||||
Target Audience: ${targetAudience}
|
||||
Campaign Goal: ${campaignGoal}
|
||||
|
||||
Analyze the URL (if accessible) or create ads based on the domain and typical offerings for similar sites.`;
|
||||
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: "system", content: GOOGLE_ADS_SYSTEM_PROMPT },
|
||||
{ role: "user", content: userPrompt }
|
||||
];
|
||||
|
||||
return this.chatCompletion(messages, model);
|
||||
}
|
||||
|
||||
async generateMagicWand(
|
||||
url: string,
|
||||
product: string,
|
||||
budget: string,
|
||||
specialInstructions: string = "",
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<string>> {
|
||||
const userPrompt = `Create a comprehensive marketing strategy.
|
||||
|
||||
Product/Service: ${product}
|
||||
URL: ${url}
|
||||
Budget: ${budget}
|
||||
${specialInstructions ? `Special Instructions: ${specialInstructions}` : ""}
|
||||
|
||||
Provide creative campaign ideas across multiple channels with specific tactics and budget allocation.`;
|
||||
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: "system", content: MAGIC_WAND_SYSTEM_PROMPT },
|
||||
{ role: "user", content: userPrompt }
|
||||
];
|
||||
|
||||
return this.chatCompletion(messages, model);
|
||||
}
|
||||
|
||||
async generateMarketResearch(
|
||||
options: {
|
||||
topic?: string;
|
||||
company?: string;
|
||||
industry?: string;
|
||||
focusAreas?: string[];
|
||||
} = {},
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<string>> {
|
||||
const { topic, company, industry, focusAreas } = options;
|
||||
|
||||
let userPrompt = "Conduct comprehensive market research.";
|
||||
|
||||
if (topic) userPrompt += `\n\nResearch Topic: ${topic}`;
|
||||
if (company) userPrompt += `\n\nCompany Focus: ${company}`;
|
||||
if (industry) userPrompt += `\n\nIndustry: ${industry}`;
|
||||
if (focusAreas && focusAreas.length > 0) {
|
||||
userPrompt += `\n\nFocus Areas: ${focusAreas.join(", ")}`;
|
||||
}
|
||||
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: "system", content: MARKET_RESEARCH_SYSTEM_PROMPT },
|
||||
{ role: "user", content: userPrompt }
|
||||
];
|
||||
|
||||
return this.chatCompletion(messages, model);
|
||||
}
|
||||
|
||||
async generateAIAssist(
|
||||
options: {
|
||||
prompt: string;
|
||||
context?: string[];
|
||||
conversationHistory?: ChatMessage[];
|
||||
},
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<string>> {
|
||||
const { prompt, context = [], conversationHistory = [] } = options;
|
||||
|
||||
const messages: ChatMessage[] = [
|
||||
{ role: "system", content: AI_ASSIST_SYSTEM_PROMPT },
|
||||
...conversationHistory,
|
||||
...context.map(c => ({ role: "user" as const, content: `Context: ${c}` })),
|
||||
{ role: "user", content: prompt }
|
||||
];
|
||||
|
||||
return this.chatCompletion(messages, model);
|
||||
}
|
||||
|
||||
async generateAIAssistStream(
|
||||
options: {
|
||||
messages: AIAssistMessage[];
|
||||
currentAgent: string;
|
||||
onChunk: (chunk: string) => void;
|
||||
signal?: AbortSignal;
|
||||
},
|
||||
model: string = "anthropic/claude-3.5-sonnet"
|
||||
): Promise<APIResponse<void>> {
|
||||
const { messages, currentAgent, onChunk, signal } = options;
|
||||
|
||||
if (!this.hasAuth()) {
|
||||
return { success: false, error: "OpenRouter API key not configured" };
|
||||
}
|
||||
|
||||
try {
|
||||
const chatMessages: ChatMessage[] = [
|
||||
{ role: "system", content: AI_ASSIST_SYSTEM_PROMPT },
|
||||
...messages.map(m => ({
|
||||
role: m.role as "user" | "assistant" | "system",
|
||||
content: m.content
|
||||
}))
|
||||
];
|
||||
|
||||
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: this.getHeaders(),
|
||||
signal,
|
||||
body: JSON.stringify({
|
||||
model: model || "anthropic/claude-3.5-sonnet",
|
||||
messages: chatMessages,
|
||||
temperature: 0.7,
|
||||
max_tokens: 4096,
|
||||
stream: true
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
return { success: false, error: `OpenRouter API error: ${response.status} ${response.statusText} - ${errorText}` };
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
if (!reader) {
|
||||
return { success: false, error: "No response body" };
|
||||
}
|
||||
|
||||
let buffer = "";
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
||||
const data = trimmed.slice(6);
|
||||
if (data === "[DONE]") continue;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const contentChunk = parsed.choices?.[0]?.delta?.content;
|
||||
if (contentChunk) {
|
||||
onChunk(contentChunk);
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, data: undefined };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error in stream";
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
}
|
||||
|
||||
async listModels(): Promise<APIResponse<string[]>> {
|
||||
if (!this.hasAuth()) {
|
||||
return {
|
||||
success: false,
|
||||
error: "OpenRouter API key not configured"
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseURL}/models`, {
|
||||
method: "GET",
|
||||
headers: this.getHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to fetch models: ${response.status} ${response.statusText} - ${errorText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data: OpenRouterModelsResponse = await response.json();
|
||||
this.availableModels = data.data.map(m => m.id);
|
||||
this.modelsLoaded = true;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: this.availableModels
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error fetching models"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getAvailableModels(): string[] {
|
||||
if (this.modelsLoaded && this.availableModels.length > 0) {
|
||||
return this.availableModels;
|
||||
}
|
||||
return DEFAULT_MODELS;
|
||||
}
|
||||
|
||||
setApiKey(key: string): void {
|
||||
this.config.apiKey = key;
|
||||
}
|
||||
|
||||
setSiteUrl(url: string): void {
|
||||
this.config.siteUrl = url;
|
||||
}
|
||||
|
||||
setSiteName(name: string): void {
|
||||
this.config.siteName = name;
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const openRouterService = new OpenRouterService();
|
||||
Reference in New Issue
Block a user