mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 19:12:27 +08:00
Structure tool response
This commit is contained in:
parent
519076148d
commit
6bba1be5f4
@ -635,16 +635,117 @@ export default class LlmChatPanel extends BasicWidget {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tool execution information in the UI
|
||||
* Handle tool execution updates
|
||||
*/
|
||||
private showToolExecutionInfo(toolExecutionData: any) {
|
||||
console.log(`Showing tool execution info: ${JSON.stringify(toolExecutionData)}`);
|
||||
|
||||
// We'll update the in-chat tool execution area in the updateStreamingUI method
|
||||
// This method is now just a hook for the WebSocket handlers
|
||||
// Create or get the tool execution container
|
||||
let toolExecutionElement = this.noteContextChatMessages.querySelector('.chat-tool-execution');
|
||||
if (!toolExecutionElement) {
|
||||
toolExecutionElement = document.createElement('div');
|
||||
toolExecutionElement.className = 'chat-tool-execution mb-3';
|
||||
|
||||
// Create header with title and controls
|
||||
const header = document.createElement('div');
|
||||
header.className = 'tool-execution-header d-flex align-items-center p-2 rounded';
|
||||
header.innerHTML = `
|
||||
<i class="bx bx-terminal me-2"></i>
|
||||
<span class="flex-grow-1">Tool Execution</span>
|
||||
<button type="button" class="btn btn-sm btn-link p-0 text-muted tool-execution-chat-clear" title="Clear tool execution history">
|
||||
<i class="bx bx-x"></i>
|
||||
</button>
|
||||
`;
|
||||
toolExecutionElement.appendChild(header);
|
||||
|
||||
// Add click handler for clear button
|
||||
const clearButton = toolExecutionElement.querySelector('.tool-execution-chat-clear');
|
||||
if (clearButton) {
|
||||
clearButton.addEventListener('click', () => {
|
||||
const stepsContainer = toolExecutionElement?.querySelector('.tool-execution-container');
|
||||
if (stepsContainer) {
|
||||
stepsContainer.innerHTML = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Create container for tool steps
|
||||
const stepsContainer = document.createElement('div');
|
||||
stepsContainer.className = 'tool-execution-container p-2 rounded mb-2';
|
||||
toolExecutionElement.appendChild(stepsContainer);
|
||||
|
||||
// Add to chat messages
|
||||
this.noteContextChatMessages.appendChild(toolExecutionElement);
|
||||
}
|
||||
|
||||
// Get the steps container
|
||||
const stepsContainer = toolExecutionElement.querySelector('.tool-execution-container');
|
||||
if (!stepsContainer) return;
|
||||
|
||||
// Process based on action type
|
||||
if (toolExecutionData.action === 'start') {
|
||||
// Tool execution started
|
||||
const step = document.createElement('div');
|
||||
step.className = 'tool-step executing p-2 mb-2 rounded';
|
||||
step.innerHTML = `
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bx bx-code-block me-2"></i>
|
||||
<span>Executing tool: <strong>${toolExecutionData.tool || 'unknown'}</strong></span>
|
||||
</div>
|
||||
<div class="tool-args mt-1 ps-3">
|
||||
<code>Args: ${JSON.stringify(toolExecutionData.args || {}, null, 2)}</code>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(step);
|
||||
}
|
||||
else if (toolExecutionData.action === 'result') {
|
||||
// Tool execution completed with results
|
||||
const step = document.createElement('div');
|
||||
step.className = 'tool-step result p-2 mb-2 rounded';
|
||||
|
||||
let resultDisplay = '';
|
||||
|
||||
// Format the result based on type
|
||||
if (typeof toolExecutionData.result === 'object') {
|
||||
// For objects, format as pretty JSON
|
||||
resultDisplay = `<pre class="mb-0"><code>${JSON.stringify(toolExecutionData.result, null, 2)}</code></pre>`;
|
||||
} else {
|
||||
// For simple values, display as text
|
||||
resultDisplay = `<div>${String(toolExecutionData.result)}</div>`;
|
||||
}
|
||||
|
||||
step.innerHTML = `
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bx bx-terminal me-2"></i>
|
||||
<span>Tool: <strong>${toolExecutionData.tool || 'unknown'}</strong></span>
|
||||
</div>
|
||||
<div class="tool-result mt-1 ps-3">
|
||||
${resultDisplay}
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(step);
|
||||
}
|
||||
else if (toolExecutionData.action === 'error') {
|
||||
// Tool execution failed
|
||||
const step = document.createElement('div');
|
||||
step.className = 'tool-step error p-2 mb-2 rounded';
|
||||
step.innerHTML = `
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bx bx-error-circle me-2"></i>
|
||||
<span>Error in tool: <strong>${toolExecutionData.tool || 'unknown'}</strong></span>
|
||||
</div>
|
||||
<div class="tool-error mt-1 ps-3 text-danger">
|
||||
${toolExecutionData.error || 'Unknown error'}
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(step);
|
||||
}
|
||||
|
||||
// Make sure the loading indicator is shown during tool execution
|
||||
this.loadingIndicator.style.display = 'flex';
|
||||
|
||||
// Scroll the chat container to show the tool execution
|
||||
this.chatContainer.scrollTop = this.chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,6 +87,80 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Tool step specific styling */
|
||||
.tool-step.executing {
|
||||
background-color: rgba(0, 123, 255, 0.05);
|
||||
border-color: rgba(0, 123, 255, 0.2);
|
||||
}
|
||||
|
||||
.tool-step.result {
|
||||
background-color: rgba(40, 167, 69, 0.05);
|
||||
border-color: rgba(40, 167, 69, 0.2);
|
||||
}
|
||||
|
||||
.tool-step.error {
|
||||
background-color: rgba(220, 53, 69, 0.05);
|
||||
border-color: rgba(220, 53, 69, 0.2);
|
||||
}
|
||||
|
||||
/* Tool result formatting */
|
||||
.tool-result pre {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
border-radius: 0.25rem;
|
||||
overflow: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.tool-result code {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.tool-args code {
|
||||
display: block;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.85em;
|
||||
color: var(--muted-text-color);
|
||||
white-space: pre-wrap;
|
||||
overflow: auto;
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
/* Tool Execution in Chat Styling */
|
||||
.chat-tool-execution {
|
||||
padding: 0 0 0 36px; /* Aligned with message content, accounting for avatar width */
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tool-execution-container {
|
||||
background-color: var(--accented-background-color, rgba(245, 247, 250, 0.7));
|
||||
border: 1px solid var(--subtle-border-color);
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
max-width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.tool-execution-header {
|
||||
background-color: var(--main-background-color);
|
||||
border-bottom: 1px solid var(--subtle-border-color);
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--muted-text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tool-execution-chat-steps {
|
||||
padding: 0.5rem;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Make error text more visible */
|
||||
.text-danger {
|
||||
color: #dc3545 !important;
|
||||
@ -166,30 +240,3 @@
|
||||
padding: 1rem;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
/* Tool Execution in Chat Styling */
|
||||
.chat-tool-execution {
|
||||
padding: 0 0 0 36px; /* Aligned with message content, accounting for avatar width */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-execution-container {
|
||||
background-color: var(--accented-background-color, rgba(245, 247, 250, 0.7));
|
||||
border: 1px solid var(--subtle-border-color);
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
max-width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.tool-execution-header {
|
||||
border-bottom: 1px solid var(--subtle-border-color);
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.tool-execution-chat-steps {
|
||||
padding: 0.5rem;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
@ -295,7 +295,7 @@ export class ChatPipeline {
|
||||
accumulatedText += processedChunk.text;
|
||||
|
||||
// Forward to callback with original chunk data in case it contains additional information
|
||||
await streamCallback!(processedChunk.text, processedChunk.done, chunk);
|
||||
streamCallback(processedChunk.text, processedChunk.done, chunk);
|
||||
});
|
||||
}
|
||||
|
||||
@ -347,7 +347,7 @@ export class ChatPipeline {
|
||||
streamingPaused = true;
|
||||
// IMPORTANT: Don't send done:true here, as it causes the client to stop processing messages
|
||||
// Instead, send a marker message that indicates tools will be executed
|
||||
await streamCallback('\n\n[Executing tools...]\n\n', false);
|
||||
streamCallback('\n\n[Executing tools...]\n\n', false);
|
||||
}
|
||||
|
||||
while (toolCallIterations < maxToolCallIterations) {
|
||||
@ -388,8 +388,42 @@ export class ChatPipeline {
|
||||
if (isStreaming && streamCallback) {
|
||||
// For each tool result, format a readable message for the user
|
||||
const toolName = this.getToolNameFromToolCallId(currentMessages, msg.tool_call_id || '');
|
||||
const formattedToolResult = `[Tool: ${toolName || 'unknown'}]\n${msg.content}\n\n`;
|
||||
streamCallback(formattedToolResult, false);
|
||||
|
||||
// Create a structured tool result message
|
||||
// The client will receive this structured data and can display it properly
|
||||
try {
|
||||
// Parse the result content if it's JSON
|
||||
let parsedContent = msg.content;
|
||||
try {
|
||||
// Check if the content is JSON
|
||||
if (msg.content.trim().startsWith('{') || msg.content.trim().startsWith('[')) {
|
||||
parsedContent = JSON.parse(msg.content);
|
||||
}
|
||||
} catch (e) {
|
||||
// If parsing fails, keep the original content
|
||||
log.info(`Could not parse tool result as JSON: ${e}`);
|
||||
}
|
||||
|
||||
// Send the structured tool result directly so the client has the raw data
|
||||
streamCallback('', false, {
|
||||
toolExecution: {
|
||||
action: 'result',
|
||||
tool: toolName,
|
||||
toolCallId: msg.tool_call_id,
|
||||
result: parsedContent
|
||||
}
|
||||
});
|
||||
|
||||
// Still send the formatted text for backwards compatibility
|
||||
// This will be phased out once the client is updated
|
||||
const formattedToolResult = `[Tool: ${toolName || 'unknown'}]\n${msg.content}\n\n`;
|
||||
streamCallback(formattedToolResult, false);
|
||||
} catch (err) {
|
||||
log.error(`Error sending structured tool result: ${err}`);
|
||||
// Fall back to the old format if there's an error
|
||||
const formattedToolResult = `[Tool: ${toolName || 'unknown'}]\n${msg.content}\n\n`;
|
||||
streamCallback(formattedToolResult, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -403,7 +437,7 @@ export class ChatPipeline {
|
||||
|
||||
// If streaming, show progress to the user
|
||||
if (isStreaming && streamCallback) {
|
||||
await streamCallback('[Generating response with tool results...]\n\n', false);
|
||||
streamCallback('[Generating response with tool results...]\n\n', false);
|
||||
}
|
||||
|
||||
// Extract tool execution status information for Ollama feedback
|
||||
@ -479,7 +513,7 @@ export class ChatPipeline {
|
||||
|
||||
// If streaming, show error to the user
|
||||
if (isStreaming && streamCallback) {
|
||||
await streamCallback(`[Tool execution error: ${error.message || 'unknown error'}]\n\n`, false);
|
||||
streamCallback(`[Tool execution error: ${error.message || 'unknown error'}]\n\n`, false);
|
||||
}
|
||||
|
||||
// For Ollama, create tool execution status with the error
|
||||
@ -529,7 +563,7 @@ export class ChatPipeline {
|
||||
|
||||
// If streaming, inform the user about iteration limit
|
||||
if (isStreaming && streamCallback) {
|
||||
await streamCallback(`[Reached maximum of ${maxToolCallIterations} tool calls. Finalizing response...]\n\n`, false);
|
||||
streamCallback(`[Reached maximum of ${maxToolCallIterations} tool calls. Finalizing response...]\n\n`, false);
|
||||
}
|
||||
|
||||
// For Ollama, create a status about reaching max iterations
|
||||
@ -573,7 +607,7 @@ export class ChatPipeline {
|
||||
|
||||
// Resume streaming with the final response text
|
||||
// This is where we send the definitive done:true signal with the complete content
|
||||
await streamCallback(currentResponse.text, true);
|
||||
streamCallback(currentResponse.text, true);
|
||||
|
||||
// Log confirmation
|
||||
log.info(`Sent final response with done=true signal`);
|
||||
@ -587,7 +621,7 @@ export class ChatPipeline {
|
||||
log.info(`Sending final streaming response without tool calls: ${currentResponse.text.length} chars`);
|
||||
|
||||
// Send the final response with done=true to complete the streaming
|
||||
await streamCallback(currentResponse.text, true);
|
||||
streamCallback(currentResponse.text, true);
|
||||
|
||||
log.info(`Sent final non-tool response with done=true signal`);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user