feat: add AI conversation capabilities to demo addon
Implements two AI integration methods: - aiChat: simple text-based AI conversation - aiStructuredOutput: schema-based AI responses using tools Adds ai:conversation permission and AnalyzeCode tool definition to addon.json with rocket icon. Both methods communicate with AIHost service on localhost:5678.
This commit is contained in:
43
addon.json
43
addon.json
@@ -16,6 +16,12 @@
|
||||
"_comment_description": "Short description for the addon list",
|
||||
"description": "A demo addon showcasing all GitCaddy addon features with detailed examples.",
|
||||
|
||||
"_comment_icon": "Icon displayed in the Addon Manager (octicon, emoji, or url)",
|
||||
"icon": {
|
||||
"type": "octicon",
|
||||
"value": "rocket"
|
||||
},
|
||||
|
||||
"_comment_author": "Author information (shown in addon details)",
|
||||
"author": {
|
||||
"name": "Your Name",
|
||||
@@ -54,7 +60,8 @@
|
||||
"network:external",
|
||||
"settings:store",
|
||||
"notifications:show",
|
||||
"ui:dialogs"
|
||||
"ui:dialogs",
|
||||
"ai:conversation"
|
||||
],
|
||||
|
||||
"_comment_capabilities": "What your addon can do - GitCaddy uses these to find addons",
|
||||
@@ -189,6 +196,40 @@
|
||||
}
|
||||
],
|
||||
|
||||
"_comment_aiTools": "AI tools for structured output (requires ai:conversation permission)",
|
||||
"aiTools": [
|
||||
{
|
||||
"name": "AnalyzeCode",
|
||||
"description": "Analyzes code and provides a structured evaluation with sentiment, complexity, and suggestions.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "sentiment",
|
||||
"type": "string",
|
||||
"description": "Overall sentiment: positive, neutral, or negative",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "complexity",
|
||||
"type": "number",
|
||||
"description": "Complexity score from 1-10",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "summary",
|
||||
"type": "string",
|
||||
"description": "Brief summary of the code",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "suggestions",
|
||||
"type": "array",
|
||||
"description": "Array of improvement suggestions",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
"_comment_contextMenuItems": "Items added to right-click context menus",
|
||||
"contextMenuItems": [
|
||||
{
|
||||
|
||||
105
main/index.js
105
main/index.js
@@ -218,6 +218,14 @@ class MyFirstAddon {
|
||||
case 'generateCommitMessage':
|
||||
return this.generateCommitMessage(args[0]);
|
||||
|
||||
// ---- AI Methods ----
|
||||
// These demonstrate the AI conversation API
|
||||
case 'aiChat':
|
||||
return this.aiChat(args[0]);
|
||||
|
||||
case 'aiStructuredOutput':
|
||||
return this.aiStructuredOutput(args[0], args[1]);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown method: ${method}`);
|
||||
}
|
||||
@@ -437,6 +445,8 @@ class MyFirstAddon {
|
||||
'IPC communication',
|
||||
'Event handling',
|
||||
'Native file dialogs',
|
||||
'AI chat (simple)',
|
||||
'AI structured output (tools)',
|
||||
],
|
||||
hasDialogs: !!this.context?.dialogs,
|
||||
};
|
||||
@@ -626,6 +636,101 @@ class MyFirstAddon {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// AI METHODS
|
||||
// These demonstrate how to use GitCaddy's AI conversation API
|
||||
// Requires "ai:conversation" permission in addon.json
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Simple AI chat - send a message and get a text response.
|
||||
*
|
||||
* This calls the AIHost service running on localhost:5678.
|
||||
* The AIHost uses the user's configured AI provider (Claude, OpenAI, etc.)
|
||||
*
|
||||
* @param {string} prompt - The user's message
|
||||
* @returns {Promise<{success: boolean, content?: string, error?: string}>}
|
||||
*/
|
||||
async aiChat(prompt) {
|
||||
const AI_HOST_PORT = 5678;
|
||||
|
||||
try {
|
||||
this.context?.log.info('Sending AI chat request...');
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${AI_HOST_PORT}/ai/chat`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: [
|
||||
{ role: 'user', content: prompt }
|
||||
]
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
this.context?.log.info('AI chat response received');
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.context?.log.error('AI chat failed:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AI structured output - get a response matching a defined schema.
|
||||
*
|
||||
* This uses AI tools/functions to get structured JSON output.
|
||||
* The tool must be defined in addon.json under contributes.aiTools.
|
||||
*
|
||||
* @param {string} prompt - The prompt describing what to analyze
|
||||
* @param {string} toolName - Name of the tool defined in addon.json (e.g., "AnalyzeCode")
|
||||
* @returns {Promise<{success: boolean, data?: object, error?: string, fallbackText?: string}>}
|
||||
*/
|
||||
async aiStructuredOutput(prompt, toolName) {
|
||||
const AI_HOST_PORT = 5678;
|
||||
|
||||
try {
|
||||
this.context?.log.info(`Sending AI structured output request using tool: ${toolName}`);
|
||||
|
||||
// Find the tool definition from our manifest
|
||||
const tool = this.context?.manifest?.contributes?.aiTools?.find(t => t.name === toolName);
|
||||
if (!tool) {
|
||||
return { success: false, error: `Tool '${toolName}' not found in addon manifest` };
|
||||
}
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${AI_HOST_PORT}/ai/addon/structured-output`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
addonId: this.context?.addonId,
|
||||
prompt: prompt,
|
||||
tool: tool,
|
||||
systemPrompt: 'You are a helpful code analysis assistant. Analyze the provided code and respond using the tool.',
|
||||
temperature: 0.3,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
this.context?.log.info('AI structured output received:', result.success ? 'success' : 'failed');
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.context?.log.error('AI structured output failed:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// EVENT HANDLERS
|
||||
// ============================================================
|
||||
|
||||
166
views/demo.html
166
views/demo.html
@@ -32,6 +32,18 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Light theme overrides */
|
||||
body.light-theme {
|
||||
--foreground: #24292f;
|
||||
--background: #ffffff;
|
||||
--background-secondary: #f6f8fa;
|
||||
--scrollbar-thumb: #afb8c1;
|
||||
--scrollbar-thumb-hover: #8c959f;
|
||||
--border-color: #d0d7de;
|
||||
--accent-color: #0969da;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
DARK MODE SCROLLBAR STYLING
|
||||
Use theme colors for scrollbars to match dark mode
|
||||
@@ -264,12 +276,11 @@
|
||||
display: none;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Make the logs section fill available space */
|
||||
@@ -311,6 +322,7 @@
|
||||
<button class="tab" data-tab="actions">Actions</button>
|
||||
<button class="tab" data-tab="ipc">IPC Demo</button>
|
||||
<button class="tab" data-tab="host">Host Demo</button>
|
||||
<button class="tab" data-tab="ai">AI Demo</button>
|
||||
<button class="tab" data-tab="logs">Logs</button>
|
||||
</div>
|
||||
|
||||
@@ -467,6 +479,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Demo Tab -->
|
||||
<div id="ai" class="tab-content">
|
||||
<div class="section">
|
||||
<h2>AI Chat</h2>
|
||||
<p>
|
||||
Send a message to the AI and get a text response.
|
||||
This uses GitCaddy's built-in AI service (configured in Settings > AI).
|
||||
</p>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<textarea id="chat-input" placeholder="Ask the AI anything..."
|
||||
style="width: 100%; height: 80px; padding: 8px; border: 1px solid var(--border, #3c3c3c);
|
||||
border-radius: 4px; background: var(--background, #1e1e1e);
|
||||
color: var(--foreground, #cccccc); font-family: inherit; font-size: 13px;
|
||||
resize: vertical;"></textarea>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button class="btn" onclick="sendAIChat()">
|
||||
Send to AI
|
||||
</button>
|
||||
</div>
|
||||
<h3>AI Response</h3>
|
||||
<div class="code-block" id="chat-result" style="min-height: 60px; white-space: pre-wrap;">
|
||||
Enter a message above and click "Send to AI" to get a response.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>AI Structured Output</h2>
|
||||
<p>
|
||||
Get structured JSON responses using AI tools.
|
||||
This example uses the "AnalyzeCode" tool defined in addon.json.
|
||||
</p>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<textarea id="code-input" placeholder="Paste some code to analyze..."
|
||||
style="width: 100%; height: 100px; padding: 8px; border: 1px solid var(--border, #3c3c3c);
|
||||
border-radius: 4px; background: var(--background, #1e1e1e);
|
||||
color: var(--foreground, #cccccc); font-family: var(--font-family-mono, monospace);
|
||||
font-size: 12px; resize: vertical;">function hello(name) {
|
||||
if (!name) {
|
||||
throw new Error("Name is required");
|
||||
}
|
||||
return "Hello, " + name + "!";
|
||||
}</textarea>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button class="btn" onclick="analyzeCode()">
|
||||
Analyze Code
|
||||
</button>
|
||||
</div>
|
||||
<h3>Structured Analysis Result</h3>
|
||||
<div class="code-block" id="structured-result" style="min-height: 80px;">
|
||||
Paste code above and click "Analyze Code" to get a structured analysis.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs Tab -->
|
||||
<div id="logs" class="tab-content">
|
||||
<div class="section">
|
||||
@@ -540,6 +608,15 @@
|
||||
// Handle messages from GitCaddy
|
||||
// ============================================================
|
||||
|
||||
// Apply theme based on isDarkTheme flag
|
||||
function applyTheme(isDarkTheme) {
|
||||
if (isDarkTheme === false) {
|
||||
document.body.classList.add('light-theme');
|
||||
} else {
|
||||
document.body.classList.remove('light-theme');
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const { type, data, actionId, requestId, result, error } = event.data || {};
|
||||
|
||||
@@ -548,10 +625,16 @@
|
||||
// Received initial context from GitCaddy
|
||||
repositoryPath = data?.repositoryPath;
|
||||
hostPort = data?.hostPort;
|
||||
applyTheme(data?.isDarkTheme);
|
||||
log('info', 'Received context from GitCaddy');
|
||||
initializeView();
|
||||
break;
|
||||
|
||||
case 'addon:context-update':
|
||||
// Context updated (e.g., theme changed)
|
||||
applyTheme(data?.isDarkTheme);
|
||||
break;
|
||||
|
||||
case 'addon:header-action':
|
||||
// Header button was clicked
|
||||
log('info', `Header action clicked: ${actionId}`);
|
||||
@@ -816,6 +899,83 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// AI DEMO FUNCTIONS
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Send a chat message to the AI.
|
||||
* Uses the aiChat method in main/index.js which calls the AIHost service.
|
||||
*/
|
||||
async function sendAIChat() {
|
||||
const inputEl = document.getElementById('chat-input');
|
||||
const resultEl = document.getElementById('chat-result');
|
||||
const prompt = inputEl.value.trim();
|
||||
|
||||
if (!prompt) {
|
||||
log('warn', 'Please enter a message to send to the AI');
|
||||
inputEl.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
resultEl.innerHTML = '<span class="loading"></span> Sending to AI...';
|
||||
log('info', 'Sending AI chat request...');
|
||||
|
||||
try {
|
||||
const result = await invokeAddon('aiChat', prompt);
|
||||
|
||||
if (result.success) {
|
||||
resultEl.textContent = result.content || '(No response)';
|
||||
log('info', 'AI chat response received');
|
||||
} else {
|
||||
resultEl.textContent = 'Error: ' + (result.error || 'Unknown error');
|
||||
log('error', 'AI chat failed: ' + result.error);
|
||||
}
|
||||
} catch (err) {
|
||||
resultEl.textContent = 'Error: ' + err.message;
|
||||
log('error', 'AI chat exception: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze code using AI structured output.
|
||||
* Uses the aiStructuredOutput method which calls the AnalyzeCode tool.
|
||||
*/
|
||||
async function analyzeCode() {
|
||||
const inputEl = document.getElementById('code-input');
|
||||
const resultEl = document.getElementById('structured-result');
|
||||
const code = inputEl.value.trim();
|
||||
|
||||
if (!code) {
|
||||
log('warn', 'Please enter some code to analyze');
|
||||
inputEl.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
resultEl.innerHTML = '<span class="loading"></span> Analyzing code...';
|
||||
log('info', 'Sending AI structured output request...');
|
||||
|
||||
try {
|
||||
const prompt = `Analyze the following code and provide a structured evaluation:\n\n\`\`\`\n${code}\n\`\`\``;
|
||||
const result = await invokeAddon('aiStructuredOutput', prompt, 'AnalyzeCode');
|
||||
|
||||
if (result.success && result.data) {
|
||||
// Format the structured data nicely
|
||||
resultEl.textContent = JSON.stringify(result.data, null, 2);
|
||||
log('info', 'AI analysis complete: ' + result.data.sentiment);
|
||||
} else if (result.success && result.fallbackText) {
|
||||
resultEl.textContent = 'Fallback response:\n' + result.fallbackText;
|
||||
log('warn', 'AI returned fallback text instead of structured output');
|
||||
} else {
|
||||
resultEl.textContent = 'Error: ' + (result.error || 'Unknown error');
|
||||
log('error', 'AI analysis failed: ' + result.error);
|
||||
}
|
||||
} catch (err) {
|
||||
resultEl.textContent = 'Error: ' + err.message;
|
||||
log('error', 'AI analysis exception: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// LOGGING
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user