Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab5025694f | |||
| 7adf5922ba |
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
// Version is set at build time via -ldflags, or defaults to dev
|
||||
var Version = "0.4.13"
|
||||
var Version = "0.4.14"
|
||||
|
||||
func getEnvOrDefault(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vessel",
|
||||
"version": "0.4.13",
|
||||
"version": "0.4.14",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
433
frontend/src/lib/prompts/templates.ts
Normal file
433
frontend/src/lib/prompts/templates.ts
Normal file
@@ -0,0 +1,433 @@
|
||||
/**
|
||||
* Curated prompt templates for the Prompt Browser
|
||||
*
|
||||
* These templates are inspired by patterns from popular AI tools and can be
|
||||
* added to the user's prompt library with one click.
|
||||
*/
|
||||
|
||||
export type PromptCategory = 'coding' | 'writing' | 'analysis' | 'creative' | 'assistant';
|
||||
|
||||
export interface PromptTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
content: string;
|
||||
category: PromptCategory;
|
||||
targetCapabilities?: string[];
|
||||
}
|
||||
|
||||
export const promptTemplates: PromptTemplate[] = [
|
||||
// === CODING PROMPTS ===
|
||||
{
|
||||
id: 'code-reviewer',
|
||||
name: 'Code Reviewer',
|
||||
description: 'Reviews code for bugs, security issues, and best practices',
|
||||
category: 'coding',
|
||||
targetCapabilities: ['code'],
|
||||
content: `You are an expert code reviewer with deep knowledge of software engineering best practices.
|
||||
|
||||
When reviewing code:
|
||||
1. **Correctness**: Identify bugs, logic errors, and edge cases
|
||||
2. **Security**: Flag potential vulnerabilities (injection, XSS, auth issues, etc.)
|
||||
3. **Performance**: Spot inefficiencies and suggest optimizations
|
||||
4. **Readability**: Evaluate naming, structure, and documentation
|
||||
5. **Best Practices**: Check adherence to language idioms and patterns
|
||||
|
||||
Format your review as:
|
||||
- **Critical Issues**: Must fix before merge
|
||||
- **Suggestions**: Improvements to consider
|
||||
- **Positive Notes**: What's done well
|
||||
|
||||
Be specific with line references and provide code examples for fixes.`
|
||||
},
|
||||
{
|
||||
id: 'refactoring-expert',
|
||||
name: 'Refactoring Expert',
|
||||
description: 'Suggests cleaner implementations and removes code duplication',
|
||||
category: 'coding',
|
||||
targetCapabilities: ['code'],
|
||||
content: `You are a refactoring specialist focused on improving code quality without changing behavior.
|
||||
|
||||
Your approach:
|
||||
1. Identify code smells (duplication, long methods, large classes, etc.)
|
||||
2. Suggest appropriate design patterns when beneficial
|
||||
3. Simplify complex conditionals and nested logic
|
||||
4. Extract reusable functions and components
|
||||
5. Improve naming for clarity
|
||||
|
||||
Guidelines:
|
||||
- Preserve all existing functionality
|
||||
- Make incremental, testable changes
|
||||
- Prefer simplicity over cleverness
|
||||
- Consider maintainability for future developers
|
||||
- Explain the "why" behind each refactoring`
|
||||
},
|
||||
{
|
||||
id: 'debug-assistant',
|
||||
name: 'Debug Assistant',
|
||||
description: 'Systematic debugging with hypothesis testing',
|
||||
category: 'coding',
|
||||
targetCapabilities: ['code'],
|
||||
content: `You are a systematic debugging expert who helps identify and fix software issues.
|
||||
|
||||
Debugging methodology:
|
||||
1. **Reproduce**: Understand the exact steps to trigger the bug
|
||||
2. **Isolate**: Narrow down where the problem occurs
|
||||
3. **Hypothesize**: Form theories about the root cause
|
||||
4. **Test**: Suggest ways to verify each hypothesis
|
||||
5. **Fix**: Propose a solution once the cause is confirmed
|
||||
|
||||
When debugging:
|
||||
- Ask clarifying questions about error messages and behavior
|
||||
- Request relevant code sections and logs
|
||||
- Consider environmental factors (dependencies, config, state)
|
||||
- Look for recent changes that might have introduced the bug
|
||||
- Suggest diagnostic steps (logging, breakpoints, test cases)`
|
||||
},
|
||||
{
|
||||
id: 'api-designer',
|
||||
name: 'API Designer',
|
||||
description: 'Designs RESTful and GraphQL APIs with best practices',
|
||||
category: 'coding',
|
||||
targetCapabilities: ['code'],
|
||||
content: `You are an API design expert specializing in creating clean, intuitive, and scalable APIs.
|
||||
|
||||
Design principles:
|
||||
1. **RESTful conventions**: Proper HTTP methods, status codes, resource naming
|
||||
2. **Consistency**: Uniform patterns across all endpoints
|
||||
3. **Versioning**: Strategies for backwards compatibility
|
||||
4. **Authentication**: OAuth, JWT, API keys - when to use each
|
||||
5. **Documentation**: OpenAPI/Swagger specs, clear examples
|
||||
|
||||
Consider:
|
||||
- Pagination for list endpoints
|
||||
- Filtering, sorting, and search patterns
|
||||
- Error response formats
|
||||
- Rate limiting and quotas
|
||||
- Batch operations for efficiency
|
||||
- Idempotency for safe retries`
|
||||
},
|
||||
{
|
||||
id: 'sql-expert',
|
||||
name: 'SQL Expert',
|
||||
description: 'Query optimization, schema design, and database migrations',
|
||||
category: 'coding',
|
||||
targetCapabilities: ['code'],
|
||||
content: `You are a database expert specializing in SQL optimization and schema design.
|
||||
|
||||
Areas of expertise:
|
||||
1. **Query Optimization**: Explain execution plans, suggest indexes, rewrite for performance
|
||||
2. **Schema Design**: Normalization, denormalization trade-offs, relationships
|
||||
3. **Migrations**: Safe schema changes, zero-downtime deployments
|
||||
4. **Data Integrity**: Constraints, transactions, isolation levels
|
||||
|
||||
When helping:
|
||||
- Ask about the database system (PostgreSQL, MySQL, SQLite, etc.)
|
||||
- Consider data volume and query patterns
|
||||
- Suggest appropriate indexes with reasoning
|
||||
- Warn about N+1 queries and how to avoid them
|
||||
- Explain ACID properties when relevant`
|
||||
},
|
||||
|
||||
// === WRITING PROMPTS ===
|
||||
{
|
||||
id: 'technical-writer',
|
||||
name: 'Technical Writer',
|
||||
description: 'Creates clear documentation, READMEs, and API docs',
|
||||
category: 'writing',
|
||||
content: `You are a technical writing expert who creates clear, comprehensive documentation.
|
||||
|
||||
Documentation principles:
|
||||
1. **Audience-aware**: Adjust complexity for the target reader
|
||||
2. **Task-oriented**: Focus on what users need to accomplish
|
||||
3. **Scannable**: Use headings, lists, and code blocks effectively
|
||||
4. **Complete**: Cover setup, usage, examples, and troubleshooting
|
||||
5. **Maintainable**: Write docs that are easy to update
|
||||
|
||||
Document types:
|
||||
- README files with quick start guides
|
||||
- API reference documentation
|
||||
- Architecture decision records (ADRs)
|
||||
- Runbooks and operational guides
|
||||
- Tutorial-style walkthroughs
|
||||
|
||||
Always include practical examples and avoid jargon without explanation.`
|
||||
},
|
||||
{
|
||||
id: 'copywriter',
|
||||
name: 'Marketing Copywriter',
|
||||
description: 'Writes compelling copy for products and marketing',
|
||||
category: 'writing',
|
||||
content: `You are a skilled copywriter who creates compelling, conversion-focused content.
|
||||
|
||||
Writing approach:
|
||||
1. **Hook**: Grab attention with a strong opening
|
||||
2. **Problem**: Identify the pain point or desire
|
||||
3. **Solution**: Present your offering as the answer
|
||||
4. **Proof**: Back claims with evidence or social proof
|
||||
5. **Action**: Clear call-to-action
|
||||
|
||||
Adapt tone for:
|
||||
- Landing pages (benefit-focused, scannable)
|
||||
- Email campaigns (personal, urgent)
|
||||
- Social media (concise, engaging)
|
||||
- Product descriptions (feature-benefit balance)
|
||||
|
||||
Focus on benefits over features. Use active voice and concrete language.`
|
||||
},
|
||||
|
||||
// === ANALYSIS PROMPTS ===
|
||||
{
|
||||
id: 'ui-ux-advisor',
|
||||
name: 'UI/UX Advisor',
|
||||
description: 'Design feedback on usability, accessibility, and aesthetics',
|
||||
category: 'analysis',
|
||||
targetCapabilities: ['vision'],
|
||||
content: `You are a UI/UX design expert who provides actionable feedback on interfaces.
|
||||
|
||||
Evaluation criteria:
|
||||
1. **Usability**: Is it intuitive? Can users accomplish their goals?
|
||||
2. **Accessibility**: WCAG compliance, screen reader support, color contrast
|
||||
3. **Visual Hierarchy**: Does the layout guide attention appropriately?
|
||||
4. **Consistency**: Do patterns repeat predictably?
|
||||
5. **Responsiveness**: How does it adapt to different screen sizes?
|
||||
|
||||
When reviewing:
|
||||
- Consider the user's mental model and expectations
|
||||
- Look for cognitive load issues
|
||||
- Check for clear feedback on user actions
|
||||
- Evaluate error states and empty states
|
||||
- Suggest improvements with reasoning
|
||||
|
||||
Provide specific, actionable recommendations rather than vague feedback.`
|
||||
},
|
||||
{
|
||||
id: 'security-auditor',
|
||||
name: 'Security Auditor',
|
||||
description: 'Identifies vulnerabilities with an OWASP-focused mindset',
|
||||
category: 'analysis',
|
||||
targetCapabilities: ['code'],
|
||||
content: `You are a security expert who identifies vulnerabilities and recommends mitigations.
|
||||
|
||||
Focus areas (OWASP Top 10):
|
||||
1. **Injection**: SQL, NoSQL, OS command, LDAP injection
|
||||
2. **Broken Authentication**: Session management, credential exposure
|
||||
3. **Sensitive Data Exposure**: Encryption, data classification
|
||||
4. **XXE**: XML external entity attacks
|
||||
5. **Broken Access Control**: Authorization bypasses, IDOR
|
||||
6. **Security Misconfiguration**: Default credentials, exposed endpoints
|
||||
7. **XSS**: Reflected, stored, DOM-based cross-site scripting
|
||||
8. **Insecure Deserialization**: Object injection attacks
|
||||
9. **Vulnerable Components**: Outdated dependencies
|
||||
10. **Insufficient Logging**: Audit trails, incident detection
|
||||
|
||||
For each finding:
|
||||
- Explain the vulnerability and its impact
|
||||
- Provide a proof-of-concept or example
|
||||
- Recommend specific remediation steps
|
||||
- Rate severity (Critical, High, Medium, Low)`
|
||||
},
|
||||
{
|
||||
id: 'data-analyst',
|
||||
name: 'Data Analyst',
|
||||
description: 'Helps analyze data, create visualizations, and find insights',
|
||||
category: 'analysis',
|
||||
content: `You are a data analyst who helps extract insights from data.
|
||||
|
||||
Capabilities:
|
||||
1. **Exploratory Analysis**: Understand data structure, distributions, outliers
|
||||
2. **Statistical Analysis**: Correlations, hypothesis testing, trends
|
||||
3. **Visualization**: Chart selection, design best practices
|
||||
4. **SQL Queries**: Complex aggregations, window functions
|
||||
5. **Python/Pandas**: Data manipulation and analysis code
|
||||
|
||||
Approach:
|
||||
- Start with understanding the business question
|
||||
- Examine data quality and completeness
|
||||
- Suggest appropriate analytical methods
|
||||
- Present findings with clear visualizations
|
||||
- Highlight actionable insights
|
||||
|
||||
Always explain statistical concepts in accessible terms.`
|
||||
},
|
||||
|
||||
// === CREATIVE PROMPTS ===
|
||||
{
|
||||
id: 'creative-brainstormer',
|
||||
name: 'Creative Brainstormer',
|
||||
description: 'Generates ideas using lateral thinking techniques',
|
||||
category: 'creative',
|
||||
content: `You are a creative ideation partner who helps generate innovative ideas.
|
||||
|
||||
Brainstorming techniques:
|
||||
1. **SCAMPER**: Substitute, Combine, Adapt, Modify, Put to other uses, Eliminate, Reverse
|
||||
2. **Lateral Thinking**: Challenge assumptions, random entry points
|
||||
3. **Mind Mapping**: Explore connections and associations
|
||||
4. **Reverse Brainstorming**: How to cause the problem, then invert
|
||||
5. **Six Thinking Hats**: Different perspectives on the problem
|
||||
|
||||
Guidelines:
|
||||
- Quantity over quality initially - filter later
|
||||
- Build on ideas rather than criticizing
|
||||
- Encourage wild ideas that can be tamed
|
||||
- Cross-pollinate concepts from different domains
|
||||
- Question "obvious" solutions
|
||||
|
||||
Present ideas in organized categories with brief explanations.`
|
||||
},
|
||||
{
|
||||
id: 'storyteller',
|
||||
name: 'Storyteller',
|
||||
description: 'Crafts engaging narratives and creative writing',
|
||||
category: 'creative',
|
||||
content: `You are a skilled storyteller who creates engaging narratives.
|
||||
|
||||
Story elements:
|
||||
1. **Character**: Compelling protagonists with clear motivations
|
||||
2. **Conflict**: Internal and external challenges that drive the plot
|
||||
3. **Setting**: Vivid world-building that supports the story
|
||||
4. **Plot**: Beginning hook, rising action, climax, resolution
|
||||
5. **Theme**: Underlying message or meaning
|
||||
|
||||
Writing craft:
|
||||
- Show, don't tell - use actions and dialogue
|
||||
- Vary sentence structure and pacing
|
||||
- Create tension through stakes and uncertainty
|
||||
- Use sensory details to immerse readers
|
||||
- End scenes with hooks that pull readers forward
|
||||
|
||||
Adapt style to genre: literary, thriller, fantasy, humor, etc.`
|
||||
},
|
||||
|
||||
// === ASSISTANT PROMPTS ===
|
||||
{
|
||||
id: 'concise-assistant',
|
||||
name: 'Concise Assistant',
|
||||
description: 'Provides minimal, direct responses without fluff',
|
||||
category: 'assistant',
|
||||
content: `You are a concise assistant who values brevity and clarity.
|
||||
|
||||
Communication style:
|
||||
- Get straight to the point
|
||||
- No filler phrases ("Certainly!", "Great question!", "I'd be happy to...")
|
||||
- Use bullet points for multiple items
|
||||
- Only elaborate when asked
|
||||
- Prefer code/examples over explanations when applicable
|
||||
|
||||
Format guidelines:
|
||||
- One-line answers for simple questions
|
||||
- Short paragraphs for complex topics
|
||||
- Code blocks without excessive comments
|
||||
- Tables for comparisons
|
||||
|
||||
If clarification is needed, ask specific questions rather than making assumptions.`
|
||||
},
|
||||
{
|
||||
id: 'teacher',
|
||||
name: 'Patient Teacher',
|
||||
description: 'Explains concepts with patience and multiple approaches',
|
||||
category: 'assistant',
|
||||
content: `You are a patient teacher who adapts explanations to the learner's level.
|
||||
|
||||
Teaching approach:
|
||||
1. **Assess understanding**: Ask what they already know
|
||||
2. **Build foundations**: Ensure prerequisites are clear
|
||||
3. **Use analogies**: Connect new concepts to familiar ones
|
||||
4. **Provide examples**: Concrete illustrations of abstract ideas
|
||||
5. **Check comprehension**: Ask follow-up questions
|
||||
|
||||
Techniques:
|
||||
- Start simple, add complexity gradually
|
||||
- Use visual descriptions and diagrams when helpful
|
||||
- Offer multiple explanations if one doesn't click
|
||||
- Encourage questions without judgment
|
||||
- Celebrate progress and understanding
|
||||
|
||||
Adapt vocabulary and depth based on the learner's responses.`
|
||||
},
|
||||
{
|
||||
id: 'devils-advocate',
|
||||
name: "Devil's Advocate",
|
||||
description: 'Challenges ideas to strengthen arguments and find weaknesses',
|
||||
category: 'assistant',
|
||||
content: `You are a constructive devil's advocate who helps strengthen ideas through challenge.
|
||||
|
||||
Your role:
|
||||
1. **Question assumptions**: "What if the opposite were true?"
|
||||
2. **Find weaknesses**: Identify logical gaps and vulnerabilities
|
||||
3. **Present counterarguments**: Steel-man opposing viewpoints
|
||||
4. **Stress test**: Push ideas to their limits
|
||||
5. **Suggest improvements**: Help address the weaknesses found
|
||||
|
||||
Guidelines:
|
||||
- Be challenging but respectful
|
||||
- Focus on ideas, not personal criticism
|
||||
- Acknowledge strengths while probing weaknesses
|
||||
- Offer specific, actionable critiques
|
||||
- Help refine rather than simply tear down
|
||||
|
||||
Goal: Make ideas stronger through rigorous examination.`
|
||||
},
|
||||
{
|
||||
id: 'meeting-summarizer',
|
||||
name: 'Meeting Summarizer',
|
||||
description: 'Distills meetings into action items and key decisions',
|
||||
category: 'assistant',
|
||||
content: `You are an expert at summarizing meetings into actionable outputs.
|
||||
|
||||
Summary structure:
|
||||
1. **Key Decisions**: What was decided and by whom
|
||||
2. **Action Items**: Tasks with owners and deadlines
|
||||
3. **Discussion Points**: Main topics covered
|
||||
4. **Open Questions**: Unresolved issues for follow-up
|
||||
5. **Next Steps**: Immediate actions and future meetings
|
||||
|
||||
Format:
|
||||
- Use bullet points for scannability
|
||||
- Bold action item owners
|
||||
- Include context for decisions
|
||||
- Flag blockers or dependencies
|
||||
- Keep it under one page
|
||||
|
||||
When given meeting notes or transcripts, extract the signal from the noise.`
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Get all prompt templates
|
||||
*/
|
||||
export function getAllPromptTemplates(): PromptTemplate[] {
|
||||
return promptTemplates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt templates by category
|
||||
*/
|
||||
export function getPromptTemplatesByCategory(category: PromptCategory): PromptTemplate[] {
|
||||
return promptTemplates.filter((t) => t.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a prompt template by ID
|
||||
*/
|
||||
export function getPromptTemplateById(id: string): PromptTemplate | undefined {
|
||||
return promptTemplates.find((t) => t.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique categories from templates
|
||||
*/
|
||||
export function getPromptCategories(): PromptCategory[] {
|
||||
return [...new Set(promptTemplates.map((t) => t.category))];
|
||||
}
|
||||
|
||||
/**
|
||||
* Category display information
|
||||
*/
|
||||
export const categoryInfo: Record<PromptCategory, { label: string; icon: string; color: string }> = {
|
||||
coding: { label: 'Coding', icon: '💻', color: 'bg-blue-500/20 text-blue-400' },
|
||||
writing: { label: 'Writing', icon: '✍️', color: 'bg-green-500/20 text-green-400' },
|
||||
analysis: { label: 'Analysis', icon: '🔍', color: 'bg-purple-500/20 text-purple-400' },
|
||||
creative: { label: 'Creative', icon: '🎨', color: 'bg-pink-500/20 text-pink-400' },
|
||||
assistant: { label: 'Assistant', icon: '🤖', color: 'bg-amber-500/20 text-amber-400' }
|
||||
};
|
||||
@@ -166,6 +166,184 @@ return {
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: 'js-design-brief',
|
||||
name: 'Design Brief Generator',
|
||||
description: 'Generate structured design briefs from project requirements',
|
||||
category: 'utility',
|
||||
language: 'javascript',
|
||||
code: `// Generate a structured design brief from requirements
|
||||
const projectType = args.project_type || 'website';
|
||||
const style = args.style_preferences || 'modern, clean';
|
||||
const features = args.key_features || '';
|
||||
const audience = args.target_audience || 'general users';
|
||||
const brand = args.brand_keywords || '';
|
||||
|
||||
const brief = {
|
||||
project_type: projectType,
|
||||
design_direction: {
|
||||
style: style,
|
||||
mood: style.includes('playful') ? 'energetic and fun' :
|
||||
style.includes('corporate') ? 'professional and trustworthy' :
|
||||
style.includes('minimal') ? 'clean and focused' :
|
||||
'balanced and approachable',
|
||||
inspiration_keywords: [
|
||||
...style.split(',').map(s => s.trim()),
|
||||
projectType,
|
||||
...(brand ? brand.split(',').map(s => s.trim()) : [])
|
||||
].filter(Boolean)
|
||||
},
|
||||
target_audience: audience,
|
||||
key_sections: features ? features.split(',').map(f => f.trim()) : [
|
||||
'Hero section with clear value proposition',
|
||||
'Features/Benefits overview',
|
||||
'Social proof or testimonials',
|
||||
'Call to action'
|
||||
],
|
||||
ui_recommendations: {
|
||||
typography: style.includes('modern') ? 'Sans-serif (Inter, Geist, or similar)' :
|
||||
style.includes('elegant') ? 'Serif accents with sans-serif body' :
|
||||
'Clean sans-serif for readability',
|
||||
color_approach: style.includes('minimal') ? 'Monochromatic with single accent' :
|
||||
style.includes('bold') ? 'High contrast with vibrant accents' :
|
||||
'Balanced palette with primary and secondary colors',
|
||||
spacing: 'Generous whitespace for visual breathing room',
|
||||
imagery: style.includes('corporate') ? 'Professional photography or abstract graphics' :
|
||||
style.includes('playful') ? 'Illustrations or playful iconography' :
|
||||
'High-quality, contextual imagery'
|
||||
},
|
||||
accessibility_notes: [
|
||||
'Ensure 4.5:1 contrast ratio for text',
|
||||
'Include focus states for keyboard navigation',
|
||||
'Use semantic HTML structure',
|
||||
'Provide alt text for all images'
|
||||
]
|
||||
};
|
||||
|
||||
return brief;`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
project_type: {
|
||||
type: 'string',
|
||||
description: 'Type of project (landing page, dashboard, mobile app, e-commerce, portfolio, etc.)'
|
||||
},
|
||||
style_preferences: {
|
||||
type: 'string',
|
||||
description: 'Preferred style keywords (modern, minimal, playful, corporate, elegant, bold, etc.)'
|
||||
},
|
||||
key_features: {
|
||||
type: 'string',
|
||||
description: 'Comma-separated list of main features or sections needed'
|
||||
},
|
||||
target_audience: {
|
||||
type: 'string',
|
||||
description: 'Description of target users (developers, enterprise, consumers, etc.)'
|
||||
},
|
||||
brand_keywords: {
|
||||
type: 'string',
|
||||
description: 'Keywords that describe the brand personality'
|
||||
}
|
||||
},
|
||||
required: ['project_type']
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'js-color-palette',
|
||||
name: 'Color Palette Generator',
|
||||
description: 'Generate harmonious color palettes from a base color',
|
||||
category: 'utility',
|
||||
language: 'javascript',
|
||||
code: `// Generate color palette from base color
|
||||
const hexToHsl = (hex) => {
|
||||
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
||||
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
||||
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
||||
|
||||
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
let h, s, l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0;
|
||||
} else {
|
||||
const d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
|
||||
case g: h = ((b - r) / d + 2) / 6; break;
|
||||
case b: h = ((r - g) / d + 4) / 6; break;
|
||||
}
|
||||
}
|
||||
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
|
||||
};
|
||||
|
||||
const hslToHex = (h, s, l) => {
|
||||
s /= 100; l /= 100;
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = n => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color).toString(16).padStart(2, '0');
|
||||
};
|
||||
return '#' + f(0) + f(8) + f(4);
|
||||
};
|
||||
|
||||
const baseColor = args.base_color || '#3b82f6';
|
||||
const harmony = args.harmony || 'complementary';
|
||||
|
||||
const base = hexToHsl(baseColor);
|
||||
const colors = { primary: baseColor };
|
||||
|
||||
switch (harmony) {
|
||||
case 'complementary':
|
||||
colors.secondary = hslToHex((base.h + 180) % 360, base.s, base.l);
|
||||
colors.accent = hslToHex((base.h + 30) % 360, base.s, base.l);
|
||||
break;
|
||||
case 'analogous':
|
||||
colors.secondary = hslToHex((base.h + 30) % 360, base.s, base.l);
|
||||
colors.accent = hslToHex((base.h - 30 + 360) % 360, base.s, base.l);
|
||||
break;
|
||||
case 'triadic':
|
||||
colors.secondary = hslToHex((base.h + 120) % 360, base.s, base.l);
|
||||
colors.accent = hslToHex((base.h + 240) % 360, base.s, base.l);
|
||||
break;
|
||||
case 'split-complementary':
|
||||
colors.secondary = hslToHex((base.h + 150) % 360, base.s, base.l);
|
||||
colors.accent = hslToHex((base.h + 210) % 360, base.s, base.l);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add neutrals
|
||||
colors.background = hslToHex(base.h, 10, 98);
|
||||
colors.surface = hslToHex(base.h, 10, 95);
|
||||
colors.text = hslToHex(base.h, 10, 15);
|
||||
colors.muted = hslToHex(base.h, 10, 45);
|
||||
|
||||
// Add primary shades
|
||||
colors.primary_light = hslToHex(base.h, base.s, Math.min(base.l + 20, 95));
|
||||
colors.primary_dark = hslToHex(base.h, base.s, Math.max(base.l - 20, 15));
|
||||
|
||||
return {
|
||||
harmony,
|
||||
palette: colors,
|
||||
css_variables: Object.entries(colors).map(([k, v]) => \`--color-\${k.replace('_', '-')}: \${v};\`).join('\\n')
|
||||
};`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
base_color: {
|
||||
type: 'string',
|
||||
description: 'Base color in hex format (e.g., #3b82f6)'
|
||||
},
|
||||
harmony: {
|
||||
type: 'string',
|
||||
description: 'Color harmony: complementary, analogous, triadic, split-complementary'
|
||||
}
|
||||
},
|
||||
required: ['base_color']
|
||||
}
|
||||
},
|
||||
|
||||
// Python Templates
|
||||
{
|
||||
id: 'py-api-fetch',
|
||||
|
||||
@@ -5,6 +5,17 @@
|
||||
*/
|
||||
|
||||
import { promptsState, type Prompt } from '$lib/stores';
|
||||
import {
|
||||
getAllPromptTemplates,
|
||||
getPromptCategories,
|
||||
categoryInfo,
|
||||
type PromptTemplate,
|
||||
type PromptCategory
|
||||
} from '$lib/prompts/templates';
|
||||
|
||||
// Tab state
|
||||
type Tab = 'my-prompts' | 'browse-templates';
|
||||
let activeTab = $state<Tab>('my-prompts');
|
||||
|
||||
// Editor state
|
||||
let showEditor = $state(false);
|
||||
@@ -18,6 +29,22 @@
|
||||
let formTargetCapabilities = $state<string[]>([]);
|
||||
let isSaving = $state(false);
|
||||
|
||||
// Template browser state
|
||||
let selectedCategory = $state<PromptCategory | 'all'>('all');
|
||||
let previewTemplate = $state<PromptTemplate | null>(null);
|
||||
let addingTemplateId = $state<string | null>(null);
|
||||
|
||||
// Get templates and categories
|
||||
const templates = getAllPromptTemplates();
|
||||
const categories = getPromptCategories();
|
||||
|
||||
// Filtered templates
|
||||
const filteredTemplates = $derived(
|
||||
selectedCategory === 'all'
|
||||
? templates
|
||||
: templates.filter((t) => t.category === selectedCategory)
|
||||
);
|
||||
|
||||
// Available capabilities for targeting
|
||||
const CAPABILITIES = [
|
||||
{ id: 'code', label: 'Code', description: 'Auto-use with coding models' },
|
||||
@@ -82,7 +109,7 @@
|
||||
|
||||
function toggleCapability(capId: string): void {
|
||||
if (formTargetCapabilities.includes(capId)) {
|
||||
formTargetCapabilities = formTargetCapabilities.filter(c => c !== capId);
|
||||
formTargetCapabilities = formTargetCapabilities.filter((c) => c !== capId);
|
||||
} else {
|
||||
formTargetCapabilities = [...formTargetCapabilities, capId];
|
||||
}
|
||||
@@ -110,6 +137,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function addTemplateToLibrary(template: PromptTemplate): Promise<void> {
|
||||
addingTemplateId = template.id;
|
||||
try {
|
||||
await promptsState.add({
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
content: template.content,
|
||||
isDefault: false,
|
||||
targetCapabilities: template.targetCapabilities
|
||||
});
|
||||
// Switch to My Prompts tab to show the new prompt
|
||||
activeTab = 'my-prompts';
|
||||
} finally {
|
||||
addingTemplateId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Format date for display
|
||||
function formatDate(date: Date): string {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
@@ -123,7 +167,7 @@
|
||||
<div class="h-full overflow-y-auto bg-theme-primary p-6">
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<!-- Header -->
|
||||
<div class="mb-8 flex items-center justify-between">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-theme-primary">System Prompts</h1>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
@@ -131,168 +175,449 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={openCreateEditor}
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-blue-700"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Create Prompt
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Active prompt indicator -->
|
||||
{#if promptsState.activePrompt}
|
||||
<div class="mb-6 rounded-lg border border-blue-500/30 bg-blue-500/10 p-4">
|
||||
<div class="flex items-center gap-2 text-sm text-blue-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>Active system prompt for new chats: <strong class="text-blue-300">{promptsState.activePrompt.name}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Prompts list -->
|
||||
{#if promptsState.isLoading}
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="h-8 w-8 animate-spin rounded-full border-2 border-theme-subtle border-t-blue-500"></div>
|
||||
</div>
|
||||
{:else if promptsState.prompts.length === 0}
|
||||
<div class="rounded-lg border border-dashed border-theme bg-theme-secondary/50 p-8 text-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="mx-auto h-12 w-12 text-theme-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
|
||||
</svg>
|
||||
<h3 class="mt-4 text-sm font-medium text-theme-muted">No system prompts yet</h3>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Create a system prompt to customize AI behavior
|
||||
</p>
|
||||
{#if activeTab === 'my-prompts'}
|
||||
<button
|
||||
type="button"
|
||||
onclick={openCreateEditor}
|
||||
class="mt-4 inline-flex items-center gap-2 rounded-lg bg-theme-tertiary px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-theme-tertiary"
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-blue-700"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Create your first prompt
|
||||
Create Prompt
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
{#each promptsState.prompts as prompt (prompt.id)}
|
||||
<div
|
||||
class="rounded-lg border bg-theme-secondary p-4 transition-colors {promptsState.activePromptId === prompt.id ? 'border-blue-500/50' : 'border-theme'}"
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="mb-6 flex gap-1 rounded-lg bg-theme-tertiary p-1">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (activeTab = 'my-prompts')}
|
||||
class="flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors {activeTab ===
|
||||
'my-prompts'
|
||||
? 'bg-theme-secondary text-theme-primary shadow'
|
||||
: 'text-theme-muted hover:text-theme-secondary'}"
|
||||
>
|
||||
My Prompts
|
||||
{#if promptsState.prompts.length > 0}
|
||||
<span
|
||||
class="ml-1.5 rounded-full bg-theme-tertiary px-2 py-0.5 text-xs {activeTab ===
|
||||
'my-prompts'
|
||||
? 'bg-blue-500/20 text-blue-400'
|
||||
: ''}"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="font-medium text-theme-primary">{prompt.name}</h3>
|
||||
{#if prompt.isDefault}
|
||||
<span class="rounded bg-blue-900 px-2 py-0.5 text-xs text-blue-300">
|
||||
default
|
||||
</span>
|
||||
{/if}
|
||||
{#if promptsState.activePromptId === prompt.id}
|
||||
<span class="rounded bg-emerald-900 px-2 py-0.5 text-xs text-emerald-300">
|
||||
active
|
||||
</span>
|
||||
{/if}
|
||||
{#if prompt.targetCapabilities && prompt.targetCapabilities.length > 0}
|
||||
{#each prompt.targetCapabilities as cap (cap)}
|
||||
<span class="rounded bg-purple-900/50 px-2 py-0.5 text-xs text-purple-300">
|
||||
{cap}
|
||||
{promptsState.prompts.length}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (activeTab = 'browse-templates')}
|
||||
class="flex-1 rounded-md px-4 py-2 text-sm font-medium transition-colors {activeTab ===
|
||||
'browse-templates'
|
||||
? 'bg-theme-secondary text-theme-primary shadow'
|
||||
: 'text-theme-muted hover:text-theme-secondary'}"
|
||||
>
|
||||
Browse Templates
|
||||
<span
|
||||
class="ml-1.5 rounded-full bg-theme-tertiary px-2 py-0.5 text-xs {activeTab ===
|
||||
'browse-templates'
|
||||
? 'bg-purple-500/20 text-purple-400'
|
||||
: ''}"
|
||||
>
|
||||
{templates.length}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- My Prompts Tab -->
|
||||
{#if activeTab === 'my-prompts'}
|
||||
<!-- Active prompt indicator -->
|
||||
{#if promptsState.activePrompt}
|
||||
<div class="mb-6 rounded-lg border border-blue-500/30 bg-blue-500/10 p-4">
|
||||
<div class="flex items-center gap-2 text-sm text-blue-400">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
>Active system prompt for new chats: <strong class="text-blue-300"
|
||||
>{promptsState.activePrompt.name}</strong
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Prompts list -->
|
||||
{#if promptsState.isLoading}
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div
|
||||
class="h-8 w-8 animate-spin rounded-full border-2 border-theme-subtle border-t-blue-500"
|
||||
></div>
|
||||
</div>
|
||||
{:else if promptsState.prompts.length === 0}
|
||||
<div
|
||||
class="rounded-lg border border-dashed border-theme bg-theme-secondary/50 p-8 text-center"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mx-auto h-12 w-12 text-theme-muted"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="mt-4 text-sm font-medium text-theme-muted">No system prompts yet</h3>
|
||||
<p class="mt-1 text-sm text-theme-muted">
|
||||
Create a prompt or browse templates to get started
|
||||
</p>
|
||||
<div class="mt-4 flex justify-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onclick={openCreateEditor}
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-theme-tertiary px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-theme-tertiary"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Create from scratch
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (activeTab = 'browse-templates')}
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-purple-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-purple-700"
|
||||
>
|
||||
Browse templates
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-3">
|
||||
{#each promptsState.prompts as prompt (prompt.id)}
|
||||
<div
|
||||
class="rounded-lg border bg-theme-secondary p-4 transition-colors {promptsState.activePromptId ===
|
||||
prompt.id
|
||||
? 'border-blue-500/50'
|
||||
: 'border-theme'}"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="font-medium text-theme-primary">{prompt.name}</h3>
|
||||
{#if prompt.isDefault}
|
||||
<span class="rounded bg-blue-900 px-2 py-0.5 text-xs text-blue-300">
|
||||
default
|
||||
</span>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if promptsState.activePromptId === prompt.id}
|
||||
<span class="rounded bg-emerald-900 px-2 py-0.5 text-xs text-emerald-300">
|
||||
active
|
||||
</span>
|
||||
{/if}
|
||||
{#if prompt.targetCapabilities && prompt.targetCapabilities.length > 0}
|
||||
{#each prompt.targetCapabilities as cap (cap)}
|
||||
<span class="rounded bg-purple-900/50 px-2 py-0.5 text-xs text-purple-300">
|
||||
{cap}
|
||||
</span>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{#if prompt.description}
|
||||
<p class="mt-1 text-sm text-theme-muted">{prompt.description}</p>
|
||||
{/if}
|
||||
<p class="mt-2 line-clamp-2 text-sm text-theme-muted">
|
||||
{prompt.content}
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-theme-muted">
|
||||
Updated {formatDate(prompt.updatedAt)}
|
||||
</p>
|
||||
</div>
|
||||
{#if prompt.description}
|
||||
<p class="mt-1 text-sm text-theme-muted">{prompt.description}</p>
|
||||
{/if}
|
||||
<p class="mt-2 line-clamp-2 text-sm text-theme-muted">
|
||||
{prompt.content}
|
||||
</p>
|
||||
<p class="mt-2 text-xs text-theme-muted">
|
||||
Updated {formatDate(prompt.updatedAt)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Use/Active toggle -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSetActive(prompt)}
|
||||
class="rounded p-1.5 transition-colors {promptsState.activePromptId === prompt.id ? 'bg-emerald-600 text-theme-primary' : 'text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
title={promptsState.activePromptId === prompt.id ? 'Deactivate' : 'Use for new chats'}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Use/Active toggle -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSetActive(prompt)}
|
||||
class="rounded p-1.5 transition-colors {promptsState.activePromptId === prompt.id
|
||||
? 'bg-emerald-600 text-theme-primary'
|
||||
: 'text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
title={promptsState.activePromptId === prompt.id
|
||||
? 'Deactivate'
|
||||
: 'Use for new chats'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Set as default -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSetDefault(prompt)}
|
||||
class="rounded p-1.5 transition-colors {prompt.isDefault ? 'bg-blue-600 text-theme-primary' : 'text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
title={prompt.isDefault ? 'Remove as default' : 'Set as default'}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill={prompt.isDefault ? 'currentColor' : 'none'} viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Set as default -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSetDefault(prompt)}
|
||||
class="rounded p-1.5 transition-colors {prompt.isDefault
|
||||
? 'bg-blue-600 text-theme-primary'
|
||||
: 'text-theme-muted hover:bg-theme-tertiary hover:text-theme-primary'}"
|
||||
title={prompt.isDefault ? 'Remove as default' : 'Set as default'}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill={prompt.isDefault ? 'currentColor' : 'none'}
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Edit -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => openEditEditor(prompt)}
|
||||
class="rounded p-1.5 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
title="Edit"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Edit -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => openEditEditor(prompt)}
|
||||
class="rounded p-1.5 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
title="Edit"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Delete -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleDelete(prompt)}
|
||||
class="rounded p-1.5 text-theme-muted transition-colors hover:bg-red-900/30 hover:text-red-400"
|
||||
title="Delete"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Delete -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleDelete(prompt)}
|
||||
class="rounded p-1.5 text-theme-muted transition-colors hover:bg-red-900/30 hover:text-red-400"
|
||||
title="Delete"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Info section -->
|
||||
<section class="mt-8 rounded-lg border border-theme bg-theme-secondary/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-theme-secondary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 text-blue-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
How System Prompts Work
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
System prompts define the AI's behavior, personality, and constraints. They're sent at
|
||||
the beginning of each conversation to set the context. Use them to create specialized
|
||||
assistants (e.g., code reviewer, writing helper) or to enforce specific response formats.
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
<strong class="text-theme-secondary">Default prompt:</strong> Used for all new chats unless
|
||||
overridden.
|
||||
<strong class="text-theme-secondary">Active prompt:</strong> Currently selected for your session.
|
||||
<strong class="text-theme-secondary">Capability targeting:</strong> Auto-matches prompts to
|
||||
models with specific capabilities (code, vision, thinking, tools).
|
||||
</p>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Browse Templates Tab -->
|
||||
{#if activeTab === 'browse-templates'}
|
||||
<!-- Category filter -->
|
||||
<div class="mb-6 flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (selectedCategory = 'all')}
|
||||
class="rounded-lg px-3 py-1.5 text-sm font-medium transition-colors {selectedCategory ===
|
||||
'all'
|
||||
? 'bg-theme-secondary text-theme-primary'
|
||||
: 'bg-theme-tertiary text-theme-muted hover:text-theme-secondary'}"
|
||||
>
|
||||
All
|
||||
</button>
|
||||
{#each categories as category (category)}
|
||||
{@const info = categoryInfo[category]}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (selectedCategory = category)}
|
||||
class="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium transition-colors {selectedCategory ===
|
||||
category
|
||||
? info.color
|
||||
: 'bg-theme-tertiary text-theme-muted hover:text-theme-secondary'}"
|
||||
>
|
||||
<span>{info.icon}</span>
|
||||
{info.label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Templates grid -->
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
{#each filteredTemplates as template (template.id)}
|
||||
{@const info = categoryInfo[template.category]}
|
||||
<div class="rounded-lg border border-theme bg-theme-secondary p-4">
|
||||
<div class="mb-3 flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="font-medium text-theme-primary">{template.name}</h3>
|
||||
<span class="mt-1 inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs {info.color}">
|
||||
<span>{info.icon}</span>
|
||||
{info.label}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => addTemplateToLibrary(template)}
|
||||
disabled={addingTemplateId === template.id}
|
||||
class="flex items-center gap-1.5 rounded-lg bg-blue-600 px-3 py-1.5 text-sm font-medium text-theme-primary transition-colors hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{#if addingTemplateId === template.id}
|
||||
<svg class="h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<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"
|
||||
></path>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
{/if}
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-theme-muted">{template.description}</p>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (previewTemplate = template)}
|
||||
class="mt-3 text-sm text-blue-400 hover:text-blue-300"
|
||||
>
|
||||
Preview prompt
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Info section -->
|
||||
<section class="mt-8 rounded-lg border border-theme bg-theme-secondary/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-theme-secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
How System Prompts Work
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
System prompts define the AI's behavior, personality, and constraints. They're sent at the
|
||||
beginning of each conversation to set the context. Use them to create specialized assistants
|
||||
(e.g., code reviewer, writing helper) or to enforce specific response formats.
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
<strong class="text-theme-secondary">Default prompt:</strong> Used for all new chats unless overridden.
|
||||
<strong class="text-theme-secondary">Active prompt:</strong> Currently selected for your session.
|
||||
<strong class="text-theme-secondary">Capability targeting:</strong> Auto-matches prompts to models with specific capabilities (code, vision, thinking, tools).
|
||||
</p>
|
||||
</section>
|
||||
<!-- Info about templates -->
|
||||
<section class="mt-8 rounded-lg border border-theme bg-theme-secondary/50 p-4">
|
||||
<h3 class="flex items-center gap-2 text-sm font-medium text-theme-secondary">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 text-purple-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8 14v3m4-3v3m4-3v3M3 21h18M3 10h18M3 7l9-4 9 4M4 10h16v11H4V10z"
|
||||
/>
|
||||
</svg>
|
||||
About Templates
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-theme-muted">
|
||||
These curated templates are designed for common use cases. When you add a template, it
|
||||
creates a copy in your library that you can customize. Templates with capability tags
|
||||
will auto-match with compatible models.
|
||||
</p>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -300,8 +625,12 @@
|
||||
{#if showEditor}
|
||||
<div
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||
onclick={(e) => { if (e.target === e.currentTarget) closeEditor(); }}
|
||||
onkeydown={(e) => { if (e.key === 'Escape') closeEditor(); }}
|
||||
onclick={(e) => {
|
||||
if (e.target === e.currentTarget) closeEditor();
|
||||
}}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Escape') closeEditor();
|
||||
}}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="editor-title"
|
||||
@@ -316,13 +645,26 @@
|
||||
onclick={closeEditor}
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSave(); }} class="p-6">
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
}}
|
||||
class="p-6"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<!-- Name -->
|
||||
<div>
|
||||
@@ -341,7 +683,10 @@
|
||||
|
||||
<!-- Description -->
|
||||
<div>
|
||||
<label for="prompt-description" class="mb-1 block text-sm font-medium text-theme-secondary">
|
||||
<label
|
||||
for="prompt-description"
|
||||
class="mb-1 block text-sm font-medium text-theme-secondary"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<input
|
||||
@@ -390,14 +735,19 @@
|
||||
Auto-use for model types
|
||||
</label>
|
||||
<p class="mb-3 text-xs text-theme-muted">
|
||||
When a model has these capabilities and no other prompt is selected, this prompt will be used automatically.
|
||||
When a model has these capabilities and no other prompt is selected, this prompt will
|
||||
be used automatically.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each CAPABILITIES as cap (cap.id)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => toggleCapability(cap.id)}
|
||||
class="rounded-lg border px-3 py-1.5 text-sm transition-colors {formTargetCapabilities.includes(cap.id) ? 'border-blue-500 bg-blue-500/20 text-blue-300' : 'border-theme-subtle bg-theme-tertiary text-theme-muted hover:border-theme hover:text-theme-secondary'}"
|
||||
class="rounded-lg border px-3 py-1.5 text-sm transition-colors {formTargetCapabilities.includes(
|
||||
cap.id
|
||||
)
|
||||
? 'border-blue-500 bg-blue-500/20 text-blue-300'
|
||||
: 'border-theme-subtle bg-theme-tertiary text-theme-muted hover:border-theme hover:text-theme-secondary'}"
|
||||
title={cap.description}
|
||||
>
|
||||
{cap.label}
|
||||
@@ -428,3 +778,94 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Template Preview Modal -->
|
||||
{#if previewTemplate}
|
||||
{@const info = categoryInfo[previewTemplate.category]}
|
||||
<div
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||
onclick={(e) => {
|
||||
if (e.target === e.currentTarget) previewTemplate = null;
|
||||
}}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Escape') previewTemplate = null;
|
||||
}}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div class="w-full max-w-2xl max-h-[80vh] flex flex-col rounded-xl bg-theme-secondary shadow-xl">
|
||||
<div class="flex items-center justify-between border-b border-theme px-6 py-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-theme-primary">{previewTemplate.name}</h2>
|
||||
<div class="mt-1 flex items-center gap-2">
|
||||
<span class="inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs {info.color}">
|
||||
<span>{info.icon}</span>
|
||||
{info.label}
|
||||
</span>
|
||||
{#if previewTemplate.targetCapabilities}
|
||||
{#each previewTemplate.targetCapabilities as cap}
|
||||
<span class="rounded bg-purple-900/50 px-2 py-0.5 text-xs text-purple-300">
|
||||
{cap}
|
||||
</span>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (previewTemplate = null)}
|
||||
class="rounded p-1 text-theme-muted transition-colors hover:bg-theme-tertiary hover:text-theme-primary"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<p class="mb-4 text-sm text-theme-muted">{previewTemplate.description}</p>
|
||||
<pre
|
||||
class="whitespace-pre-wrap rounded-lg bg-theme-tertiary p-4 font-mono text-sm text-theme-primary">{previewTemplate.content}</pre>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 border-t border-theme px-6 py-4">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (previewTemplate = null)}
|
||||
class="rounded-lg px-4 py-2 text-sm font-medium text-theme-secondary transition-colors hover:bg-theme-tertiary"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => {
|
||||
if (previewTemplate) {
|
||||
addTemplateToLibrary(previewTemplate);
|
||||
previewTemplate = null;
|
||||
}
|
||||
}}
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-theme-primary transition-colors hover:bg-blue-700"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Add to Library
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user