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:
2026-01-24 17:51:55 -05:00
parent 2f88b6af51
commit 2f99c6fb0b
3 changed files with 310 additions and 4 deletions

View File

@@ -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": [
{

View File

@@ -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
// ============================================================

View File

@@ -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 &gt; 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
// ============================================================