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",
|
"_comment_description": "Short description for the addon list",
|
||||||
"description": "A demo addon showcasing all GitCaddy addon features with detailed examples.",
|
"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)",
|
"_comment_author": "Author information (shown in addon details)",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Your Name",
|
"name": "Your Name",
|
||||||
@@ -54,7 +60,8 @@
|
|||||||
"network:external",
|
"network:external",
|
||||||
"settings:store",
|
"settings:store",
|
||||||
"notifications:show",
|
"notifications:show",
|
||||||
"ui:dialogs"
|
"ui:dialogs",
|
||||||
|
"ai:conversation"
|
||||||
],
|
],
|
||||||
|
|
||||||
"_comment_capabilities": "What your addon can do - GitCaddy uses these to find addons",
|
"_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",
|
"_comment_contextMenuItems": "Items added to right-click context menus",
|
||||||
"contextMenuItems": [
|
"contextMenuItems": [
|
||||||
{
|
{
|
||||||
|
|||||||
105
main/index.js
105
main/index.js
@@ -218,6 +218,14 @@ class MyFirstAddon {
|
|||||||
case 'generateCommitMessage':
|
case 'generateCommitMessage':
|
||||||
return this.generateCommitMessage(args[0]);
|
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:
|
default:
|
||||||
throw new Error(`Unknown method: ${method}`);
|
throw new Error(`Unknown method: ${method}`);
|
||||||
}
|
}
|
||||||
@@ -437,6 +445,8 @@ class MyFirstAddon {
|
|||||||
'IPC communication',
|
'IPC communication',
|
||||||
'Event handling',
|
'Event handling',
|
||||||
'Native file dialogs',
|
'Native file dialogs',
|
||||||
|
'AI chat (simple)',
|
||||||
|
'AI structured output (tools)',
|
||||||
],
|
],
|
||||||
hasDialogs: !!this.context?.dialogs,
|
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
|
// EVENT HANDLERS
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
166
views/demo.html
166
views/demo.html
@@ -32,6 +32,18 @@
|
|||||||
flex-direction: column;
|
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
|
DARK MODE SCROLLBAR STYLING
|
||||||
Use theme colors for scrollbars to match dark mode
|
Use theme colors for scrollbars to match dark mode
|
||||||
@@ -264,12 +276,11 @@
|
|||||||
display: none;
|
display: none;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-content.active {
|
.tab-content.active {
|
||||||
display: flex;
|
display: block;
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make the logs section fill available space */
|
/* Make the logs section fill available space */
|
||||||
@@ -311,6 +322,7 @@
|
|||||||
<button class="tab" data-tab="actions">Actions</button>
|
<button class="tab" data-tab="actions">Actions</button>
|
||||||
<button class="tab" data-tab="ipc">IPC Demo</button>
|
<button class="tab" data-tab="ipc">IPC Demo</button>
|
||||||
<button class="tab" data-tab="host">Host 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>
|
<button class="tab" data-tab="logs">Logs</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -467,6 +479,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Logs Tab -->
|
||||||
<div id="logs" class="tab-content">
|
<div id="logs" class="tab-content">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
@@ -540,6 +608,15 @@
|
|||||||
// Handle messages from GitCaddy
|
// 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) => {
|
window.addEventListener('message', (event) => {
|
||||||
const { type, data, actionId, requestId, result, error } = event.data || {};
|
const { type, data, actionId, requestId, result, error } = event.data || {};
|
||||||
|
|
||||||
@@ -548,10 +625,16 @@
|
|||||||
// Received initial context from GitCaddy
|
// Received initial context from GitCaddy
|
||||||
repositoryPath = data?.repositoryPath;
|
repositoryPath = data?.repositoryPath;
|
||||||
hostPort = data?.hostPort;
|
hostPort = data?.hostPort;
|
||||||
|
applyTheme(data?.isDarkTheme);
|
||||||
log('info', 'Received context from GitCaddy');
|
log('info', 'Received context from GitCaddy');
|
||||||
initializeView();
|
initializeView();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'addon:context-update':
|
||||||
|
// Context updated (e.g., theme changed)
|
||||||
|
applyTheme(data?.isDarkTheme);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'addon:header-action':
|
case 'addon:header-action':
|
||||||
// Header button was clicked
|
// Header button was clicked
|
||||||
log('info', `Header action clicked: ${actionId}`);
|
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
|
// LOGGING
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user