mirror of
https://github.com/zadam/trilium.git
synced 2026-05-09 03:27:19 +02:00
chore(llm): fix type issues
This commit is contained in:
@@ -19,7 +19,8 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
||||
search: null,
|
||||
text: null,
|
||||
webView: null,
|
||||
spreadsheet: null
|
||||
spreadsheet: null,
|
||||
llmChat: null
|
||||
};
|
||||
|
||||
export const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||
|
||||
@@ -16,8 +16,9 @@ export interface ChatConfig {
|
||||
}
|
||||
|
||||
export interface Citation {
|
||||
url: string;
|
||||
url?: string;
|
||||
title?: string;
|
||||
citedText?: string;
|
||||
}
|
||||
|
||||
export interface StreamCallbacks {
|
||||
@@ -91,7 +92,9 @@ export async function streamChatCompletion(
|
||||
callbacks.onToolResult?.(data.toolName, data.result);
|
||||
break;
|
||||
case "citation":
|
||||
callbacks.onCitation?.({ url: data.url, title: data.title });
|
||||
if (data.citation) {
|
||||
callbacks.onCitation?.(data.citation);
|
||||
}
|
||||
break;
|
||||
case "error":
|
||||
callbacks.onError(data.error);
|
||||
|
||||
@@ -88,18 +88,39 @@ export default function ChatMessage({ message, isStreaming }: Props) {
|
||||
{t("llm_chat.sources")}
|
||||
</div>
|
||||
<ul className="llm-chat-citations-list">
|
||||
{message.citations.map((citation, idx) => (
|
||||
<li key={idx}>
|
||||
<a
|
||||
href={citation.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={citation.url}
|
||||
>
|
||||
{citation.title || new URL(citation.url).hostname}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
{message.citations.map((citation, idx) => {
|
||||
// Determine display text: title, URL hostname, or cited text
|
||||
let displayText = citation.title;
|
||||
if (!displayText && citation.url) {
|
||||
try {
|
||||
displayText = new URL(citation.url).hostname;
|
||||
} catch {
|
||||
displayText = citation.url;
|
||||
}
|
||||
}
|
||||
if (!displayText) {
|
||||
displayText = citation.citedText?.slice(0, 50) || `Source ${idx + 1}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={idx}>
|
||||
{citation.url ? (
|
||||
<a
|
||||
href={citation.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={citation.citedText || citation.url}
|
||||
>
|
||||
{displayText}
|
||||
</a>
|
||||
) : (
|
||||
<span title={citation.citedText}>
|
||||
{displayText}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import type { LlmProvider, LlmMessage, LlmStreamChunk, LlmProviderConfig } from "../types.js";
|
||||
|
||||
import type { LlmMessage, LlmProvider, LlmProviderConfig,LlmStreamChunk } from "../types.js";
|
||||
|
||||
const DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
||||
const DEFAULT_MAX_TOKENS = 8096;
|
||||
|
||||
/**
|
||||
* Server-side web search tool type.
|
||||
* Not yet in SDK types as of @anthropic-ai/sdk.
|
||||
*/
|
||||
interface WebSearchTool {
|
||||
type: "web_search_20250305";
|
||||
name: "web_search";
|
||||
max_uses?: number;
|
||||
}
|
||||
|
||||
export class AnthropicProvider implements LlmProvider {
|
||||
name = "anthropic";
|
||||
private client: Anthropic;
|
||||
@@ -23,19 +34,18 @@ export class AnthropicProvider implements LlmProvider {
|
||||
const systemPrompt = config.systemPrompt || messages.find(m => m.role === "system")?.content;
|
||||
const chatMessages = messages.filter(m => m.role !== "system");
|
||||
|
||||
// Build tools array - using 'unknown' assertion for server-side tools
|
||||
// that may not be in the SDK types yet
|
||||
const tools: unknown[] = [];
|
||||
// Build tools array
|
||||
// Using union with WebSearchTool since it's not in SDK types yet
|
||||
const tools: (Anthropic.ToolUnion | WebSearchTool)[] = [];
|
||||
if (config.enableWebSearch) {
|
||||
tools.push({
|
||||
type: "web_search_20250305",
|
||||
name: "web_search",
|
||||
max_uses: 5 // Limit searches per request
|
||||
});
|
||||
} satisfies WebSearchTool);
|
||||
}
|
||||
|
||||
try {
|
||||
// Cast tools to any since server-side tools may not be in SDK types yet
|
||||
const streamParams: Anthropic.Messages.MessageStreamParams = {
|
||||
model: config.model || DEFAULT_MODEL,
|
||||
max_tokens: config.maxTokens || DEFAULT_MAX_TOKENS,
|
||||
@@ -47,7 +57,8 @@ export class AnthropicProvider implements LlmProvider {
|
||||
};
|
||||
|
||||
if (tools.length > 0) {
|
||||
(streamParams as any).tools = tools;
|
||||
// Cast needed until SDK adds WebSearchTool type
|
||||
streamParams.tools = tools as Anthropic.ToolUnion[];
|
||||
}
|
||||
|
||||
// Enable extended thinking for deeper reasoning
|
||||
@@ -55,7 +66,7 @@ export class AnthropicProvider implements LlmProvider {
|
||||
const thinkingBudget = config.thinkingBudget || 10000;
|
||||
// max_tokens must be greater than thinking budget
|
||||
streamParams.max_tokens = Math.max(streamParams.max_tokens, thinkingBudget + 4000);
|
||||
(streamParams as any).thinking = {
|
||||
streamParams.thinking = {
|
||||
type: "enabled",
|
||||
budget_tokens: thinkingBudget
|
||||
};
|
||||
@@ -82,7 +93,7 @@ export class AnthropicProvider implements LlmProvider {
|
||||
if (delta.type === "text_delta") {
|
||||
yield { type: "text", content: delta.text };
|
||||
} else if (delta.type === "thinking_delta") {
|
||||
yield { type: "thinking", content: (delta as any).thinking };
|
||||
yield { type: "thinking", content: delta.thinking };
|
||||
} else if (delta.type === "input_json_delta") {
|
||||
// Tool input is being streamed - we could accumulate it
|
||||
// For now, we already emitted tool_use at start
|
||||
@@ -102,17 +113,21 @@ export class AnthropicProvider implements LlmProvider {
|
||||
// Get the final message to extract any citations
|
||||
const finalMessage = await stream.finalMessage();
|
||||
for (const block of finalMessage.content) {
|
||||
if (block.type === "text") {
|
||||
// Check for citations in the text block
|
||||
// Anthropic returns citations as part of the content
|
||||
if ("citations" in block && Array.isArray((block as any).citations)) {
|
||||
for (const citation of (block as any).citations) {
|
||||
yield {
|
||||
type: "citation",
|
||||
url: citation.url || citation.source,
|
||||
title: citation.title
|
||||
};
|
||||
}
|
||||
if (block.type === "text" && block.citations) {
|
||||
for (const citation of block.citations) {
|
||||
// Extract citation info from SDK types (CitationCharLocation, etc.)
|
||||
// These have: cited_text, document_index, document_title
|
||||
// Web search citations may have additional properties at runtime
|
||||
const citationData = citation as unknown as Record<string, unknown>;
|
||||
yield {
|
||||
type: "citation",
|
||||
citation: {
|
||||
title: citation.document_title ?? undefined,
|
||||
citedText: citation.cited_text,
|
||||
// URL may be present for web search results (not in SDK types yet)
|
||||
url: typeof citationData.url === "string" ? citationData.url : undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,19 @@ export interface LlmMessage {
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Citation information extracted from LLM responses.
|
||||
* May include URL (for web search) or document metadata (for document citations).
|
||||
*/
|
||||
export interface LlmCitation {
|
||||
/** Source URL (typically from web search) */
|
||||
url?: string;
|
||||
/** Document or page title */
|
||||
title?: string;
|
||||
/** The text that was cited */
|
||||
citedText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream chunk types for real-time updates.
|
||||
*/
|
||||
@@ -16,7 +29,7 @@ export type LlmStreamChunk =
|
||||
| { type: "thinking"; content: string }
|
||||
| { type: "tool_use"; toolName: string; toolInput: Record<string, unknown> }
|
||||
| { type: "tool_result"; toolName: string; result: string }
|
||||
| { type: "citation"; url: string; title?: string }
|
||||
| { type: "citation"; citation: LlmCitation }
|
||||
| { type: "error"; error: string }
|
||||
| { type: "done" };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user