mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-31 20:22: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) {
|
private showToolExecutionInfo(toolExecutionData: any) {
|
||||||
console.log(`Showing tool execution info: ${JSON.stringify(toolExecutionData)}`);
|
console.log(`Showing tool execution info: ${JSON.stringify(toolExecutionData)}`);
|
||||||
|
|
||||||
// We'll update the in-chat tool execution area in the updateStreamingUI method
|
// Create or get the tool execution container
|
||||||
// This method is now just a hook for the WebSocket handlers
|
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
|
// Make sure the loading indicator is shown during tool execution
|
||||||
this.loadingIndicator.style.display = 'flex';
|
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;
|
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 */
|
/* Make error text more visible */
|
||||||
.text-danger {
|
.text-danger {
|
||||||
color: #dc3545 !important;
|
color: #dc3545 !important;
|
||||||
@ -166,30 +240,3 @@
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: var(--muted-text-color);
|
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;
|
accumulatedText += processedChunk.text;
|
||||||
|
|
||||||
// Forward to callback with original chunk data in case it contains additional information
|
// 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;
|
streamingPaused = true;
|
||||||
// IMPORTANT: Don't send done:true here, as it causes the client to stop processing messages
|
// 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
|
// 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) {
|
while (toolCallIterations < maxToolCallIterations) {
|
||||||
@ -388,8 +388,42 @@ export class ChatPipeline {
|
|||||||
if (isStreaming && streamCallback) {
|
if (isStreaming && streamCallback) {
|
||||||
// For each tool result, format a readable message for the user
|
// For each tool result, format a readable message for the user
|
||||||
const toolName = this.getToolNameFromToolCallId(currentMessages, msg.tool_call_id || '');
|
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 streaming, show progress to the user
|
||||||
if (isStreaming && streamCallback) {
|
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
|
// Extract tool execution status information for Ollama feedback
|
||||||
@ -479,7 +513,7 @@ export class ChatPipeline {
|
|||||||
|
|
||||||
// If streaming, show error to the user
|
// If streaming, show error to the user
|
||||||
if (isStreaming && streamCallback) {
|
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
|
// 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 streaming, inform the user about iteration limit
|
||||||
if (isStreaming && streamCallback) {
|
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
|
// For Ollama, create a status about reaching max iterations
|
||||||
@ -573,7 +607,7 @@ export class ChatPipeline {
|
|||||||
|
|
||||||
// Resume streaming with the final response text
|
// Resume streaming with the final response text
|
||||||
// This is where we send the definitive done:true signal with the complete content
|
// 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 confirmation
|
||||||
log.info(`Sent final response with done=true signal`);
|
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`);
|
log.info(`Sending final streaming response without tool calls: ${currentResponse.text.length} chars`);
|
||||||
|
|
||||||
// Send the final response with done=true to complete the streaming
|
// 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`);
|
log.info(`Sent final non-tool response with done=true signal`);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user