mirror of
https://github.com/zadam/trilium.git
synced 2026-05-31 12:22:41 +02:00
feat(llm): show tool calls as references
This commit is contained in:
@@ -1622,7 +1622,10 @@
|
||||
"sources": "Sources",
|
||||
"extended_thinking": "Extended thinking",
|
||||
"thinking": "Thinking...",
|
||||
"thought_process": "Thought process"
|
||||
"thought_process": "Thought process",
|
||||
"tool_calls": "{{count}} tool call(s)",
|
||||
"input": "Input",
|
||||
"result": "Result"
|
||||
},
|
||||
"shared_switch": {
|
||||
"shared": "Shared",
|
||||
|
||||
@@ -12,6 +12,13 @@ marked.setOptions({
|
||||
|
||||
type MessageType = "message" | "error" | "thinking";
|
||||
|
||||
interface ToolCall {
|
||||
id: string;
|
||||
toolName: string;
|
||||
input: Record<string, unknown>;
|
||||
result?: string;
|
||||
}
|
||||
|
||||
interface StoredMessage {
|
||||
id: string;
|
||||
role: "user" | "assistant" | "system";
|
||||
@@ -20,6 +27,8 @@ interface StoredMessage {
|
||||
citations?: LlmCitation[];
|
||||
/** Message type for special rendering. Defaults to "message" if omitted. */
|
||||
type?: MessageType;
|
||||
/** Tool calls made during this response */
|
||||
toolCalls?: ToolCall[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -81,6 +90,42 @@ export default function ChatMessage({ message, isStreaming }: Props) {
|
||||
message.content
|
||||
)}
|
||||
</div>
|
||||
{message.toolCalls && message.toolCalls.length > 0 && (
|
||||
<details className="llm-chat-tool-calls">
|
||||
<summary className="llm-chat-tool-calls-summary">
|
||||
<span className="bx bx-wrench" />
|
||||
{t("llm_chat.tool_calls", { count: message.toolCalls.length })}
|
||||
</summary>
|
||||
<div className="llm-chat-tool-calls-list">
|
||||
{message.toolCalls.map((tool) => (
|
||||
<div key={tool.id} className="llm-chat-tool-call">
|
||||
<div className="llm-chat-tool-call-name">
|
||||
{tool.toolName}
|
||||
</div>
|
||||
<div className="llm-chat-tool-call-input">
|
||||
<strong>{t("llm_chat.input")}:</strong>
|
||||
<pre>{JSON.stringify(tool.input, null, 2)}</pre>
|
||||
</div>
|
||||
{tool.result && (
|
||||
<div className="llm-chat-tool-call-result">
|
||||
<strong>{t("llm_chat.result")}:</strong>
|
||||
<pre>{(() => {
|
||||
if (typeof tool.result === "string" && (tool.result.startsWith("{") || tool.result.startsWith("["))) {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(tool.result), null, 2);
|
||||
} catch {
|
||||
return tool.result;
|
||||
}
|
||||
}
|
||||
return tool.result;
|
||||
})()}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
{message.citations && message.citations.length > 0 && (
|
||||
<div className="llm-chat-citations">
|
||||
<div className="llm-chat-citations-label">
|
||||
|
||||
@@ -422,3 +422,86 @@
|
||||
.llm-chat-markdown em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Tool calls display */
|
||||
.llm-chat-tool-calls {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.llm-chat-tool-calls-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: var(--muted-text-color);
|
||||
padding: 0.25rem 0;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.llm-chat-tool-calls-summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.llm-chat-tool-calls-summary::before {
|
||||
content: "▶";
|
||||
font-size: 0.7em;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.llm-chat-tool-calls[open] .llm-chat-tool-calls-summary::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.llm-chat-tool-calls-summary .bx {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.llm-chat-tool-calls-list {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.llm-chat-tool-call {
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.llm-chat-tool-call-name {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--main-text-color);
|
||||
font-family: var(--monospace-font-family, monospace);
|
||||
}
|
||||
|
||||
.llm-chat-tool-call-input,
|
||||
.llm-chat-tool-call-result {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.llm-chat-tool-call-input strong,
|
||||
.llm-chat-tool-call-result strong {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted-text-color);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.llm-chat-tool-call pre {
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
background: var(--main-background-color);
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.8rem;
|
||||
font-family: var(--monospace-font-family, monospace);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,13 @@ import "./LlmChat.css";
|
||||
|
||||
type MessageType = "message" | "error" | "thinking";
|
||||
|
||||
interface ToolCall {
|
||||
id: string;
|
||||
toolName: string;
|
||||
input: Record<string, unknown>;
|
||||
result?: string;
|
||||
}
|
||||
|
||||
interface StoredMessage {
|
||||
id: string;
|
||||
role: "user" | "assistant" | "system";
|
||||
@@ -18,6 +25,8 @@ interface StoredMessage {
|
||||
citations?: LlmCitation[];
|
||||
/** Message type for special rendering. Defaults to "message" if omitted. */
|
||||
type?: MessageType;
|
||||
/** Tool calls made during this response */
|
||||
toolCalls?: ToolCall[];
|
||||
}
|
||||
|
||||
interface LlmChatContent {
|
||||
@@ -137,6 +146,7 @@ export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) {
|
||||
let assistantContent = "";
|
||||
let thinkingContent = "";
|
||||
const citations: LlmCitation[] = [];
|
||||
const toolCalls: ToolCall[] = [];
|
||||
|
||||
const apiMessages: LlmMessage[] = newMessages.map(m => ({
|
||||
role: m.role,
|
||||
@@ -157,11 +167,24 @@ export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) {
|
||||
setStreamingThinking(thinkingContent);
|
||||
setToolActivity(t("llm_chat.thinking"));
|
||||
},
|
||||
onToolUse: (toolName, _input) => {
|
||||
onToolUse: (toolName, toolInput) => {
|
||||
const toolLabel = toolName === "web_search"
|
||||
? t("llm_chat.searching_web")
|
||||
: `Using ${toolName}...`;
|
||||
setToolActivity(toolLabel);
|
||||
// Track the tool call
|
||||
toolCalls.push({
|
||||
id: randomString(),
|
||||
toolName,
|
||||
input: toolInput
|
||||
});
|
||||
},
|
||||
onToolResult: (toolName, result) => {
|
||||
// Find the most recent tool call with this name and add the result
|
||||
const toolCall = [...toolCalls].reverse().find(tc => tc.toolName === toolName && !tc.result);
|
||||
if (toolCall) {
|
||||
toolCall.result = result;
|
||||
}
|
||||
},
|
||||
onCitation: (citation) => {
|
||||
citations.push(citation);
|
||||
@@ -198,13 +221,14 @@ export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) {
|
||||
});
|
||||
}
|
||||
|
||||
if (assistantContent) {
|
||||
if (assistantContent || toolCalls.length > 0) {
|
||||
newMessages.push({
|
||||
id: randomString(),
|
||||
role: "assistant",
|
||||
content: assistantContent,
|
||||
createdAt: new Date().toISOString(),
|
||||
citations: citations.length > 0 ? citations : undefined
|
||||
citations: citations.length > 0 ? citations : undefined,
|
||||
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user