Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 566273415f | |||
| 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.15"
|
||||
|
||||
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.15",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
let { toolCalls }: Props = $props();
|
||||
|
||||
// Tool metadata for icons and colors
|
||||
const toolMeta: Record<string, { icon: string; color: string; label: string }> = {
|
||||
// Tool metadata for built-in tools (exact matches)
|
||||
const builtinToolMeta: Record<string, { icon: string; color: string; label: string }> = {
|
||||
get_location: {
|
||||
icon: '📍',
|
||||
color: 'from-rose-500 to-pink-600',
|
||||
@@ -41,12 +41,103 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Pattern-based styling for custom tools (checked in order, first match wins)
|
||||
const toolPatterns: Array<{ patterns: string[]; icon: string; color: string; label: string }> = [
|
||||
// Agentic Tools (check first for specific naming)
|
||||
{ patterns: ['task_manager', 'task-manager', 'taskmanager'], icon: '📋', color: 'from-indigo-500 to-purple-600', label: 'Tasks' },
|
||||
{ patterns: ['memory_store', 'memory-store', 'memorystore', 'scratchpad'], icon: '🧠', color: 'from-violet-500 to-purple-600', label: 'Memory' },
|
||||
{ patterns: ['think_step', 'structured_thinking', 'reasoning'], icon: '💭', color: 'from-cyan-500 to-blue-600', label: 'Thinking' },
|
||||
{ patterns: ['decision_matrix', 'decision-matrix', 'evaluate'], icon: '⚖️', color: 'from-amber-500 to-orange-600', label: 'Decision' },
|
||||
{ patterns: ['project_planner', 'project-planner', 'breakdown'], icon: '📊', color: 'from-emerald-500 to-teal-600', label: 'Planning' },
|
||||
// Design & UI
|
||||
{ patterns: ['design', 'brief', 'ui', 'ux', 'layout', 'wireframe'], icon: '🎨', color: 'from-pink-500 to-rose-600', label: 'Design' },
|
||||
{ patterns: ['color', 'palette', 'theme', 'style'], icon: '🎨', color: 'from-fuchsia-500 to-pink-600', label: 'Color' },
|
||||
// Search & Discovery
|
||||
{ patterns: ['search', 'find', 'lookup', 'query'], icon: '🔍', color: 'from-blue-500 to-cyan-600', label: 'Search' },
|
||||
// Web & API
|
||||
{ patterns: ['fetch', 'http', 'api', 'request', 'webhook'], icon: '🌐', color: 'from-violet-500 to-purple-600', label: 'API' },
|
||||
{ patterns: ['url', 'link', 'web', 'scrape'], icon: '🔗', color: 'from-indigo-500 to-violet-600', label: 'Web' },
|
||||
// Data & Analysis
|
||||
{ patterns: ['data', 'analyze', 'stats', 'chart', 'graph', 'metric'], icon: '📊', color: 'from-cyan-500 to-blue-600', label: 'Analysis' },
|
||||
{ patterns: ['json', 'transform', 'parse', 'convert', 'format'], icon: '🔄', color: 'from-sky-500 to-cyan-600', label: 'Transform' },
|
||||
// Math & Calculation
|
||||
{ patterns: ['calc', 'math', 'compute', 'formula', 'number'], icon: '🧮', color: 'from-emerald-500 to-teal-600', label: 'Calculate' },
|
||||
// Time & Date
|
||||
{ patterns: ['time', 'date', 'clock', 'schedule', 'calendar'], icon: '🕐', color: 'from-amber-500 to-orange-600', label: 'Time' },
|
||||
// Location & Maps
|
||||
{ patterns: ['location', 'geo', 'place', 'address', 'map', 'coord'], icon: '📍', color: 'from-rose-500 to-pink-600', label: 'Location' },
|
||||
// Text & String
|
||||
{ patterns: ['text', 'string', 'word', 'sentence', 'paragraph'], icon: '📝', color: 'from-slate-500 to-gray-600', label: 'Text' },
|
||||
// Files & Storage
|
||||
{ patterns: ['file', 'read', 'write', 'save', 'load', 'export', 'import'], icon: '📁', color: 'from-yellow-500 to-amber-600', label: 'File' },
|
||||
// Communication
|
||||
{ patterns: ['email', 'mail', 'send', 'message', 'notify', 'alert'], icon: '📧', color: 'from-red-500 to-rose-600', label: 'Message' },
|
||||
// User & Auth
|
||||
{ patterns: ['user', 'auth', 'login', 'account', 'profile', 'session'], icon: '👤', color: 'from-blue-500 to-indigo-600', label: 'User' },
|
||||
// Database
|
||||
{ patterns: ['database', 'db', 'sql', 'table', 'record', 'store'], icon: '🗄️', color: 'from-orange-500 to-red-600', label: 'Database' },
|
||||
// Code & Execution
|
||||
{ patterns: ['code', 'script', 'execute', 'run', 'shell', 'command'], icon: '💻', color: 'from-green-500 to-emerald-600', label: 'Code' },
|
||||
// Images & Media
|
||||
{ patterns: ['image', 'photo', 'picture', 'screenshot', 'media', 'video'], icon: '🖼️', color: 'from-purple-500 to-fuchsia-600', label: 'Media' },
|
||||
// Weather
|
||||
{ patterns: ['weather', 'forecast', 'temperature', 'climate'], icon: '🌤️', color: 'from-sky-400 to-blue-500', label: 'Weather' },
|
||||
// Translation & Language
|
||||
{ patterns: ['translate', 'language', 'i18n', 'locale'], icon: '🌍', color: 'from-teal-500 to-cyan-600', label: 'Translate' },
|
||||
// Security & Encryption
|
||||
{ patterns: ['encrypt', 'decrypt', 'hash', 'encode', 'decode', 'secure', 'password'], icon: '🔐', color: 'from-red-600 to-orange-600', label: 'Security' },
|
||||
// Random & Generation
|
||||
{ patterns: ['random', 'generate', 'uuid', 'create', 'make'], icon: '🎲', color: 'from-violet-500 to-purple-600', label: 'Generate' },
|
||||
// Lists & Collections
|
||||
{ patterns: ['list', 'array', 'collection', 'filter', 'sort'], icon: '📋', color: 'from-blue-400 to-indigo-500', label: 'List' },
|
||||
// Validation & Check
|
||||
{ patterns: ['valid', 'check', 'verify', 'test', 'assert'], icon: '✅', color: 'from-green-500 to-teal-600', label: 'Validate' }
|
||||
];
|
||||
|
||||
const defaultMeta = {
|
||||
icon: '⚙️',
|
||||
color: 'from-gray-500 to-gray-600',
|
||||
color: 'from-slate-500 to-slate-600',
|
||||
label: 'Tool'
|
||||
};
|
||||
|
||||
/**
|
||||
* Get tool metadata - checks builtin tools first, then pattern matches, then default
|
||||
*/
|
||||
function getToolMeta(toolName: string): { icon: string; color: string; label: string } {
|
||||
// Check builtin tools first (exact match)
|
||||
if (builtinToolMeta[toolName]) {
|
||||
return builtinToolMeta[toolName];
|
||||
}
|
||||
|
||||
// Pattern match for custom tools
|
||||
const lowerName = toolName.toLowerCase();
|
||||
for (const pattern of toolPatterns) {
|
||||
if (pattern.patterns.some((p) => lowerName.includes(p))) {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
||||
// Default fallback
|
||||
return defaultMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert tool name to human-readable label
|
||||
*/
|
||||
function formatToolLabel(toolName: string, detectedLabel: string): string {
|
||||
// If it's a known builtin or detected pattern, use that label
|
||||
if (detectedLabel !== 'Tool') {
|
||||
return detectedLabel;
|
||||
}
|
||||
// Otherwise, humanize the tool name
|
||||
return toolName
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
||||
.split(' ')
|
||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse arguments to display-friendly format
|
||||
*/
|
||||
@@ -200,7 +291,8 @@
|
||||
|
||||
<div class="my-3 space-y-2">
|
||||
{#each toolCalls as call (call.id)}
|
||||
{@const meta = toolMeta[call.name] || defaultMeta}
|
||||
{@const meta = getToolMeta(call.name)}
|
||||
{@const displayLabel = formatToolLabel(call.name, meta.label)}
|
||||
{@const args = parseArgs(call.arguments)}
|
||||
{@const argEntries = Object.entries(args).filter(([_, v]) => v !== undefined && v !== null)}
|
||||
{@const isExpanded = expandedCalls.has(call.id)}
|
||||
@@ -216,12 +308,12 @@
|
||||
class="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-slate-100/50 dark:hover:bg-slate-700/50"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<span class="text-xl" role="img" aria-label={meta.label}>{meta.icon}</span>
|
||||
<span class="text-xl" role="img" aria-label={displayLabel}>{meta.icon}</span>
|
||||
|
||||
<!-- Tool name and summary -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-slate-800 dark:text-slate-100">{meta.label}</span>
|
||||
<span class="font-medium text-slate-800 dark:text-slate-100">{displayLabel}</span>
|
||||
<span class="font-mono text-xs text-slate-500 dark:text-slate-400">{call.name}</span>
|
||||
</div>
|
||||
|
||||
|
||||
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' }
|
||||
};
|
||||
@@ -8,7 +8,7 @@ export interface ToolTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
category: 'api' | 'data' | 'utility' | 'integration';
|
||||
category: 'api' | 'data' | 'utility' | 'integration' | 'agentic';
|
||||
language: ToolImplementation;
|
||||
code: string;
|
||||
parameters: JSONSchema;
|
||||
@@ -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',
|
||||
@@ -336,6 +514,531 @@ print(json.dumps(result))`,
|
||||
},
|
||||
required: ['text', 'operation']
|
||||
}
|
||||
},
|
||||
|
||||
// Agentic Templates
|
||||
{
|
||||
id: 'js-task-manager',
|
||||
name: 'Task Manager',
|
||||
description: 'Create, update, list, and complete tasks with persistent storage',
|
||||
category: 'agentic',
|
||||
language: 'javascript',
|
||||
code: `// Task Manager with localStorage persistence
|
||||
const STORAGE_KEY = 'vessel_agent_tasks';
|
||||
|
||||
const loadTasks = () => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
|
||||
} catch { return []; }
|
||||
};
|
||||
|
||||
const saveTasks = (tasks) => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
|
||||
};
|
||||
|
||||
const action = args.action;
|
||||
let tasks = loadTasks();
|
||||
|
||||
switch (action) {
|
||||
case 'create': {
|
||||
const task = {
|
||||
id: Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
||||
title: args.title,
|
||||
description: args.description || '',
|
||||
priority: args.priority || 'medium',
|
||||
status: 'pending',
|
||||
created: new Date().toISOString(),
|
||||
due: args.due || null,
|
||||
tags: args.tags || []
|
||||
};
|
||||
tasks.push(task);
|
||||
saveTasks(tasks);
|
||||
return { success: true, task, message: 'Task created' };
|
||||
}
|
||||
|
||||
case 'list': {
|
||||
let filtered = tasks;
|
||||
if (args.status) filtered = filtered.filter(t => t.status === args.status);
|
||||
if (args.priority) filtered = filtered.filter(t => t.priority === args.priority);
|
||||
if (args.tag) filtered = filtered.filter(t => t.tags?.includes(args.tag));
|
||||
return {
|
||||
tasks: filtered,
|
||||
total: tasks.length,
|
||||
pending: tasks.filter(t => t.status === 'pending').length,
|
||||
completed: tasks.filter(t => t.status === 'completed').length
|
||||
};
|
||||
}
|
||||
|
||||
case 'update': {
|
||||
const idx = tasks.findIndex(t => t.id === args.id);
|
||||
if (idx === -1) return { error: 'Task not found' };
|
||||
if (args.title) tasks[idx].title = args.title;
|
||||
if (args.description !== undefined) tasks[idx].description = args.description;
|
||||
if (args.priority) tasks[idx].priority = args.priority;
|
||||
if (args.status) tasks[idx].status = args.status;
|
||||
if (args.due !== undefined) tasks[idx].due = args.due;
|
||||
if (args.tags) tasks[idx].tags = args.tags;
|
||||
tasks[idx].updated = new Date().toISOString();
|
||||
saveTasks(tasks);
|
||||
return { success: true, task: tasks[idx], message: 'Task updated' };
|
||||
}
|
||||
|
||||
case 'complete': {
|
||||
const idx = tasks.findIndex(t => t.id === args.id);
|
||||
if (idx === -1) return { error: 'Task not found' };
|
||||
tasks[idx].status = 'completed';
|
||||
tasks[idx].completedAt = new Date().toISOString();
|
||||
saveTasks(tasks);
|
||||
return { success: true, task: tasks[idx], message: 'Task completed' };
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
const idx = tasks.findIndex(t => t.id === args.id);
|
||||
if (idx === -1) return { error: 'Task not found' };
|
||||
const deleted = tasks.splice(idx, 1)[0];
|
||||
saveTasks(tasks);
|
||||
return { success: true, deleted, message: 'Task deleted' };
|
||||
}
|
||||
|
||||
case 'clear_completed': {
|
||||
const before = tasks.length;
|
||||
tasks = tasks.filter(t => t.status !== 'completed');
|
||||
saveTasks(tasks);
|
||||
return { success: true, removed: before - tasks.length, remaining: tasks.length };
|
||||
}
|
||||
|
||||
default:
|
||||
return { error: 'Unknown action. Use: create, list, update, complete, delete, clear_completed' };
|
||||
}`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
description: 'Action: create, list, update, complete, delete, clear_completed'
|
||||
},
|
||||
id: { type: 'string', description: 'Task ID (for update/complete/delete)' },
|
||||
title: { type: 'string', description: 'Task title (for create/update)' },
|
||||
description: { type: 'string', description: 'Task description' },
|
||||
priority: { type: 'string', description: 'Priority: low, medium, high, urgent' },
|
||||
status: { type: 'string', description: 'Filter/set status: pending, in_progress, completed' },
|
||||
due: { type: 'string', description: 'Due date (ISO format)' },
|
||||
tags: { type: 'array', description: 'Tags for categorization' },
|
||||
tag: { type: 'string', description: 'Filter by tag (for list)' }
|
||||
},
|
||||
required: ['action']
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'js-memory-store',
|
||||
name: 'Memory Store',
|
||||
description: 'Store and recall information across conversation turns',
|
||||
category: 'agentic',
|
||||
language: 'javascript',
|
||||
code: `// Memory Store - persistent key-value storage for agent context
|
||||
const STORAGE_KEY = 'vessel_agent_memory';
|
||||
|
||||
const loadMemory = () => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
|
||||
} catch { return {}; }
|
||||
};
|
||||
|
||||
const saveMemory = (mem) => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(mem));
|
||||
};
|
||||
|
||||
const action = args.action;
|
||||
let memory = loadMemory();
|
||||
|
||||
switch (action) {
|
||||
case 'store': {
|
||||
const key = args.key;
|
||||
const value = args.value;
|
||||
const category = args.category || 'general';
|
||||
|
||||
if (!memory[category]) memory[category] = {};
|
||||
memory[category][key] = {
|
||||
value,
|
||||
stored: new Date().toISOString(),
|
||||
accessCount: 0
|
||||
};
|
||||
saveMemory(memory);
|
||||
return { success: true, key, category, message: 'Memory stored' };
|
||||
}
|
||||
|
||||
case 'recall': {
|
||||
const key = args.key;
|
||||
const category = args.category;
|
||||
|
||||
if (category && key) {
|
||||
const item = memory[category]?.[key];
|
||||
if (!item) return { found: false, key, category };
|
||||
item.accessCount++;
|
||||
item.lastAccess = new Date().toISOString();
|
||||
saveMemory(memory);
|
||||
return { found: true, key, category, value: item.value, stored: item.stored };
|
||||
}
|
||||
|
||||
if (category) {
|
||||
return { category, items: memory[category] || {} };
|
||||
}
|
||||
|
||||
if (key) {
|
||||
// Search across all categories
|
||||
for (const cat in memory) {
|
||||
if (memory[cat][key]) {
|
||||
memory[cat][key].accessCount++;
|
||||
saveMemory(memory);
|
||||
return { found: true, key, category: cat, value: memory[cat][key].value };
|
||||
}
|
||||
}
|
||||
return { found: false, key };
|
||||
}
|
||||
|
||||
return { error: 'Provide key and/or category' };
|
||||
}
|
||||
|
||||
case 'list': {
|
||||
const category = args.category;
|
||||
if (category) {
|
||||
return {
|
||||
category,
|
||||
keys: Object.keys(memory[category] || {}),
|
||||
count: Object.keys(memory[category] || {}).length
|
||||
};
|
||||
}
|
||||
const summary = {};
|
||||
for (const cat in memory) {
|
||||
summary[cat] = Object.keys(memory[cat]).length;
|
||||
}
|
||||
return { categories: summary, totalCategories: Object.keys(memory).length };
|
||||
}
|
||||
|
||||
case 'forget': {
|
||||
const key = args.key;
|
||||
const category = args.category;
|
||||
|
||||
if (category && key) {
|
||||
if (memory[category]?.[key]) {
|
||||
delete memory[category][key];
|
||||
if (Object.keys(memory[category]).length === 0) delete memory[category];
|
||||
saveMemory(memory);
|
||||
return { success: true, forgotten: key, category };
|
||||
}
|
||||
return { error: 'Memory not found' };
|
||||
}
|
||||
|
||||
if (category) {
|
||||
delete memory[category];
|
||||
saveMemory(memory);
|
||||
return { success: true, forgotten: category, type: 'category' };
|
||||
}
|
||||
|
||||
return { error: 'Provide key and/or category to forget' };
|
||||
}
|
||||
|
||||
case 'clear': {
|
||||
const before = Object.keys(memory).length;
|
||||
memory = {};
|
||||
saveMemory(memory);
|
||||
return { success: true, cleared: before, message: 'All memory cleared' };
|
||||
}
|
||||
|
||||
default:
|
||||
return { error: 'Unknown action. Use: store, recall, list, forget, clear' };
|
||||
}`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
description: 'Action: store, recall, list, forget, clear'
|
||||
},
|
||||
key: { type: 'string', description: 'Memory key/identifier' },
|
||||
value: { type: 'string', description: 'Value to store (for store action)' },
|
||||
category: { type: 'string', description: 'Category for organizing memories (facts, preferences, context, etc.)' }
|
||||
},
|
||||
required: ['action']
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'js-think-step-by-step',
|
||||
name: 'Structured Thinking',
|
||||
description: 'Break down problems into explicit reasoning steps',
|
||||
category: 'agentic',
|
||||
language: 'javascript',
|
||||
code: `// Structured Thinking - explicit step-by-step reasoning
|
||||
const problem = args.problem;
|
||||
const steps = args.steps || [];
|
||||
const conclusion = args.conclusion;
|
||||
const confidence = args.confidence || 'medium';
|
||||
|
||||
const analysis = {
|
||||
problem: problem,
|
||||
reasoning: {
|
||||
steps: steps.map((step, i) => ({
|
||||
step: i + 1,
|
||||
thought: step,
|
||||
type: step.toLowerCase().includes('assume') ? 'assumption' :
|
||||
step.toLowerCase().includes('if') ? 'conditional' :
|
||||
step.toLowerCase().includes('because') ? 'justification' :
|
||||
step.toLowerCase().includes('therefore') ? 'inference' :
|
||||
'observation'
|
||||
})),
|
||||
stepCount: steps.length
|
||||
},
|
||||
conclusion: conclusion,
|
||||
confidence: confidence,
|
||||
confidenceScore: confidence === 'high' ? 0.9 :
|
||||
confidence === 'medium' ? 0.7 :
|
||||
confidence === 'low' ? 0.4 : 0.5,
|
||||
metadata: {
|
||||
hasAssumptions: steps.some(s => s.toLowerCase().includes('assume')),
|
||||
hasConditionals: steps.some(s => s.toLowerCase().includes('if')),
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
|
||||
// Add quality indicators
|
||||
analysis.quality = {
|
||||
hasMultipleSteps: steps.length >= 3,
|
||||
hasConclusion: !!conclusion,
|
||||
isWellStructured: steps.length >= 2 && !!conclusion,
|
||||
suggestions: []
|
||||
};
|
||||
|
||||
if (steps.length < 2) {
|
||||
analysis.quality.suggestions.push('Consider breaking down into more steps');
|
||||
}
|
||||
if (!conclusion) {
|
||||
analysis.quality.suggestions.push('Add a clear conclusion');
|
||||
}
|
||||
if (confidence === 'low') {
|
||||
analysis.quality.suggestions.push('Identify what additional information would increase confidence');
|
||||
}
|
||||
|
||||
return analysis;`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
problem: {
|
||||
type: 'string',
|
||||
description: 'The problem or question to reason about'
|
||||
},
|
||||
steps: {
|
||||
type: 'array',
|
||||
description: 'Array of reasoning steps, each a string explaining one step of thought'
|
||||
},
|
||||
conclusion: {
|
||||
type: 'string',
|
||||
description: 'The final conclusion reached'
|
||||
},
|
||||
confidence: {
|
||||
type: 'string',
|
||||
description: 'Confidence level: low, medium, high'
|
||||
}
|
||||
},
|
||||
required: ['problem', 'steps']
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'js-decision-matrix',
|
||||
name: 'Decision Matrix',
|
||||
description: 'Evaluate options against weighted criteria for better decisions',
|
||||
category: 'agentic',
|
||||
language: 'javascript',
|
||||
code: `// Decision Matrix - weighted multi-criteria decision analysis
|
||||
const options = args.options || [];
|
||||
const criteria = args.criteria || [];
|
||||
const scores = args.scores || {};
|
||||
|
||||
if (options.length === 0) {
|
||||
return { error: 'Provide at least one option' };
|
||||
}
|
||||
if (criteria.length === 0) {
|
||||
return { error: 'Provide at least one criterion with name and weight' };
|
||||
}
|
||||
|
||||
// Normalize weights
|
||||
const totalWeight = criteria.reduce((sum, c) => sum + (c.weight || 1), 0);
|
||||
const normalizedCriteria = criteria.map(c => ({
|
||||
name: c.name,
|
||||
weight: (c.weight || 1) / totalWeight,
|
||||
originalWeight: c.weight || 1
|
||||
}));
|
||||
|
||||
// Calculate weighted scores for each option
|
||||
const results = options.map(option => {
|
||||
let totalScore = 0;
|
||||
const breakdown = [];
|
||||
|
||||
for (const criterion of normalizedCriteria) {
|
||||
const score = scores[option]?.[criterion.name] ?? 5; // Default to 5/10
|
||||
const weighted = score * criterion.weight;
|
||||
totalScore += weighted;
|
||||
breakdown.push({
|
||||
criterion: criterion.name,
|
||||
rawScore: score,
|
||||
weight: Math.round(criterion.weight * 100) + '%',
|
||||
weightedScore: Math.round(weighted * 100) / 100
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
option,
|
||||
totalScore: Math.round(totalScore * 100) / 100,
|
||||
maxPossible: 10,
|
||||
percentage: Math.round(totalScore * 10) + '%',
|
||||
breakdown
|
||||
};
|
||||
});
|
||||
|
||||
// Sort by score
|
||||
results.sort((a, b) => b.totalScore - a.totalScore);
|
||||
|
||||
// Identify winner and insights
|
||||
const winner = results[0];
|
||||
const runnerUp = results[1];
|
||||
const margin = runnerUp ? Math.round((winner.totalScore - runnerUp.totalScore) * 100) / 100 : null;
|
||||
|
||||
return {
|
||||
recommendation: winner.option,
|
||||
confidence: margin > 1.5 ? 'high' : margin > 0.5 ? 'medium' : 'low',
|
||||
margin: margin,
|
||||
rankings: results,
|
||||
criteria: normalizedCriteria.map(c => ({
|
||||
name: c.name,
|
||||
weight: Math.round(c.weight * 100) + '%'
|
||||
})),
|
||||
insight: margin && margin < 0.5 ?
|
||||
'Options are very close - consider additional criteria or qualitative factors' :
|
||||
margin && margin > 2 ?
|
||||
\`\${winner.option} is a clear winner with significant margin\` :
|
||||
'Decision is reasonably clear but review the breakdown for nuance'
|
||||
};`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
options: {
|
||||
type: 'array',
|
||||
description: 'Array of option names to evaluate (e.g., ["Option A", "Option B"])'
|
||||
},
|
||||
criteria: {
|
||||
type: 'array',
|
||||
description: 'Array of criteria objects with name and weight (e.g., [{"name": "Cost", "weight": 3}, {"name": "Quality", "weight": 2}])'
|
||||
},
|
||||
scores: {
|
||||
type: 'object',
|
||||
description: 'Scores object: { "Option A": { "Cost": 8, "Quality": 7 }, "Option B": { "Cost": 6, "Quality": 9 } }'
|
||||
}
|
||||
},
|
||||
required: ['options', 'criteria', 'scores']
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'js-project-planner',
|
||||
name: 'Project Planner',
|
||||
description: 'Break down projects into phases, tasks, and dependencies',
|
||||
category: 'agentic',
|
||||
language: 'javascript',
|
||||
code: `// Project Planner - decompose projects into actionable plans
|
||||
const projectName = args.project_name;
|
||||
const goal = args.goal;
|
||||
const phases = args.phases || [];
|
||||
const constraints = args.constraints || [];
|
||||
|
||||
if (!projectName || !goal) {
|
||||
return { error: 'Provide project_name and goal' };
|
||||
}
|
||||
|
||||
const plan = {
|
||||
project: projectName,
|
||||
goal: goal,
|
||||
created: new Date().toISOString(),
|
||||
constraints: constraints,
|
||||
phases: phases.map((phase, phaseIdx) => ({
|
||||
id: \`phase-\${phaseIdx + 1}\`,
|
||||
name: phase.name,
|
||||
description: phase.description || '',
|
||||
order: phaseIdx + 1,
|
||||
tasks: (phase.tasks || []).map((task, taskIdx) => ({
|
||||
id: \`\${phaseIdx + 1}.\${taskIdx + 1}\`,
|
||||
title: task.title || task,
|
||||
description: task.description || '',
|
||||
dependencies: task.dependencies || [],
|
||||
status: 'pending',
|
||||
priority: task.priority || 'medium'
|
||||
})),
|
||||
deliverables: phase.deliverables || []
|
||||
})),
|
||||
summary: {
|
||||
totalPhases: phases.length,
|
||||
totalTasks: phases.reduce((sum, p) => sum + (p.tasks?.length || 0), 0),
|
||||
hasConstraints: constraints.length > 0
|
||||
}
|
||||
};
|
||||
|
||||
// Identify critical path (tasks with most dependents)
|
||||
const allTasks = plan.phases.flatMap(p => p.tasks);
|
||||
const dependencyCounts = {};
|
||||
allTasks.forEach(t => {
|
||||
t.dependencies.forEach(dep => {
|
||||
dependencyCounts[dep] = (dependencyCounts[dep] || 0) + 1;
|
||||
});
|
||||
});
|
||||
|
||||
plan.criticalTasks = Object.entries(dependencyCounts)
|
||||
.filter(([_, count]) => count > 1)
|
||||
.map(([id, count]) => ({ taskId: id, dependentCount: count }))
|
||||
.sort((a, b) => b.dependentCount - a.dependentCount);
|
||||
|
||||
// Generate next actions (tasks with no pending dependencies)
|
||||
const completedTasks = new Set();
|
||||
plan.nextActions = allTasks
|
||||
.filter(t => t.dependencies.every(d => completedTasks.has(d)))
|
||||
.slice(0, 5)
|
||||
.map(t => ({ id: t.id, title: t.title, phase: t.id.split('.')[0] }));
|
||||
|
||||
// Validation
|
||||
plan.validation = {
|
||||
isValid: phases.length > 0 && plan.summary.totalTasks > 0,
|
||||
warnings: []
|
||||
};
|
||||
|
||||
if (phases.length === 0) {
|
||||
plan.validation.warnings.push('No phases defined');
|
||||
}
|
||||
if (plan.summary.totalTasks === 0) {
|
||||
plan.validation.warnings.push('No tasks defined');
|
||||
}
|
||||
if (constraints.length === 0) {
|
||||
plan.validation.warnings.push('Consider adding constraints (time, budget, resources)');
|
||||
}
|
||||
|
||||
return plan;`,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
project_name: {
|
||||
type: 'string',
|
||||
description: 'Name of the project'
|
||||
},
|
||||
goal: {
|
||||
type: 'string',
|
||||
description: 'The main goal or outcome of the project'
|
||||
},
|
||||
phases: {
|
||||
type: 'array',
|
||||
description: 'Array of phase objects: [{ name, description, tasks: [{ title, dependencies, priority }], deliverables }]'
|
||||
},
|
||||
constraints: {
|
||||
type: 'array',
|
||||
description: 'Array of constraints (e.g., ["Budget: $10k", "Timeline: 2 weeks"])'
|
||||
}
|
||||
},
|
||||
required: ['project_name', 'goal']
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -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,461 @@
|
||||
</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>
|
||||
<p class="mt-3 text-xs text-theme-muted">
|
||||
Inspired by prompts from the
|
||||
<a
|
||||
href="https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-purple-400 hover:text-purple-300 hover:underline"
|
||||
>
|
||||
system-prompts-and-models-of-ai-tools
|
||||
</a>
|
||||
collection.
|
||||
</p>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -300,8 +637,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 +657,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 +695,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 +747,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 +790,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