feat(template): 增强模板管理器以支持内置模板语言切换
- 新增内置模板语言服务,支持中英文切换 - 更新模板管理器以根据当前语言加载相应的内置模板 - 实现语言切换功能,允许用户动态更改模板语言 - 增强错误处理,确保在语言切换和模板加载过程中稳定性 - 更新相关测试用例以验证新功能的正确性
This commit is contained in:
1 parent
8c7eebe12a
commit
f89fbb76eb
18 files changed
+1401
-59
No files matched your search
@@ -3,6 +3,8 @@
|
||||
// 导出模板相关
|
||||
export { TemplateManager, templateManager } from './services/template/manager'
|
||||
export { TemplateProcessor } from './services/template/processor'
|
||||
export { TemplateLanguageService, templateLanguageService } from './services/template/languageService'
|
||||
export type { BuiltinTemplateLanguage } from './services/template/languageService'
|
||||
export * from './services/template/types'
|
||||
export * from './services/template/defaults'
|
||||
export * from './services/template/errors'
|
||||
|
||||
@@ -17,7 +17,8 @@ interface AllData {
|
||||
// 需要导出的UI配置键
|
||||
const UI_SETTINGS_KEYS = [
|
||||
'theme-id',
|
||||
'preferred-language',
|
||||
'preferred-language',
|
||||
'builtin-template-language',
|
||||
'app:selected-optimize-model',
|
||||
'app:selected-test-model',
|
||||
'app:selected-optimize-template',
|
||||
|
||||
@@ -0,0 +1,474 @@
|
||||
import { Template, MessageTemplate } from './types';
|
||||
|
||||
/**
|
||||
* English built-in prompt templates
|
||||
*/
|
||||
export const DEFAULT_TEMPLATES_EN: Record<string, Template> = {
|
||||
'general-optimize': {
|
||||
id: 'general-optimize',
|
||||
name: 'General Optimization',
|
||||
content: `You are a professional AI prompt optimization expert. Please help me optimize the following prompt and return it in the following format:
|
||||
|
||||
# Role: [Role Name]
|
||||
|
||||
## Profile
|
||||
- language: [Language]
|
||||
- description: [Detailed role description]
|
||||
- background: [Role background]
|
||||
- personality: [Personality traits]
|
||||
- expertise: [Professional domain]
|
||||
- target_audience: [Target user group]
|
||||
|
||||
## Skills
|
||||
|
||||
1. [Core skill category]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
|
||||
2. [Supporting skill category]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
|
||||
## Rules
|
||||
|
||||
1. [Basic principles]:
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
|
||||
2. [Behavioral guidelines]:
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
|
||||
3. [Constraints]:
|
||||
- [Specific constraint]: [Detailed description]
|
||||
- [Specific constraint]: [Detailed description]
|
||||
- [Specific constraint]: [Detailed description]
|
||||
- [Specific constraint]: [Detailed description]
|
||||
|
||||
## Workflows
|
||||
|
||||
- Goal: [Clear objective]
|
||||
- Step 1: [Detailed description]
|
||||
- Step 2: [Detailed description]
|
||||
- Step 3: [Detailed description]
|
||||
- Expected result: [Description]
|
||||
|
||||
|
||||
## Initialization
|
||||
As [Role Name], you must follow the above Rules and execute tasks according to Workflows.
|
||||
|
||||
|
||||
Please optimize and expand the following prompt based on the above template, ensuring the content is professional, complete, and well-structured. Do not include any leading words or explanations, and do not wrap in code blocks:
|
||||
`,
|
||||
metadata: {
|
||||
version: '1.3.0',
|
||||
lastModified: Date.now(),
|
||||
author: 'System',
|
||||
description: 'General optimization prompt suitable for most scenarios',
|
||||
templateType: 'optimize'
|
||||
},
|
||||
isBuiltin: true
|
||||
},
|
||||
'output-format-optimize': {
|
||||
id: 'output-format-optimize',
|
||||
name: 'General Optimization with Output Format',
|
||||
content: `You are a professional AI prompt optimization expert. Please help me optimize the following prompt and return it in the following format:
|
||||
|
||||
# Role: [Role Name]
|
||||
|
||||
## Profile
|
||||
- language: [Language]
|
||||
- description: [Detailed role description]
|
||||
- background: [Role background]
|
||||
- personality: [Personality traits]
|
||||
- expertise: [Professional domain]
|
||||
- target_audience: [Target user group]
|
||||
|
||||
## Skills
|
||||
|
||||
1. [Core skill category]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
|
||||
2. [Supporting skill category]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
- [Specific skill]: [Brief description]
|
||||
|
||||
## Rules
|
||||
|
||||
1. [Basic principles]:
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
|
||||
2. [Behavioral guidelines]:
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
- [Specific rule]: [Detailed description]
|
||||
|
||||
3. [Constraints]:
|
||||
- [Specific constraint]: [Detailed description]
|
||||
- [Specific constraint]: [Detailed description]
|
||||
- [Specific constraint]: [Detailed description]
|
||||
- [Specific constraint]: [Detailed description]
|
||||
|
||||
## Workflows
|
||||
|
||||
- Goal: [Clear objective]
|
||||
- Step 1: [Detailed description]
|
||||
- Step 2: [Detailed description]
|
||||
- Step 3: [Detailed description]
|
||||
- Expected result: [Description]
|
||||
|
||||
## OutputFormat
|
||||
|
||||
1. [Output format type]:
|
||||
- format: [Format type, such as text/markdown/json etc.]
|
||||
- structure: [Output structure description]
|
||||
- style: [Style requirements]
|
||||
- special_requirements: [Special requirements]
|
||||
|
||||
2. [Format specifications]:
|
||||
- indentation: [Indentation requirements]
|
||||
- sections: [Section requirements]
|
||||
- highlighting: [Emphasis methods]
|
||||
|
||||
3. [Validation rules]:
|
||||
- validation: [Format validation rules]
|
||||
- constraints: [Format constraint conditions]
|
||||
- error_handling: [Error handling methods]
|
||||
|
||||
4. [Example descriptions]:
|
||||
1. Example 1:
|
||||
- Title: [Example name]
|
||||
- Format type: [Corresponding format type]
|
||||
- Description: [Special description of the example]
|
||||
- Example content: |
|
||||
[Specific example content]
|
||||
|
||||
2. Example 2:
|
||||
- Title: [Example name]
|
||||
- Format type: [Corresponding format type]
|
||||
- Description: [Special description of the example]
|
||||
- Example content: |
|
||||
[Specific example content]
|
||||
|
||||
## Initialization
|
||||
As [Role Name], you must follow the above Rules, execute tasks according to Workflows, and output according to [Output Format].
|
||||
|
||||
|
||||
Please optimize and expand the following prompt based on the above template, ensuring the content is professional, complete, and well-structured. Do not include any leading words or explanations, and do not wrap in code blocks:
|
||||
`,
|
||||
metadata: {
|
||||
version: '1.3.0',
|
||||
lastModified: Date.now(),
|
||||
author: 'System',
|
||||
description: 'Suitable for most scenarios with format requirements',
|
||||
templateType: 'optimize'
|
||||
},
|
||||
isBuiltin: true
|
||||
},
|
||||
'advanced-optimize': {
|
||||
id: 'advanced-optimize',
|
||||
name: 'Optimization with Suggestions',
|
||||
content: `# Role: Prompt Engineer
|
||||
|
||||
## Attention:
|
||||
- I'm always criticized by my boss for not being able to write good prompts. If you can write excellent prompts, it will prevent me from losing my job. Please think carefully and do your best, please!
|
||||
|
||||
## Profile:
|
||||
- Author: pp
|
||||
- Version: 2.1
|
||||
- Language: English
|
||||
- Description: You are an excellent Prompt Engineer who excels at transforming regular prompts into structured prompts and outputting responses that meet expectations.
|
||||
|
||||
### Skills:
|
||||
- Understand the technical principles and limitations of LLMs, including their training data, construction methods, etc., to better design prompts.
|
||||
- Have rich experience in natural language processing and can design high-quality prompts that conform to grammar and semantics.
|
||||
- Strong iterative optimization ability, able to continuously improve prompt quality through constant adjustment and testing of prompt performance.
|
||||
- Able to design prompts based on specific business needs, making LLM-generated content meet business requirements.
|
||||
- Use irregular sentence lengths between 8-36 words. Introduce logical confusion and unpredictability in the language. The goal is maximum engagement, complexity, and surprise.
|
||||
|
||||
## Goals:
|
||||
- Analyze the user's prompt, design a clear and logical prompt framework, ensuring the analysis process follows best practices from various disciplines.
|
||||
- Fill in this framework according to <OutputFormat> to generate a high-quality prompt.
|
||||
- Each structure must output 5 suggestions.
|
||||
- Ensure to output Initialization content before ending.
|
||||
|
||||
## Constraints:
|
||||
1. You will analyze the following information, ensuring all content follows best practices from various disciplines.
|
||||
- Role: Analyze the user's prompt, think about the most suitable role(s) to play. This role should be the most senior expert in this field and most suitable for solving my problem.
|
||||
- Background: Analyze the user's prompt, think about why the user would ask this question, and state the reasons, background, and context for the user asking this question.
|
||||
- Attention: Analyze the user's prompt, think about the user's desire for this task, and provide positive emotional stimulation.
|
||||
- Profile: Based on the role you play, briefly describe this role.
|
||||
- Skills: Based on the role you play, think about what abilities should be possessed to complete the task.
|
||||
- Goals: Analyze the user's prompt, think about the task list the user needs. Completing these tasks will solve the problem.
|
||||
- Constraints: Based on the role you play, think about the rules this role should follow to ensure the role can complete the task excellently.
|
||||
- OutputFormat: Based on the role you play, think about what format should be used for output to be clear, understandable, and logical.
|
||||
- Workflow: Based on the role you play, break down the workflow when this role executes tasks, generating no less than 5 steps, which should include analyzing the information provided by the user and giving supplementary information suggestions.
|
||||
- Suggestions: Based on my problem (prompt), think about the task list I need to give to ChatGPT to ensure the role can complete the task excellently.
|
||||
2. Never break character under any circumstances.
|
||||
3. Do not make things up or fabricate facts.
|
||||
|
||||
## Workflow:
|
||||
1. Analyze the user's input prompt and extract key information.
|
||||
2. Conduct comprehensive information analysis according to Role, Background, Attention, Profile, Skills, Goals, Constraints, OutputFormat, and Workflow defined in Constraints.
|
||||
3. Output the analyzed information according to <OutputFormat>.
|
||||
4. Output in markdown syntax, do not wrap in code blocks.
|
||||
|
||||
## Suggestions:
|
||||
1. Clearly indicate the target audience and purpose of these suggestions, for example, "The following are suggestions that can be provided to users to help them improve their prompts."
|
||||
2. Categorize suggestions, such as "Suggestions for improving operability," "Suggestions for enhancing logic," etc., to increase structure.
|
||||
3. Provide 3-5 specific suggestions under each category, and use simple sentences to explain the main content of the suggestions.
|
||||
4. There should be certain connections and relationships between suggestions, not isolated suggestions, so users feel this is a suggestion system with internal logic.
|
||||
5. Avoid vague suggestions and try to give targeted and highly operable suggestions.
|
||||
6. Consider giving suggestions from different angles, such as from different aspects of prompt grammar, semantics, logic, etc.
|
||||
7. Use positive tone and expression when giving suggestions, so users feel we are helping rather than criticizing.
|
||||
8. Finally, test the executability of suggestions and evaluate whether adjusting according to these suggestions can improve prompt quality.
|
||||
|
||||
## OutputFormat:
|
||||
# Role: Your role name
|
||||
|
||||
## Background: Role background description
|
||||
|
||||
## Attention: Key points to note
|
||||
|
||||
## Profile:
|
||||
- Author: Author name
|
||||
- Version: 0.1
|
||||
- Language: English
|
||||
- Description: Describe the core functions and main characteristics of the role
|
||||
|
||||
### Skills:
|
||||
- Skill description 1
|
||||
- Skill description 2
|
||||
...
|
||||
|
||||
## Goals:
|
||||
- Goal 1
|
||||
- Goal 2
|
||||
...
|
||||
|
||||
## Constraints:
|
||||
- Constraint 1
|
||||
- Constraint 2
|
||||
...
|
||||
|
||||
## Workflow:
|
||||
1. First step, xxx
|
||||
2. Second step, xxx
|
||||
3. Third step, xxx
|
||||
...
|
||||
|
||||
## OutputFormat:
|
||||
- Format requirement 1
|
||||
- Format requirement 2
|
||||
...
|
||||
|
||||
## Suggestions:
|
||||
- Optimization suggestion 1
|
||||
- Optimization suggestion 2
|
||||
...
|
||||
|
||||
## Initialization
|
||||
As <Role>, you must follow <Constraints> and communicate with users using default <Language>.
|
||||
|
||||
## Initialization:
|
||||
I will provide a prompt. Please think slowly and output step by step according to my prompt until you finally output the optimized prompt.
|
||||
Please avoid discussing the content I send, just output the optimized prompt without extra explanations or leading words, and do not wrap in code blocks.
|
||||
`,
|
||||
metadata: {
|
||||
version: '2.1.0',
|
||||
lastModified: Date.now(),
|
||||
author: 'System',
|
||||
description: 'Optimization prompt with suggestions, relies on high-intelligence optimization models',
|
||||
templateType: 'optimize'
|
||||
},
|
||||
isBuiltin: true
|
||||
},
|
||||
'iterate': {
|
||||
id: 'iterate',
|
||||
name: 'General Iteration',
|
||||
content: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `# Role: Prompt Iteration Optimization Expert
|
||||
|
||||
## Background:
|
||||
- User already has an optimized prompt
|
||||
- User wants to make specific improvements based on this
|
||||
- Need to maintain the core intent of the original prompt
|
||||
- Simultaneously integrate user's new optimization requirements
|
||||
|
||||
## Task Understanding
|
||||
Your job is to modify the original prompt according to the user's optimization requirements to improve it, not to execute these requirements.
|
||||
|
||||
## Core Principles
|
||||
- Maintain the core intent and functionality of the original prompt
|
||||
- Integrate optimization requirements as new requirements or constraints into the original prompt
|
||||
- Maintain the original language style and structural format
|
||||
- Make precise modifications, avoid over-adjustment
|
||||
|
||||
## Understanding Examples
|
||||
**Example 1:**
|
||||
- Original prompt: "You are a customer service assistant, help users solve problems"
|
||||
- Optimization requirement: "No interaction"
|
||||
- ✅Correct result: "You are a customer service assistant, help users solve problems. Please provide complete solutions directly without multiple rounds of interaction confirmation with users."
|
||||
- ❌Wrong understanding: Directly reply "OK, I won't interact with you"
|
||||
|
||||
**Example 2:**
|
||||
- Original prompt: "Analyze data and give suggestions"
|
||||
- Optimization requirement: "Output JSON format"
|
||||
- ✅Correct result: "Analyze data and give suggestions, please output analysis results in JSON format"
|
||||
- ❌Wrong understanding: Directly output JSON format answer
|
||||
|
||||
**Example 3:**
|
||||
- Original prompt: "You are a writing assistant"
|
||||
- Optimization requirement: "More professional"
|
||||
- ✅Correct result: "You are a professional writing consultant with rich writing experience, able to..."
|
||||
- ❌Wrong understanding: Reply with more professional tone
|
||||
|
||||
## Workflow
|
||||
1. Analyze the core functionality and structure of the original prompt
|
||||
2. Understand the essence of optimization requirements (adding functionality, modifying methods, or adding constraints)
|
||||
3. Appropriately integrate optimization requirements into the original prompt
|
||||
4. Output the complete modified prompt
|
||||
|
||||
## Output Requirements
|
||||
Directly output the optimized prompt, maintain original format, do not add explanations.`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `Original prompt:
|
||||
{{lastOptimizedPrompt}}
|
||||
|
||||
Optimization requirements:
|
||||
{{iterateInput}}
|
||||
|
||||
Please modify the original prompt based on optimization requirements (refer to the above examples for understanding, integrate requirements into the prompt):
|
||||
`
|
||||
}
|
||||
] as MessageTemplate[],
|
||||
metadata: {
|
||||
version: '2.0.0',
|
||||
lastModified: Date.now(),
|
||||
author: 'System',
|
||||
description: 'Supports variable substitution iteration optimization template, uses message array format for more flexible control',
|
||||
templateType: 'iterate'
|
||||
},
|
||||
isBuiltin: true
|
||||
},
|
||||
'structured-format': {
|
||||
id: 'structured-format',
|
||||
name: 'Instruction-based Optimization',
|
||||
content: `# Role: Structured Prompt Conversion Expert
|
||||
|
||||
## Profile:
|
||||
- Author: prompt-optimizer
|
||||
- Version: 1.0.3
|
||||
- Language: English
|
||||
- Description: Focused on converting ordinary prompts into structured tag format, improving prompt clarity and effectiveness.
|
||||
|
||||
## Background:
|
||||
- Ordinary prompts often lack clear structure and organization
|
||||
- Structured tag format can help AI better understand tasks
|
||||
- Users need to convert ordinary instructions into standardized structure
|
||||
- Correct structure can improve task completion accuracy and efficiency
|
||||
|
||||
## Skills:
|
||||
1. Core analysis capabilities
|
||||
- Task extraction: Accurately identify core tasks in prompts
|
||||
- Background retention: Completely retain original prompt content
|
||||
- Instruction refinement: Convert implicit instructions into clear steps
|
||||
- Output standardization: Define clear output format requirements
|
||||
|
||||
2. Structured conversion capabilities
|
||||
- Semantic retention: Ensure no loss of original semantics during conversion
|
||||
- Structure optimization: Classify mixed content into appropriate tags
|
||||
- Detail supplementation: Add necessary details based on task type
|
||||
- Format standardization: Follow consistent tag format specifications
|
||||
|
||||
## Rules:
|
||||
|
||||
1. Tag structure specifications:
|
||||
- Tag completeness: Must include four basic tags: <task>, <context>, <instructions>, and <output_format>
|
||||
- Tag order: Follow standard order: task first, then context, then instructions, finally output format
|
||||
- Empty lines between tags: Must have one empty line between each tag
|
||||
- Format consistency: All tags use angle brackets <>, maintain format uniformity
|
||||
|
||||
2. Content conversion rules:
|
||||
- Task simplification: <task> tag content should be concise, describing core task in one sentence
|
||||
- Original text retention: <context> tag must completely retain original prompt text, maintain original expression, must not reorganize or rewrite
|
||||
- Instruction structuring: <instructions> tag content should use ordered lists to present detailed steps, including necessary sub-item indentation
|
||||
- Output detailing: <output_format> tag must clearly specify expected output format and requirements
|
||||
|
||||
3. Format detail handling:
|
||||
- Ordered lists: Instruction steps use number plus dot format (1. 2. 3.)
|
||||
- Sub-item indentation: Sub-items use three spaces indentation and start with dash
|
||||
- Paragraph breaks: Use empty lines between paragraphs within tags
|
||||
- Code references: Use backticks to mark code, without language identifiers
|
||||
|
||||
## Workflow:
|
||||
1. Analyze original prompt, understand its core intent and key elements
|
||||
2. Extract core task, form <task> tag content
|
||||
3. Directly copy original prompt text content to <context> tag, maintain original format and expression
|
||||
4. Based on original prompt, refine detailed execution steps, form <instructions> tag content
|
||||
5. Clarify output format requirements, form <output_format> tag content
|
||||
6. Combine all tag content according to specified format to form complete structured prompt
|
||||
7. Check if format meets requirements, especially empty lines between tags and list format
|
||||
|
||||
## Initialization:
|
||||
I will provide ordinary format prompts, please convert them to structured tag format.
|
||||
|
||||
Output using the following precise format, note that <context> tag must retain original prompt text:
|
||||
|
||||
<optimized_prompt>
|
||||
<task>Task description</task>
|
||||
|
||||
<context>
|
||||
Original prompt content, keep original text unchanged
|
||||
Can be multiple lines
|
||||
</context>
|
||||
|
||||
<instructions>
|
||||
1. First step instruction
|
||||
2. Second step instruction
|
||||
3. Third step instruction, may include sub-items:
|
||||
- Sub-item one
|
||||
- Sub-item two
|
||||
- Sub-item three
|
||||
4. Fourth step instruction
|
||||
5. Fifth step instruction
|
||||
</instructions>
|
||||
|
||||
<output_format>
|
||||
Expected output format description
|
||||
</output_format>
|
||||
</optimized_prompt>
|
||||
|
||||
Note: Must output according to the above precise format, do not add any leading words or explanations, do not wrap output content in code blocks. <context> tag must retain complete original text of original prompt, must not reorganize or rewrite.
|
||||
`,
|
||||
metadata: {
|
||||
version: '1.0.3',
|
||||
lastModified: Date.now(),
|
||||
author: 'System',
|
||||
description: 'Suitable for instruction-based prompt optimization, optimizes while following original instructions',
|
||||
templateType: 'optimize'
|
||||
},
|
||||
isBuiltin: true
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
import { IStorageProvider } from '../storage/types';
|
||||
import { StorageFactory } from '../storage/factory';
|
||||
|
||||
/**
|
||||
* Supported built-in template languages
|
||||
*/
|
||||
export type BuiltinTemplateLanguage = 'zh-CN' | 'en-US';
|
||||
|
||||
/**
|
||||
* Simplified built-in template language service
|
||||
*/
|
||||
export class TemplateLanguageService {
|
||||
private readonly STORAGE_KEY = 'builtin-template-language';
|
||||
private readonly SUPPORTED_LANGUAGES: BuiltinTemplateLanguage[] = ['zh-CN', 'en-US'];
|
||||
private readonly DEFAULT_LANGUAGE: BuiltinTemplateLanguage = 'en-US';
|
||||
|
||||
private currentLanguage: BuiltinTemplateLanguage = this.DEFAULT_LANGUAGE;
|
||||
private storage: IStorageProvider;
|
||||
private initialized = false;
|
||||
|
||||
constructor(storage?: IStorageProvider) {
|
||||
this.storage = storage || StorageFactory.createDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the service
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const savedLanguage = await this.storage.getItem(this.STORAGE_KEY);
|
||||
|
||||
if (savedLanguage && this.isValidLanguage(savedLanguage)) {
|
||||
this.currentLanguage = savedLanguage as BuiltinTemplateLanguage;
|
||||
} else {
|
||||
// Auto-detect: Chinese browsers use Chinese, others use English
|
||||
const isChineseBrowser = navigator.language?.startsWith('zh') ?? false;
|
||||
this.currentLanguage = isChineseBrowser ? 'zh-CN' : 'en-US';
|
||||
await this.storage.setItem(this.STORAGE_KEY, this.currentLanguage);
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize template language service:', error);
|
||||
this.currentLanguage = this.DEFAULT_LANGUAGE;
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current language
|
||||
*/
|
||||
getCurrentLanguage(): BuiltinTemplateLanguage {
|
||||
return this.currentLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set language
|
||||
*/
|
||||
async setLanguage(language: BuiltinTemplateLanguage): Promise<void> {
|
||||
if (!this.isValidLanguage(language)) {
|
||||
throw new Error(`Unsupported language: ${language}`);
|
||||
}
|
||||
|
||||
this.currentLanguage = language;
|
||||
await this.storage.setItem(this.STORAGE_KEY, language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle between Chinese and English
|
||||
*/
|
||||
async toggleLanguage(): Promise<BuiltinTemplateLanguage> {
|
||||
const newLanguage = this.currentLanguage === 'zh-CN' ? 'en-US' : 'zh-CN';
|
||||
await this.setLanguage(newLanguage);
|
||||
return newLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if language is valid
|
||||
*/
|
||||
isValidLanguage(language: string): boolean {
|
||||
return this.SUPPORTED_LANGUAGES.includes(language as BuiltinTemplateLanguage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported languages list
|
||||
*/
|
||||
getSupportedLanguages(): BuiltinTemplateLanguage[] {
|
||||
return [...this.SUPPORTED_LANGUAGES];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display name for a language
|
||||
*/
|
||||
getLanguageDisplayName(language: BuiltinTemplateLanguage): string {
|
||||
switch (language) {
|
||||
case 'zh-CN':
|
||||
return '中文';
|
||||
case 'en-US':
|
||||
return 'English';
|
||||
default:
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if initialized
|
||||
*/
|
||||
isInitialized(): boolean {
|
||||
return this.initialized;
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const templateLanguageService = new TemplateLanguageService();
|
||||
@@ -2,8 +2,12 @@ import { ITemplateManager, Template, TemplateManagerConfig } from './types';
|
||||
import { IStorageProvider } from '../storage/types';
|
||||
import { StorageFactory } from '../storage/factory';
|
||||
import { DEFAULT_TEMPLATES } from './defaults';
|
||||
import { DEFAULT_TEMPLATES_EN } from './defaults_en';
|
||||
import { TemplateError, TemplateValidationError } from './errors';
|
||||
import { templateSchema } from './types';
|
||||
import { templateLanguageService, BuiltinTemplateLanguage } from './languageService';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 提示词管理器实现
|
||||
@@ -12,6 +16,8 @@ export class TemplateManager implements ITemplateManager {
|
||||
private readonly builtinTemplates: Map<string, Template>;
|
||||
private readonly userTemplates: Map<string, Template>;
|
||||
private readonly config: Required<TemplateManagerConfig>;
|
||||
private initPromise: Promise<void> | null = null;
|
||||
protected initialized = false;
|
||||
|
||||
constructor(private storageProvider: IStorageProvider, config?: TemplateManagerConfig) {
|
||||
// Default configuration
|
||||
@@ -25,25 +31,114 @@ export class TemplateManager implements ITemplateManager {
|
||||
this.builtinTemplates = new Map();
|
||||
this.userTemplates = new Map();
|
||||
|
||||
// Load built-in templates
|
||||
for (const [id, template] of Object.entries(DEFAULT_TEMPLATES)) {
|
||||
this.builtinTemplates.set(id, { ...template, isBuiltin: true });
|
||||
}
|
||||
|
||||
// Initialize asynchronously - load user templates
|
||||
this.init().catch(error => {
|
||||
// Initialize asynchronously with improved error handling
|
||||
this.initPromise = this.init().catch(error => {
|
||||
console.error('Template manager initialization failed:', error);
|
||||
// Don't rethrow - allow fallback initialization
|
||||
return this.fallbackInit();
|
||||
});
|
||||
}
|
||||
|
||||
private async init(): Promise<void> {
|
||||
try {
|
||||
// Initialize template language service first
|
||||
await templateLanguageService.initialize();
|
||||
|
||||
// Load built-in templates based on current language
|
||||
await this.loadBuiltinTemplates();
|
||||
|
||||
// Load user templates
|
||||
await this.loadUserTemplates();
|
||||
|
||||
this.initialized = true;
|
||||
} catch (error) {
|
||||
console.error('Template manager initialization failed:', error);
|
||||
this.initialized = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback initialization with default templates
|
||||
*/
|
||||
private async fallbackInit(): Promise<void> {
|
||||
try {
|
||||
console.log('Attempting fallback initialization with default templates');
|
||||
|
||||
// Clear any partially loaded templates
|
||||
this.builtinTemplates.clear();
|
||||
|
||||
// Load default Chinese templates as fallback
|
||||
const { DEFAULT_TEMPLATES } = await import('./defaults');
|
||||
for (const [id, template] of Object.entries(DEFAULT_TEMPLATES)) {
|
||||
this.builtinTemplates.set(id, { ...template, isBuiltin: true });
|
||||
}
|
||||
|
||||
// Try to load user templates (non-critical)
|
||||
try {
|
||||
await this.loadUserTemplates();
|
||||
} catch (userTemplateError) {
|
||||
console.warn('Failed to load user templates during fallback:', userTemplateError);
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
console.log('Fallback initialization completed');
|
||||
} catch (fallbackError) {
|
||||
console.error('Fallback initialization also failed:', fallbackError);
|
||||
this.initialized = false;
|
||||
throw fallbackError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the template manager is initialized
|
||||
* This should be called before any template operations
|
||||
*/
|
||||
async ensureInitialized(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.initPromise) {
|
||||
this.initPromise = this.init();
|
||||
}
|
||||
|
||||
try {
|
||||
await this.initPromise;
|
||||
} catch (error) {
|
||||
// Reset initPromise to allow retry
|
||||
this.initPromise = null;
|
||||
// If initialization still fails, at least ensure we have some templates
|
||||
if (!this.initialized) {
|
||||
console.error('Initialization failed, attempting emergency fallback');
|
||||
await this.fallbackInit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the template manager is initialized
|
||||
*/
|
||||
isInitialized(): boolean {
|
||||
return this.initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method to check initialization state for sync methods
|
||||
*/
|
||||
private checkInitialized(methodName: string): void {
|
||||
if (!this.initialized) {
|
||||
throw new TemplateError(`Template manager not initialized. Call ensureInitialized() first before using ${methodName}.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method to ensure initialization for async methods
|
||||
*/
|
||||
private async ensureInitForAsyncMethod(): Promise<void> {
|
||||
await this.ensureInitialized();
|
||||
}
|
||||
|
||||
private validateTemplateSchema(template: Partial<Template>): void {
|
||||
const result = templateSchema.safeParse(template);
|
||||
if (!result.success) {
|
||||
@@ -84,24 +179,30 @@ export class TemplateManager implements ITemplateManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a template by ID
|
||||
* @param id Template ID
|
||||
* @returns Template
|
||||
*/
|
||||
/**
|
||||
* Gets a template by ID
|
||||
* @param id Template ID
|
||||
* @returns Template
|
||||
*/
|
||||
getTemplate(id: string | null | undefined): Template {
|
||||
this.checkInitialized('getTemplate');
|
||||
this.validateTemplateId(id);
|
||||
|
||||
|
||||
// Check built-in templates first
|
||||
if (this.builtinTemplates.has(id!)) {
|
||||
return this.builtinTemplates.get(id!)!;
|
||||
}
|
||||
|
||||
|
||||
// Check user templates
|
||||
if (this.userTemplates.has(id!)) {
|
||||
return this.userTemplates.get(id!)!;
|
||||
}
|
||||
|
||||
|
||||
// If we get here, the template wasn't found
|
||||
const available = [...this.builtinTemplates.keys(), ...this.userTemplates.keys()];
|
||||
throw new TemplateError(`Template ${id} not found, available templates: ${JSON.stringify(available, null, 2)}`);
|
||||
@@ -113,22 +214,24 @@ export class TemplateManager implements ITemplateManager {
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
async saveTemplate(template: Template): Promise<void> {
|
||||
await this.ensureInitForAsyncMethod();
|
||||
|
||||
// Validate ID
|
||||
this.validateTemplateId(template.id);
|
||||
|
||||
|
||||
// Validate template type
|
||||
if (template.metadata?.templateType) {
|
||||
this.validateTemplateType(template.metadata.templateType);
|
||||
}
|
||||
|
||||
|
||||
// Prevent overwriting built-in templates
|
||||
if (this.builtinTemplates.has(template.id)) {
|
||||
throw new TemplateError(`Cannot overwrite built-in template: ${template.id}`);
|
||||
}
|
||||
|
||||
|
||||
// Validate schema
|
||||
this.validateTemplateSchema(template);
|
||||
|
||||
|
||||
// Set isBuiltin to false and add timestamp
|
||||
const templateToSave: Template = {
|
||||
...template,
|
||||
@@ -138,10 +241,10 @@ export class TemplateManager implements ITemplateManager {
|
||||
lastModified: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Add to user templates
|
||||
this.userTemplates.set(template.id, templateToSave);
|
||||
|
||||
|
||||
// Persist
|
||||
await this.persistUserTemplates();
|
||||
}
|
||||
@@ -151,6 +254,8 @@ export class TemplateManager implements ITemplateManager {
|
||||
* @param id Template ID
|
||||
*/
|
||||
async deleteTemplate(id: string): Promise<void> {
|
||||
await this.ensureInitForAsyncMethod();
|
||||
|
||||
// Validate ID
|
||||
this.validateTemplateId(id);
|
||||
|
||||
@@ -175,6 +280,8 @@ export class TemplateManager implements ITemplateManager {
|
||||
* List all templates
|
||||
*/
|
||||
listTemplates(): Template[] {
|
||||
this.checkInitialized('listTemplates');
|
||||
|
||||
const templates = [
|
||||
...Array.from(this.builtinTemplates.values()),
|
||||
...Array.from(this.userTemplates.values())
|
||||
@@ -184,14 +291,14 @@ export class TemplateManager implements ITemplateManager {
|
||||
if (a.isBuiltin !== b.isBuiltin) {
|
||||
return a.isBuiltin ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
// Non-built-in templates sorted by timestamp descending
|
||||
if (!a.isBuiltin && !b.isBuiltin) {
|
||||
const timeA = a.metadata.lastModified || 0;
|
||||
const timeB = b.metadata.lastModified || 0;
|
||||
return timeB - timeA;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
@@ -254,6 +361,59 @@ export class TemplateManager implements ITemplateManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load built-in templates based on current language setting
|
||||
*/
|
||||
private async loadBuiltinTemplates(): Promise<void> {
|
||||
try {
|
||||
// Clear existing built-in templates
|
||||
this.builtinTemplates.clear();
|
||||
|
||||
// Get current language from template language service
|
||||
const currentLanguage = templateLanguageService.getCurrentLanguage();
|
||||
|
||||
// Load appropriate template set based on language
|
||||
const templateSet = await this.getTemplateSet(currentLanguage);
|
||||
|
||||
// Load templates from selected set
|
||||
for (const [id, template] of Object.entries(templateSet)) {
|
||||
this.builtinTemplates.set(id, { ...template, isBuiltin: true });
|
||||
}
|
||||
|
||||
console.log(`Loaded built-in templates in ${currentLanguage} (${this.builtinTemplates.size} templates)`);
|
||||
} catch (error) {
|
||||
console.error('Failed to load built-in templates:', error);
|
||||
// Fallback to Chinese templates
|
||||
for (const [id, template] of Object.entries(DEFAULT_TEMPLATES)) {
|
||||
this.builtinTemplates.set(id, { ...template, isBuiltin: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template set for the specified language
|
||||
* This method provides better extensibility for adding new languages
|
||||
*/
|
||||
private async getTemplateSet(language: BuiltinTemplateLanguage): Promise<Record<string, Template>> {
|
||||
switch (language) {
|
||||
case 'en-US':
|
||||
return DEFAULT_TEMPLATES_EN;
|
||||
case 'zh-CN':
|
||||
return DEFAULT_TEMPLATES;
|
||||
default:
|
||||
console.warn(`Unsupported language: ${language}, falling back to Chinese templates`);
|
||||
return DEFAULT_TEMPLATES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload built-in templates with new language
|
||||
* This method can be called when user changes built-in template language
|
||||
*/
|
||||
async reloadBuiltinTemplates(): Promise<void> {
|
||||
await this.loadBuiltinTemplates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads user templates from storage
|
||||
*/
|
||||
@@ -298,6 +458,38 @@ export class TemplateManager implements ITemplateManager {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change built-in template language
|
||||
*/
|
||||
async changeBuiltinTemplateLanguage(language: BuiltinTemplateLanguage): Promise<void> {
|
||||
try {
|
||||
// Update language service
|
||||
await templateLanguageService.setLanguage(language);
|
||||
|
||||
// Reload built-in templates with new language
|
||||
await this.reloadBuiltinTemplates();
|
||||
|
||||
console.log(`Changed built-in template language to ${language}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to change built-in template language:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current built-in template language
|
||||
*/
|
||||
getCurrentBuiltinTemplateLanguage(): BuiltinTemplateLanguage {
|
||||
return templateLanguageService.getCurrentLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported built-in template languages
|
||||
*/
|
||||
getSupportedBuiltinTemplateLanguages(): BuiltinTemplateLanguage[] {
|
||||
return templateLanguageService.getSupportedLanguages();
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
|
||||
@@ -362,7 +362,10 @@ describe('PromptService', () => {
|
||||
});
|
||||
|
||||
describe('提示词管理器状态检查', () => {
|
||||
it('应该能检测到提示词管理器的初始化状态', () => {
|
||||
it('应该能检测到提示词管理器的初始化状态', async () => {
|
||||
// 确保模板管理器已初始化
|
||||
await templateManager.ensureInitialized();
|
||||
|
||||
expect(() => {
|
||||
templateManager.getTemplate('non-existent-template');
|
||||
}).toThrow('Template non-existent-template not found');
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { TemplateLanguageService } from '../../../src/services/template/languageService';
|
||||
import { createMockStorage } from '../../mocks/mockStorage';
|
||||
|
||||
describe('TemplateLanguageService', () => {
|
||||
let service: TemplateLanguageService;
|
||||
let mockStorage: ReturnType<typeof createMockStorage>;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock navigator.language to English for consistent test environment
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
value: 'en-US',
|
||||
configurable: true
|
||||
});
|
||||
|
||||
mockStorage = createMockStorage();
|
||||
service = new TemplateLanguageService(mockStorage);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('initialization', () => {
|
||||
it('should initialize with default language when no saved preference', async () => {
|
||||
mockStorage.getItem.mockResolvedValue(null);
|
||||
|
||||
await service.initialize();
|
||||
|
||||
expect(service.getCurrentLanguage()).toBe('en-US');
|
||||
expect(service.isInitialized()).toBe(true);
|
||||
});
|
||||
|
||||
it('should load saved language preference', async () => {
|
||||
mockStorage.getItem.mockResolvedValue('en-US');
|
||||
|
||||
await service.initialize();
|
||||
|
||||
expect(service.getCurrentLanguage()).toBe('en-US');
|
||||
expect(mockStorage.getItem).toHaveBeenCalledWith('builtin-template-language');
|
||||
});
|
||||
|
||||
it('should fallback to default language on invalid saved preference', async () => {
|
||||
mockStorage.getItem.mockResolvedValue('invalid-lang');
|
||||
|
||||
await service.initialize();
|
||||
|
||||
expect(service.getCurrentLanguage()).toBe('en-US');
|
||||
});
|
||||
|
||||
it('should handle storage errors gracefully', async () => {
|
||||
mockStorage.getItem.mockRejectedValue(new Error('Storage error'));
|
||||
|
||||
await service.initialize();
|
||||
|
||||
expect(service.getCurrentLanguage()).toBe('en-US');
|
||||
expect(service.isInitialized()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('language management', () => {
|
||||
beforeEach(async () => {
|
||||
await service.initialize();
|
||||
});
|
||||
|
||||
it('should set language successfully', async () => {
|
||||
await service.setLanguage('en-US');
|
||||
|
||||
expect(service.getCurrentLanguage()).toBe('en-US');
|
||||
expect(mockStorage.setItem).toHaveBeenCalledWith('builtin-template-language', 'en-US');
|
||||
});
|
||||
|
||||
it('should reject invalid language', async () => {
|
||||
await expect(service.setLanguage('invalid-lang' as any)).rejects.toThrow(
|
||||
'Unsupported language: invalid-lang'
|
||||
);
|
||||
});
|
||||
|
||||
it('should toggle between languages', async () => {
|
||||
// Start with detected language (en-US in test environment)
|
||||
expect(service.getCurrentLanguage()).toBe('en-US');
|
||||
|
||||
// Toggle to Chinese
|
||||
const newLang1 = await service.toggleLanguage();
|
||||
expect(newLang1).toBe('zh-CN');
|
||||
expect(service.getCurrentLanguage()).toBe('zh-CN');
|
||||
|
||||
// Toggle back to English
|
||||
const newLang2 = await service.toggleLanguage();
|
||||
expect(newLang2).toBe('en-US');
|
||||
expect(service.getCurrentLanguage()).toBe('en-US');
|
||||
});
|
||||
});
|
||||
|
||||
describe('utility methods', () => {
|
||||
it('should validate languages correctly', () => {
|
||||
expect(service.isValidLanguage('zh-CN')).toBe(true);
|
||||
expect(service.isValidLanguage('en-US')).toBe(true);
|
||||
expect(service.isValidLanguage('fr-FR')).toBe(false);
|
||||
expect(service.isValidLanguage('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('browser language detection', () => {
|
||||
it('should detect Chinese browser language', async () => {
|
||||
// Mock navigator.language
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
value: 'zh-CN',
|
||||
configurable: true
|
||||
});
|
||||
|
||||
mockStorage.getItem.mockResolvedValue(null);
|
||||
const newService = new TemplateLanguageService(mockStorage);
|
||||
await newService.initialize();
|
||||
|
||||
expect(newService.getCurrentLanguage()).toBe('zh-CN');
|
||||
});
|
||||
|
||||
it('should detect English browser language', async () => {
|
||||
// Mock navigator.language
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
value: 'en-US',
|
||||
configurable: true
|
||||
});
|
||||
|
||||
mockStorage.getItem.mockResolvedValue(null);
|
||||
const newService = new TemplateLanguageService(mockStorage);
|
||||
await newService.initialize();
|
||||
|
||||
expect(newService.getCurrentLanguage()).toBe('en-US');
|
||||
});
|
||||
|
||||
it('should default to English for unsupported browser languages', async () => {
|
||||
// Mock navigator.language
|
||||
Object.defineProperty(navigator, 'language', {
|
||||
value: 'fr-FR',
|
||||
configurable: true
|
||||
});
|
||||
|
||||
mockStorage.getItem.mockResolvedValue(null);
|
||||
const newService = new TemplateLanguageService(mockStorage);
|
||||
await newService.initialize();
|
||||
|
||||
expect(newService.getCurrentLanguage()).toBe('en-US');
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance behavior', () => {
|
||||
it('should create independent instances', () => {
|
||||
const instance1 = new TemplateLanguageService();
|
||||
const instance2 = new TemplateLanguageService();
|
||||
|
||||
expect(instance1).not.toBe(instance2);
|
||||
expect(instance1).toBeInstanceOf(TemplateLanguageService);
|
||||
expect(instance2).toBeInstanceOf(TemplateLanguageService);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,8 @@ import { IStorageProvider } from '../../../src/services/storage/types';
|
||||
import { Template, templateSchema } from '../../../src/services/template/types'; // Removed TemplateSchema as it's not directly used, templateSchema is
|
||||
import { TemplateError, TemplateValidationError } from '../../../src/services/template/errors';
|
||||
import { DEFAULT_TEMPLATES } from '../../../src/services/template/defaults';
|
||||
import { DEFAULT_TEMPLATES_EN } from '../../../src/services/template/defaults_en';
|
||||
import { templateLanguageService } from '../../../src/services/template/languageService';
|
||||
import { createMockStorage } from '../../mocks/mockStorage';
|
||||
|
||||
// Helper for deep cloning default templates for isolated tests
|
||||
@@ -32,6 +34,11 @@ describe('TemplateManager', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
mockStorage = createMockStorage();
|
||||
|
||||
// Mock template language service initialization
|
||||
vi.spyOn(templateLanguageService, 'initialize').mockResolvedValue();
|
||||
vi.spyOn(templateLanguageService, 'getCurrentLanguage').mockReturnValue('zh-CN');
|
||||
|
||||
templateManager = new TemplateManager(mockStorage);
|
||||
// Allow async init (which calls loadUserTemplates) to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
@@ -41,6 +48,11 @@ describe('TemplateManager', () => {
|
||||
});
|
||||
|
||||
describe('Initialization', () => {
|
||||
it('should have initialization methods', async () => {
|
||||
expect(templateManager.isInitialized()).toBe(true);
|
||||
await expect(templateManager.ensureInitialized()).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should load built-in templates correctly', () => {
|
||||
const builtInIds = Object.keys(DEFAULT_TEMPLATES);
|
||||
expect(builtInIds.length).toBeGreaterThan(0);
|
||||
@@ -51,6 +63,14 @@ describe('TemplateManager', () => {
|
||||
expect(template.id).toBe(firstBuiltInId);
|
||||
});
|
||||
|
||||
it('should throw error when accessing templates before initialization', () => {
|
||||
const uninitializedStorage = createMockStorage();
|
||||
const uninitializedManager = new TemplateManager(uninitializedStorage);
|
||||
|
||||
// Access template before initialization completes
|
||||
expect(() => uninitializedManager.getTemplate('general-optimize')).toThrow('Template manager not initialized');
|
||||
});
|
||||
|
||||
it('should load user templates from storage during init', async () => {
|
||||
const userTpl = createTemplate('user1', 'User Template 1');
|
||||
const initialMockStorage = createMockStorage();
|
||||
@@ -277,8 +297,16 @@ describe('TemplateManager', () => {
|
||||
it('should return built-in and user templates, sorted correctly', async () => {
|
||||
// 创建一个自定义的templateManager,手动设置userTemplates
|
||||
const customMockStorage = createMockStorage();
|
||||
|
||||
// Mock template language service for the custom manager
|
||||
vi.spyOn(templateLanguageService, 'initialize').mockResolvedValue();
|
||||
vi.spyOn(templateLanguageService, 'getCurrentLanguage').mockReturnValue('zh-CN');
|
||||
|
||||
const customTemplateManager = new TemplateManager(customMockStorage);
|
||||
|
||||
// 等待异步初始化完成
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
// 创建两个时间戳有明显差异的模板
|
||||
const oldTemplate = createTemplate('user-tpl-1', 'User Alpha', 'optimize', 'content', false, 1000);
|
||||
const newTemplate = createTemplate('user-tpl-2', 'User Beta', 'iterate', 'content', false, 2000);
|
||||
@@ -434,18 +462,29 @@ describe('TemplateManager', () => {
|
||||
|
||||
describe('StorageError Handling', () => {
|
||||
it('init (via loadUserTemplates) should throw TemplateError if storageProvider.getItem fails', async () => {
|
||||
const errorMockStorage = createMockStorage();
|
||||
errorMockStorage.getItem.mockRejectedValue(new Error("Storage Get Failed!"));
|
||||
const testMockStorage = createMockStorage();
|
||||
|
||||
// 模拟存储获取失败
|
||||
testMockStorage.getItem.mockRejectedValue(new Error('Storage Get Failed!'));
|
||||
|
||||
const templateManager = new TemplateManager(testMockStorage);
|
||||
|
||||
try {
|
||||
// 等待初始化完成,现在会使用fallback机制
|
||||
await templateManager.ensureInitialized();
|
||||
|
||||
// Expect constructor, which calls async init, to result in an error being thrown and caught by the .catch
|
||||
// This is hard to test directly on constructor. Better to test loadUserTemplates if it were public.
|
||||
// For now, we check console.error or if the manager is in a state indicating failure.
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
new TemplateManager(errorMockStorage);
|
||||
// Wait for async init to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith('Template manager initialization failed:', expect.any(Error));
|
||||
consoleErrorSpy.mockRestore();
|
||||
// 验证初始化成功(使用fallback)
|
||||
expect(templateManager.isInitialized()).toBe(true);
|
||||
|
||||
// 验证仍然有内置模板可用
|
||||
const templates = templateManager.listTemplates();
|
||||
expect(templates.length).toBeGreaterThan(0);
|
||||
expect(templates.every(t => t.isBuiltin)).toBe(true);
|
||||
|
||||
} catch (error) {
|
||||
// 如果fallback也失败,验证错误
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
}
|
||||
});
|
||||
|
||||
it('saveTemplate (via persistUserTemplates) should throw TemplateError if storageProvider.setItem fails', async () => {
|
||||
@@ -485,4 +524,63 @@ describe('TemplateManager', () => {
|
||||
await expect(templateManager.importTemplate(jsonToImport)).rejects.toThrow('Failed to save user templates: Storage Set Failed on Import!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Built-in Template Language Support', () => {
|
||||
it('should load Chinese templates by default', () => {
|
||||
const builtinTemplates = templateManager.listTemplates().filter(t => t.isBuiltin);
|
||||
expect(builtinTemplates.length).toBeGreaterThan(0);
|
||||
|
||||
// Check that we have Chinese templates (should contain Chinese characters)
|
||||
const generalOptimize = builtinTemplates.find(t => t.id === 'general-optimize');
|
||||
expect(generalOptimize).toBeDefined();
|
||||
expect(generalOptimize!.name).toBe('通用优化'); // Chinese name
|
||||
});
|
||||
|
||||
it('should change to English templates when language is changed', async () => {
|
||||
// Mock language service to return English
|
||||
vi.spyOn(templateLanguageService, 'getCurrentLanguage').mockReturnValue('en-US');
|
||||
vi.spyOn(templateLanguageService, 'setLanguage').mockResolvedValue();
|
||||
|
||||
await templateManager.changeBuiltinTemplateLanguage('en-US');
|
||||
|
||||
const builtinTemplates = templateManager.listTemplates().filter(t => t.isBuiltin);
|
||||
const generalOptimize = builtinTemplates.find(t => t.id === 'general-optimize');
|
||||
expect(generalOptimize).toBeDefined();
|
||||
expect(generalOptimize!.name).toBe('General Optimization'); // English name
|
||||
});
|
||||
|
||||
it('should get current builtin template language', () => {
|
||||
const currentLang = templateManager.getCurrentBuiltinTemplateLanguage();
|
||||
expect(currentLang).toBe('zh-CN');
|
||||
});
|
||||
|
||||
it('should get supported builtin template languages', () => {
|
||||
const supportedLangs = templateManager.getSupportedBuiltinTemplateLanguages();
|
||||
expect(supportedLangs).toEqual(['zh-CN', 'en-US']);
|
||||
});
|
||||
|
||||
it('should handle language change errors gracefully', async () => {
|
||||
vi.spyOn(templateLanguageService, 'setLanguage').mockRejectedValue(new Error('Language change failed'));
|
||||
|
||||
await expect(templateManager.changeBuiltinTemplateLanguage('en-US')).rejects.toThrow('Language change failed');
|
||||
});
|
||||
|
||||
it('should reload builtin templates when language changes', async () => {
|
||||
const initialTemplates = templateManager.listTemplates().filter(t => t.isBuiltin);
|
||||
const initialCount = initialTemplates.length;
|
||||
|
||||
// Mock language service to return English
|
||||
vi.spyOn(templateLanguageService, 'getCurrentLanguage').mockReturnValue('en-US');
|
||||
vi.spyOn(templateLanguageService, 'setLanguage').mockResolvedValue();
|
||||
|
||||
await templateManager.changeBuiltinTemplateLanguage('en-US');
|
||||
|
||||
const newTemplates = templateManager.listTemplates().filter(t => t.isBuiltin);
|
||||
expect(newTemplates.length).toBe(initialCount); // Same number of templates
|
||||
|
||||
// But content should be different (English vs Chinese)
|
||||
const newGeneralOptimize = newTemplates.find(t => t.id === 'general-optimize');
|
||||
expect(newGeneralOptimize!.name).toBe('General Optimization');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
@click="handleLanguageToggle"
|
||||
:disabled="isChanging"
|
||||
:title="t('template.switchBuiltinLanguage')"
|
||||
class="theme-language-button"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<svg
|
||||
class="w-3 h-3 theme-icon"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-medium">{{ getCurrentLanguageShort }}</span>
|
||||
<div v-if="isChanging" class="w-3 h-3 animate-spin">
|
||||
<svg class="w-full h-full" viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useToast } from '../composables/useToast'
|
||||
import { templateManager, templateLanguageService } from '@prompt-optimizer/core'
|
||||
import type { BuiltinTemplateLanguage } from '@prompt-optimizer/core'
|
||||
|
||||
const { t } = useI18n()
|
||||
const toast = useToast()
|
||||
|
||||
// Reactive state
|
||||
const currentLanguage = ref<BuiltinTemplateLanguage>('zh-CN')
|
||||
const supportedLanguages = ref<BuiltinTemplateLanguage[]>([])
|
||||
const isChanging = ref(false)
|
||||
|
||||
// Computed properties
|
||||
const getCurrentLanguageShort = computed(() => {
|
||||
try {
|
||||
return templateLanguageService.getLanguageDisplayName(currentLanguage.value)
|
||||
} catch (error) {
|
||||
console.error('Error getting current language short:', error)
|
||||
return '中文' // fallback to Chinese
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Event emitters
|
||||
const emit = defineEmits<{
|
||||
languageChanged: [language: BuiltinTemplateLanguage]
|
||||
}>()
|
||||
|
||||
/**
|
||||
* Initialize component
|
||||
*/
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// Ensure template language service is initialized
|
||||
if (!templateLanguageService.isInitialized()) {
|
||||
await templateLanguageService.initialize()
|
||||
}
|
||||
|
||||
// Get current language and supported languages
|
||||
currentLanguage.value = templateLanguageService.getCurrentLanguage()
|
||||
supportedLanguages.value = templateLanguageService.getSupportedLanguages()
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize builtin template language switch:', error)
|
||||
// Set fallback values
|
||||
currentLanguage.value = 'zh-CN'
|
||||
supportedLanguages.value = ['zh-CN', 'en-US']
|
||||
|
||||
// Only show toast error if toast is available
|
||||
try {
|
||||
toast.error(t('template.languageInitError'))
|
||||
} catch (toastError) {
|
||||
console.error('Failed to show toast error:', toastError)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Handle language toggle
|
||||
*/
|
||||
const handleLanguageToggle = async () => {
|
||||
if (isChanging.value) return
|
||||
|
||||
const oldLanguage = currentLanguage.value
|
||||
const newLanguage = oldLanguage === 'zh-CN' ? 'en-US' : 'zh-CN'
|
||||
|
||||
try {
|
||||
isChanging.value = true
|
||||
|
||||
// Change the built-in template language
|
||||
await templateManager.changeBuiltinTemplateLanguage(newLanguage)
|
||||
|
||||
// Update local state
|
||||
currentLanguage.value = newLanguage
|
||||
|
||||
// Emit event to notify parent components
|
||||
emit('languageChanged', newLanguage)
|
||||
|
||||
// Show success message
|
||||
try {
|
||||
const languageName = templateLanguageService.getLanguageDisplayName(newLanguage)
|
||||
toast.success(t('template.languageChanged', { language: languageName }))
|
||||
} catch (toastError) {
|
||||
console.error('Failed to show success toast:', toastError)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle builtin template language:', error)
|
||||
|
||||
// Revert to old language on error
|
||||
currentLanguage.value = oldLanguage
|
||||
|
||||
// Show error message
|
||||
try {
|
||||
toast.error(t('template.languageChangeError'))
|
||||
} catch (toastError) {
|
||||
console.error('Failed to show error toast:', toastError)
|
||||
}
|
||||
} finally {
|
||||
isChanging.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh current language (useful for external updates)
|
||||
*/
|
||||
const refresh = () => {
|
||||
currentLanguage.value = templateLanguageService.getCurrentLanguage()
|
||||
}
|
||||
|
||||
// Expose methods for parent components
|
||||
defineExpose({
|
||||
refresh
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.theme-language-button {
|
||||
@apply inline-flex items-center px-2 py-1 text-xs rounded-md border transition-all duration-200;
|
||||
@apply bg-white border-gray-300 text-gray-700 hover:bg-gray-50;
|
||||
@apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500;
|
||||
@apply disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white;
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
@apply text-gray-500;
|
||||
}
|
||||
|
||||
/* Theme variants */
|
||||
:root.dark .theme-language-button {
|
||||
@apply bg-slate-800 border-slate-600 text-slate-200 hover:bg-slate-700;
|
||||
@apply focus:ring-blue-400 focus:border-blue-400;
|
||||
@apply disabled:hover:bg-slate-800;
|
||||
}
|
||||
|
||||
:root.dark .theme-icon {
|
||||
@apply text-slate-400;
|
||||
}
|
||||
|
||||
:root.theme-blue .theme-language-button {
|
||||
@apply bg-sky-50 border-sky-200 text-sky-800 hover:bg-sky-100;
|
||||
@apply focus:ring-sky-500 focus:border-sky-500;
|
||||
@apply disabled:hover:bg-sky-50;
|
||||
}
|
||||
|
||||
:root.theme-blue .theme-icon {
|
||||
@apply text-sky-600;
|
||||
}
|
||||
|
||||
:root.theme-green .theme-language-button {
|
||||
@apply bg-teal-50 border-teal-200 text-teal-800 hover:bg-teal-100;
|
||||
@apply focus:ring-teal-500 focus:border-teal-500;
|
||||
@apply disabled:hover:bg-teal-50;
|
||||
}
|
||||
|
||||
:root.theme-green .theme-icon {
|
||||
@apply text-teal-600;
|
||||
}
|
||||
|
||||
:root.theme-purple .theme-language-button {
|
||||
@apply bg-purple-50 border-purple-200 text-purple-800 hover:bg-purple-100;
|
||||
@apply focus:ring-purple-500 focus:border-purple-500;
|
||||
@apply disabled:hover:bg-purple-50;
|
||||
}
|
||||
|
||||
:root.theme-purple .theme-icon {
|
||||
@apply text-purple-600;
|
||||
}
|
||||
</style>
|
||||
@@ -22,6 +22,8 @@
|
||||
</svg>
|
||||
<span class="hidden md:inline">{{ t('templateManager.syntaxGuide') }}</span>
|
||||
</button>
|
||||
<!-- Built-in Template Language Switch -->
|
||||
<BuiltinTemplateLanguageSwitch @language-changed="handleLanguageChanged" />
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span v-if="selectedTemplate" class="text-sm theme-manager-text-secondary">
|
||||
@@ -617,6 +619,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { templateManager, TemplateProcessor } from '@prompt-optimizer/core'
|
||||
import { useToast } from '../composables/useToast'
|
||||
import MarkdownRenderer from './MarkdownRenderer.vue'
|
||||
import BuiltinTemplateLanguageSwitch from './BuiltinTemplateLanguageSwitch.vue'
|
||||
import { syntaxGuideContent } from '../docs/syntax-guide'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -696,8 +699,11 @@ const processedPreview = computed(() => {
|
||||
})
|
||||
|
||||
// 加载提示词列表
|
||||
const loadTemplates = () => {
|
||||
const loadTemplates = async () => {
|
||||
try {
|
||||
// Ensure template manager is initialized
|
||||
await templateManager.ensureInitialized()
|
||||
|
||||
const allTemplates = templateManager.listTemplates()
|
||||
templates.value = allTemplates
|
||||
console.log('加载到的提示词:', templates.value)
|
||||
@@ -881,9 +887,13 @@ const applyMigration = async () => {
|
||||
(currentType.value === 'iterate' && props.selectedIterateTemplate?.id === template.id)
|
||||
|
||||
if (isCurrentSelected) {
|
||||
const updated = templateManager.getTemplate(template.id)
|
||||
if (updated) {
|
||||
emit('select', updated, currentType.value)
|
||||
try {
|
||||
const updated = templateManager.getTemplate(template.id)
|
||||
if (updated) {
|
||||
emit('select', updated, currentType.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get updated template:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,9 +947,13 @@ const handleSubmit = () => {
|
||||
(currentType.value === 'iterate' && props.selectedIterateTemplate?.id === templateData.id)
|
||||
|
||||
if (editingTemplate.value && isCurrentSelected) {
|
||||
const updatedTemplate = templateManager.getTemplate(templateData.id)
|
||||
if (updatedTemplate) {
|
||||
emit('select', updatedTemplate, currentType.value)
|
||||
try {
|
||||
const updatedTemplate = templateManager.getTemplate(templateData.id)
|
||||
if (updatedTemplate) {
|
||||
emit('select', updatedTemplate, currentType.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get updated template after save:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -952,12 +966,12 @@ const handleSubmit = () => {
|
||||
}
|
||||
|
||||
// 确认删除
|
||||
const confirmDelete = (templateId) => {
|
||||
const confirmDelete = async (templateId) => {
|
||||
if (confirm(t('template.deleteConfirm'))) {
|
||||
try {
|
||||
templateManager.deleteTemplate(templateId)
|
||||
const remainingTemplates = templateManager.listTemplatesByType(currentType.value)
|
||||
loadTemplates()
|
||||
await loadTemplates()
|
||||
|
||||
if (currentType.value === 'optimize' && props.selectedOptimizeTemplate?.id === templateId) {
|
||||
emit('select', remainingTemplates[0] || null, 'optimize')
|
||||
@@ -1048,9 +1062,41 @@ const syntaxGuideMarkdown = computed(() => {
|
||||
return syntaxGuideContent[locale] || syntaxGuideContent['zh-CN']
|
||||
})
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
// 处理内置模板语言变化
|
||||
const handleLanguageChanged = (newLanguage) => {
|
||||
// 重新加载模板列表以反映新的语言
|
||||
loadTemplates()
|
||||
|
||||
// 如果当前选中的模板是内置模板,需要重新选择以获取新语言版本
|
||||
const currentSelected = currentType.value === 'optimize'
|
||||
? props.selectedOptimizeTemplate
|
||||
: props.selectedIterateTemplate
|
||||
|
||||
if (currentSelected && currentSelected.isBuiltin) {
|
||||
try {
|
||||
// 获取新语言版本的同一模板
|
||||
const updatedTemplate = templateManager.getTemplate(currentSelected.id)
|
||||
if (updatedTemplate) {
|
||||
emit('select', updatedTemplate, currentType.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update selected template after language change:', error)
|
||||
// 如果获取失败,选择第一个可用的模板
|
||||
try {
|
||||
const availableTemplates = templateManager.listTemplatesByType(currentType.value)
|
||||
if (availableTemplates.length > 0) {
|
||||
emit('select', availableTemplates[0], currentType.value)
|
||||
}
|
||||
} catch (listError) {
|
||||
console.error('Failed to list templates after language change:', listError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(async () => {
|
||||
await loadTemplates()
|
||||
})
|
||||
|
||||
// 监听表单消息数量变化,只在新增消息时初始化新textarea
|
||||
|
||||
@@ -136,8 +136,10 @@ const handleResize = () => {
|
||||
updateDropdownPosition()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
// 确保模板管理器已初始化
|
||||
await templateManager.ensureInitialized()
|
||||
refreshTemplates()
|
||||
})
|
||||
|
||||
@@ -159,6 +161,10 @@ watch(isOpen, async (newValue) => {
|
||||
const templates = computed(() => {
|
||||
// 使用 refreshTrigger 触发重新计算
|
||||
refreshTrigger.value
|
||||
// 检查模板管理器是否已初始化
|
||||
if (!templateManager.isInitialized()) {
|
||||
return []
|
||||
}
|
||||
return templateManager.listTemplatesByType(props.type)
|
||||
})
|
||||
|
||||
@@ -180,6 +186,10 @@ watch(
|
||||
// 改进刷新方法
|
||||
const refreshTemplates = () => {
|
||||
refreshTrigger.value++
|
||||
// 检查模板管理器是否已初始化
|
||||
if (!templateManager.isInitialized()) {
|
||||
return
|
||||
}
|
||||
// 刷新时也检查当前选中状态
|
||||
const currentTemplates = templateManager.listTemplatesByType(props.type)
|
||||
const currentTemplate = props.modelValue
|
||||
|
||||
@@ -27,7 +27,7 @@ export function useModals(
|
||||
// 加载提示词模板
|
||||
const loadTemplates = async () => {
|
||||
try {
|
||||
await templateManager.init()
|
||||
await templateManager.ensureInitialized()
|
||||
await initTemplateSelection()
|
||||
} catch (error) {
|
||||
console.error(t('toast.error.loadTemplatesFailed'), error)
|
||||
|
||||
@@ -198,6 +198,9 @@ export function usePromptOptimizer(
|
||||
// 初始化提示词选择
|
||||
const initTemplateSelection = async () => {
|
||||
try {
|
||||
// 确保模板管理器已初始化
|
||||
await templateManager.ensureInitialized()
|
||||
|
||||
// 加载优化提示词
|
||||
const optimizeTemplateId = await storage.getItem(STORAGE_KEYS.OPTIMIZE_TEMPLATE)
|
||||
if (optimizeTemplateId) {
|
||||
@@ -207,7 +210,7 @@ export function usePromptOptimizer(
|
||||
selectedOptimizeTemplate.value = optimizeTemplate
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(t('toast.warn.loadOptimizeTemplateFailed'), error)
|
||||
console.warn('加载已保存的优化提示词失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +231,7 @@ export function usePromptOptimizer(
|
||||
selectedIterateTemplate.value = iterateTemplate
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(t('toast.warn.loadIterateTemplateFailed'), error)
|
||||
console.warn('加载已保存的迭代提示词失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,11 +245,11 @@ export function usePromptOptimizer(
|
||||
|
||||
// 如果仍然无法加载任何提示词,显示错误
|
||||
if (!selectedOptimizeTemplate.value || !selectedIterateTemplate.value) {
|
||||
throw new Error(t('toast.error.noDefaultTemplate'))
|
||||
throw new Error('无法加载默认提示词')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(t('toast.error.loadTemplateFailed'), error)
|
||||
toast.error(t('toast.error.loadTemplateFailed'))
|
||||
console.error('加载模板失败', error)
|
||||
toast.error('加载模板失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,13 @@ export function useServiceInitializer(
|
||||
const llmService = createLLMService(modelManager)
|
||||
|
||||
// Initialize base services
|
||||
const initBaseServices = () => {
|
||||
const initBaseServices = async () => {
|
||||
try {
|
||||
console.log(t('log.info.initBaseServicesStart'))
|
||||
|
||||
// 确保模板管理器已初始化
|
||||
await templateManager.ensureInitialized()
|
||||
|
||||
// Get and verify template list
|
||||
const templates = templateManager.listTemplates()
|
||||
console.log(t('log.info.templateList'), templates)
|
||||
@@ -26,7 +29,7 @@ export function useServiceInitializer(
|
||||
// Create prompt service
|
||||
console.log(t('log.info.createPromptService'))
|
||||
promptServiceRef.value = createPromptService(modelManager, llmService, templateManager, historyManager)
|
||||
|
||||
|
||||
console.log(t('log.info.initComplete'))
|
||||
} catch (error) {
|
||||
console.error(t('log.error.initBaseServicesFailed'), error)
|
||||
@@ -36,8 +39,8 @@ export function useServiceInitializer(
|
||||
}
|
||||
|
||||
// Auto initialize on mounted
|
||||
onMounted(() => {
|
||||
initBaseServices()
|
||||
onMounted(async () => {
|
||||
await initBaseServices()
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -39,6 +39,9 @@ export function useTemplateManager(options: TemplateManagerOptions): TemplateMan
|
||||
// Initialize template selection
|
||||
const initTemplateSelection = async () => {
|
||||
try {
|
||||
// 确保模板管理器已初始化
|
||||
await templateManager.ensureInitialized()
|
||||
|
||||
// Load optimization template
|
||||
const optimizeTemplateId = await storage.getItem('app:selected-optimize-template')
|
||||
if (optimizeTemplateId) {
|
||||
@@ -48,7 +51,7 @@ export function useTemplateManager(options: TemplateManagerOptions): TemplateMan
|
||||
selectedOptimizeTemplate.value = optimizeTemplate
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(t('toast.warn.loadOptimizeTemplateFailed'), error)
|
||||
console.warn('加载已保存的优化提示词失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +72,7 @@ export function useTemplateManager(options: TemplateManagerOptions): TemplateMan
|
||||
selectedIterateTemplate.value = iterateTemplate
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(t('toast.warn.loadIterateTemplateFailed'), error)
|
||||
console.warn('加载已保存的迭代提示词失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +86,11 @@ export function useTemplateManager(options: TemplateManagerOptions): TemplateMan
|
||||
|
||||
// If still unable to load any templates, show error
|
||||
if (!selectedOptimizeTemplate.value || !selectedIterateTemplate.value) {
|
||||
throw new Error(t('toast.error.noDefaultTemplate'))
|
||||
throw new Error('无法加载默认提示词')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(t('toast.error.initTemplateFailed'), error)
|
||||
toast.error(t('toast.error.initTemplateFailed'))
|
||||
console.error('初始化模板选择失败', error)
|
||||
toast.error('初始化模板选择失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -316,6 +316,11 @@ export default {
|
||||
configure: 'Configure Template',
|
||||
selected: 'Selected',
|
||||
select: 'Select',
|
||||
builtinLanguage: 'Built-in Template Language',
|
||||
switchBuiltinLanguage: 'Switch built-in template language',
|
||||
languageChanged: 'Built-in template language switched to {language}',
|
||||
languageChangeError: 'Failed to switch built-in template language',
|
||||
languageInitError: 'Failed to initialize built-in template language',
|
||||
type: {
|
||||
optimize: 'Optimize',
|
||||
iterate: 'Iterate'
|
||||
@@ -421,6 +426,7 @@ export default {
|
||||
},
|
||||
success: {
|
||||
optimizeSuccess: 'Optimization successful',
|
||||
iterateComplete: 'Iteration completed',
|
||||
iterateSuccess: 'Iteration optimization successful',
|
||||
modelSelected: 'Model selected: {name}',
|
||||
templateSelected: '{type} template selected: {name}',
|
||||
|
||||
@@ -316,6 +316,11 @@ export default {
|
||||
configure: '配置提示词',
|
||||
selected: '已选择',
|
||||
select: '选择',
|
||||
builtinLanguage: '内置模板语言',
|
||||
switchBuiltinLanguage: '切换内置模板语言',
|
||||
languageChanged: '内置模板语言已切换为 {language}',
|
||||
languageChangeError: '切换内置模板语言失败',
|
||||
languageInitError: '初始化内置模板语言失败',
|
||||
type: {
|
||||
optimize: '优化',
|
||||
iterate: '迭代'
|
||||
@@ -421,6 +426,7 @@ export default {
|
||||
},
|
||||
success: {
|
||||
optimizeSuccess: '优化成功',
|
||||
iterateComplete: '迭代完成',
|
||||
iterateSuccess: '迭代优化成功',
|
||||
modelSelected: '已选择模型: {name}',
|
||||
templateSelected: '已选择{type}提示词: {name}',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// 导入样式
|
||||
// 导入样式
|
||||
import 'element-plus/dist/index.css'
|
||||
import './styles/index.css'
|
||||
import './styles/scrollbar.css'
|
||||
@@ -29,6 +29,7 @@ export { default as ActionButtonUI } from './components/ActionButton.vue'
|
||||
export { default as ThemeToggleUI } from './components/ThemeToggleUI.vue'
|
||||
export { default as TestPanelUI } from './components/TestPanel.vue'
|
||||
export { default as LanguageSwitchUI } from './components/LanguageSwitch.vue'
|
||||
export { default as BuiltinTemplateLanguageSwitchUi } from './components/BuiltinTemplateLanguageSwitch.vue'
|
||||
export { default as DataManagerUI } from './components/DataManager.vue'
|
||||
|
||||
// 导出指令
|
||||
|
||||
Reference in new issue
Block a user