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:
admin
2026-03-18 18:45:37 +00:00
Unverified
parent cca11fe07a
commit a4b7a0d9e4
17 changed files with 3189 additions and 358 deletions

View File

@@ -2,6 +2,7 @@ import type { ModelProvider, APIResponse, ChatMessage, AIAssistMessage } from "@
import OllamaCloudService from "./ollama-cloud";
import ZaiPlanService from "./zai-plan";
import qwenOAuthService, { QwenOAuthConfig, QwenOAuthToken } from "./qwen-oauth";
import { OpenRouterService } from "./openrouter";
export interface ModelAdapterConfig {
qwen?: QwenOAuthConfig;
@@ -14,17 +15,22 @@ export interface ModelAdapterConfig {
generalEndpoint?: string;
codingEndpoint?: string;
};
openrouter?: {
apiKey?: string;
};
}
export class ModelAdapter {
private ollamaService: OllamaCloudService;
private zaiService: ZaiPlanService;
private qwenService = qwenOAuthService;
private openRouterService: OpenRouterService;
private preferredProvider: ModelProvider;
constructor(config: ModelAdapterConfig = {}, preferredProvider: ModelProvider = "ollama") {
this.ollamaService = new OllamaCloudService(config.ollama);
this.zaiService = new ZaiPlanService(config.zai);
this.openRouterService = new OpenRouterService(config.openrouter);
this.preferredProvider = preferredProvider;
if (config.qwen) {
@@ -62,6 +68,10 @@ export class ModelAdapter {
this.qwenService.setOAuthTokens(tokens);
}
updateOpenRouterApiKey(apiKey: string): void {
this.openRouterService = new OpenRouterService({ apiKey });
}
async startQwenOAuth(): Promise<QwenOAuthToken> {
return await this.qwenService.signIn();
}
@@ -90,6 +100,8 @@ export class ModelAdapter {
return this.ollamaService.hasAuth();
case "zai":
return this.zaiService.hasAuth();
case "openrouter":
return this.openRouterService.hasAuth();
default:
return false;
}
@@ -114,6 +126,8 @@ export class ModelAdapter {
return this.ollamaService;
case "zai":
return this.zaiService;
case "openrouter":
return this.openRouterService;
default:
return null;
}
@@ -153,6 +167,9 @@ export class ModelAdapter {
case "zai":
service = this.zaiService;
break;
case "openrouter":
service = this.openRouterService;
break;
}
const result = await operation(service);
@@ -183,26 +200,26 @@ export class ModelAdapter {
};
}
async enhancePrompt(prompt: string, provider?: ModelProvider, model?: string): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
async enhancePrompt(prompt: string, provider?: ModelProvider, model?: string, options?: { toolCategory?: string; template?: string; diagnostics?: string }): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.enhancePrompt(prompt, model), providers);
return this.callWithFallback((service) => service.enhancePrompt(prompt, model, options), providers);
}
async generatePRD(idea: string, provider?: ModelProvider, model?: string): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.generatePRD(idea, model), providers);
}
async generateActionPlan(prd: string, provider?: ModelProvider, model?: string): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.generateActionPlan(prd, model), providers);
}
async generateUXDesignerPrompt(appDescription: string, provider?: ModelProvider, model?: string): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.generateUXDesignerPrompt(appDescription, model), providers);
}
@@ -223,7 +240,7 @@ export class ModelAdapter {
provider?: ModelProvider,
model?: string
): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.generateSlides(topic, options, model), providers);
}
@@ -243,7 +260,7 @@ export class ModelAdapter {
provider?: ModelProvider,
model?: string
): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.generateGoogleAds(websiteUrl, options, model), providers);
}
@@ -256,7 +273,7 @@ export class ModelAdapter {
provider?: ModelProvider,
model?: string
): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.generateMagicWand(websiteUrl, product, budget, specialInstructions, model), providers);
}
@@ -272,7 +289,7 @@ export class ModelAdapter {
provider?: ModelProvider,
model?: string
): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.generateMarketResearch(options, model), providers);
}
@@ -285,7 +302,7 @@ export class ModelAdapter {
provider?: ModelProvider,
model?: string
): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
return this.callWithFallback((service) => service.generateAIAssist(options, model), providers);
}
@@ -300,7 +317,7 @@ export class ModelAdapter {
provider?: ModelProvider,
model?: string
): Promise<APIResponse<void>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai", "openrouter");
const providers: ModelProvider[] = provider ? [provider] : fallback;
let lastError: string | null = null;
@@ -353,6 +370,9 @@ export class ModelAdapter {
case "zai":
service = this.zaiService;
break;
case "openrouter":
service = this.openRouterService;
break;
}
return await service.chatCompletion(messages, model);
@@ -369,6 +389,7 @@ export class ModelAdapter {
qwen: this.qwenService.getAvailableModels(),
ollama: ["gpt-oss:120b", "llama3.1", "gemma3", "deepseek-r1", "qwen3"],
zai: ["glm-4.7", "glm-4.5", "glm-4.5-air", "glm-4-flash", "glm-4-flashx"],
openrouter: ["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 models: Record<ModelProvider, string[]> = { ...fallbackModels };
@@ -404,6 +425,8 @@ export class ModelAdapter {
return this.ollamaService.getAvailableModels();
case "zai":
return this.zaiService.getAvailableModels();
case "openrouter":
return this.openRouterService.getAvailableModels();
default:
return [];
}