mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-04 18:31:36 -07:00
251 lines
9.2 KiB
JavaScript
251 lines
9.2 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
const serverUrlInput = document.getElementById('server-url');
|
|
const chatHistory = document.getElementById('chat-history');
|
|
const promptInput = document.getElementById('prompt-input');
|
|
const sendBtn = document.getElementById('send-btn');
|
|
const includeContextCheckbox = document.getElementById('include-page-context');
|
|
|
|
// Helper for UUID generation
|
|
function generateUUID() {
|
|
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
return crypto.randomUUID();
|
|
}
|
|
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c =>
|
|
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
|
);
|
|
}
|
|
|
|
// Load saved settings
|
|
chrome.storage.local.get(['serverUrl'], (result) => {
|
|
if (result.serverUrl) {
|
|
serverUrlInput.value = result.serverUrl;
|
|
}
|
|
});
|
|
|
|
document.getElementById('save-config').addEventListener('click', () => {
|
|
chrome.storage.local.set({ serverUrl: serverUrlInput.value });
|
|
alert('Settings saved');
|
|
});
|
|
|
|
sendBtn.addEventListener('click', async () => {
|
|
console.log('Send button clicked');
|
|
const prompt = promptInput.value.trim();
|
|
if (!prompt) return;
|
|
|
|
// Remove trailing slash if present
|
|
const serverUrl = serverUrlInput.value.replace(/\/$/, '');
|
|
if (!serverUrl) {
|
|
alert('Please set the Server URL');
|
|
return;
|
|
}
|
|
|
|
appendMessage('user', prompt);
|
|
promptInput.value = '';
|
|
sendBtn.disabled = true;
|
|
|
|
let context = '';
|
|
if (includeContextCheckbox.checked) {
|
|
try {
|
|
console.log('Fetching page context...');
|
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
if (tab && tab.id) {
|
|
const [{ result }] = await chrome.scripting.executeScript({
|
|
target: { tabId: tab.id },
|
|
func: () => document.body.innerText,
|
|
});
|
|
context = `
|
|
|
|
[Context from ${tab.url}]:
|
|
${result}
|
|
`;
|
|
console.log('Page context retrieved successfully');
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to get page context:', e);
|
|
appendMessage('agent', 'Error: Could not retrieve page context. ' + e.message);
|
|
}
|
|
}
|
|
|
|
try {
|
|
console.log(`Sending message to ${serverUrl}...`);
|
|
await sendMessage(serverUrl, prompt + context);
|
|
} catch (error) {
|
|
console.error('sendMessage failed:', error);
|
|
appendMessage('agent', `Error: ${error.message}`);
|
|
} finally {
|
|
sendBtn.disabled = false;
|
|
}
|
|
});
|
|
|
|
function appendMessage(role, text) {
|
|
const div = document.createElement('div');
|
|
div.className = `message ${role}-message`;
|
|
div.innerText = text;
|
|
chatHistory.appendChild(div);
|
|
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
}
|
|
|
|
async function sendMessage(baseUrl, text) {
|
|
let messageId;
|
|
try {
|
|
messageId = generateUUID();
|
|
} catch (e) {
|
|
console.error('UUID generation failed:', e);
|
|
messageId = 'fallback-id-' + Date.now();
|
|
}
|
|
|
|
// Construct JSON-RPC payload based on testing_utils.ts
|
|
const rpcPayload = {
|
|
jsonrpc: '2.0',
|
|
id: generateUUID(),
|
|
method: 'message/stream',
|
|
params: {
|
|
message: {
|
|
kind: 'message',
|
|
role: 'user',
|
|
parts: [{ kind: 'text', text: text }],
|
|
messageId: messageId
|
|
},
|
|
metadata: {
|
|
coderAgent: {
|
|
kind: 'agent-settings',
|
|
// Optional: Try to infer or leave empty if server has defaults
|
|
// workspacePath: '/tmp'
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
console.log('Sending payload:', rpcPayload);
|
|
|
|
let response;
|
|
|
|
// Try root endpoint first (JSON-RPC style)
|
|
try {
|
|
console.log(`Attempting POST ${baseUrl}/`);
|
|
response = await fetch(`${baseUrl}/`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(rpcPayload)
|
|
});
|
|
console.log(`Root endpoint response status: ${response.status}`);
|
|
} catch (e) {
|
|
console.error(`Fetch to ${baseUrl}/ failed:`, e);
|
|
}
|
|
|
|
if (!response || response.status === 404) {
|
|
console.log('Root endpoint 404 or failed, trying /message/stream (REST style)...');
|
|
// Fallback to /message/stream
|
|
try {
|
|
response = await fetch(`${baseUrl}/message/stream`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(rpcPayload.params) // Send params as body
|
|
});
|
|
console.log(`/message/stream response status: ${response ? response.status : 'undefined'}`);
|
|
} catch (e) {
|
|
console.error(`Fetch to ${baseUrl}/message/stream failed:`, e);
|
|
}
|
|
}
|
|
|
|
if (!response || response.status === 404) {
|
|
console.log('Trying /v1/message/stream...');
|
|
try {
|
|
response = await fetch(`${baseUrl}/v1/message/stream`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(rpcPayload.params)
|
|
});
|
|
console.log(`/v1/message/stream response status: ${response ? response.status : 'undefined'}`);
|
|
} catch (e) {
|
|
console.error(`Fetch to ${baseUrl}/v1/message/stream failed:`, e);
|
|
}
|
|
}
|
|
|
|
if (!response) {
|
|
throw new Error('All fetch attempts failed. Check console for details.');
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Server returned ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
await handleStream(response);
|
|
}
|
|
|
|
async function handleStream(response) {
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
const messageDiv = document.createElement('div');
|
|
messageDiv.className = 'message agent-message';
|
|
chatHistory.appendChild(messageDiv);
|
|
|
|
let buffer = '';
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
|
|
const chunk = decoder.decode(value, { stream: true });
|
|
buffer += chunk;
|
|
|
|
const lines = buffer.split('\n');
|
|
// Process all complete lines
|
|
for (let i = 0; i < lines.length - 1; i++) {
|
|
const line = lines[i];
|
|
if (line.startsWith('data: ')) {
|
|
try {
|
|
const jsonStr = line.substring(6);
|
|
if (jsonStr.trim() === '[DONE]') continue;
|
|
|
|
const jsonResponse = JSON.parse(jsonStr);
|
|
console.log('Event:', jsonResponse);
|
|
|
|
// The server response wrapper: { jsonrpc: '2.0', result: EVENT, id: ... }
|
|
const event = jsonResponse.result || jsonResponse;
|
|
|
|
if (event.kind === 'status-update' && event.status && event.status.message) {
|
|
const message = event.status.message;
|
|
if (message.parts) {
|
|
const textParts = message.parts.filter(p => p.kind === 'text');
|
|
if (textParts.length > 0) {
|
|
// Accumulate text parts.
|
|
// Note: In a real streaming scenario, we might get partial text or full updates.
|
|
// If _sendTextContent sends a new message each time, we should append.
|
|
// If it sends the same message with more text, we should replace?
|
|
// Based on task.ts, _sendTextContent creates a NEW messageId each time.
|
|
// So we should APPEND.
|
|
|
|
const newText = textParts.map(p => p.text).join('');
|
|
messageDiv.innerText += newText;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle Tool Calls (if any info is sent via different event kind)
|
|
// task.ts sends 'status-update' with 'ToolCallConfirmationEvent' or 'ToolCallUpdateEvent'
|
|
// The message contains 'data' part with ToolCall info.
|
|
|
|
if (event.kind === 'status-update' && event.status.message?.parts) {
|
|
const dataParts = event.status.message.parts.filter(p => p.kind === 'data');
|
|
for (const part of dataParts) {
|
|
if (part.data && part.data.request) { // It's a tool call
|
|
const toolName = part.data.tool?.name || part.data.request.name || 'Unknown Tool';
|
|
const status = part.data.status;
|
|
messageDiv.innerText += `\n[Tool: ${toolName} (${status})]\n`;
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (e) {
|
|
console.error('Parse error', e, line);
|
|
}
|
|
}
|
|
}
|
|
// Keep the last incomplete line in buffer
|
|
buffer = lines[lines.length - 1];
|
|
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
}
|
|
}
|
|
});
|