mirror of
https://github.com/zadam/trilium.git
synced 2026-07-07 17:12:53 +02:00
feat(llm): group sources in expandable header
This commit is contained in:
@@ -1639,6 +1639,7 @@
|
||||
"web_search": "Web search",
|
||||
"note_tools": "Note access",
|
||||
"sources": "Sources",
|
||||
"sources_summary": "{{count}} sources from {{sites}} sites",
|
||||
"extended_thinking": "Extended thinking",
|
||||
"legacy_models": "Legacy models",
|
||||
"thinking": "Thinking...",
|
||||
|
||||
@@ -70,49 +70,45 @@
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* Citations */
|
||||
.llm-chat-citations {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
/* Citations table (inside a tool-call-card) */
|
||||
.llm-chat-citations-list {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.llm-chat-citations-list td {
|
||||
padding: 0.25rem 0.75rem;
|
||||
}
|
||||
|
||||
.llm-chat-citations-list tr + tr td {
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.llm-chat-citations-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--muted-text-color);
|
||||
margin-bottom: 0.25rem;
|
||||
.llm-chat-citation-title {
|
||||
max-width: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.llm-chat-citations-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.llm-chat-citations-list li {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.llm-chat-citations-list a {
|
||||
.llm-chat-citation-title a {
|
||||
color: var(--link-color, #007bff);
|
||||
text-decoration: none;
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.llm-chat-citations-list a:hover {
|
||||
.llm-chat-citation-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.llm-chat-citation-site {
|
||||
white-space: nowrap;
|
||||
color: var(--muted-text-color);
|
||||
font-size: 0.75rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Error */
|
||||
.llm-chat-error {
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
@@ -3,6 +3,8 @@ import "./ChatMessage.css";
|
||||
import { Marked } from "marked";
|
||||
import { useMemo } from "preact/hooks";
|
||||
|
||||
import type { LlmCitation } from "@triliumnext/commons";
|
||||
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import utils from "../../../services/utils.js";
|
||||
import { SanitizedHtml } from "../../react/RawHtml.js";
|
||||
@@ -35,6 +37,65 @@ type ContentGroup =
|
||||
| { type: "text"; block: TextBlock; index: number }
|
||||
| { type: "tool_calls"; blocks: ToolCallBlock[]; index: number };
|
||||
|
||||
function getUniqueSiteCount(citations: LlmCitation[]): number {
|
||||
const hosts = new Set<string>();
|
||||
for (const c of citations) {
|
||||
if (c.url) {
|
||||
try {
|
||||
hosts.add(new URL(c.url).hostname);
|
||||
} catch { /* ignore invalid URLs */ }
|
||||
}
|
||||
}
|
||||
return hosts.size;
|
||||
}
|
||||
|
||||
function CitationsSection({ citations }: { citations: LlmCitation[] }) {
|
||||
const siteCount = getUniqueSiteCount(citations);
|
||||
const summary = t("llm_chat.sources_summary", { count: citations.length, sites: siteCount });
|
||||
|
||||
return (
|
||||
<div className="llm-chat-tool-call-card">
|
||||
<details className="llm-chat-tool-call-section">
|
||||
<summary className="llm-chat-tool-call-section-summary">
|
||||
<span className="bx bx-link" />
|
||||
{summary}
|
||||
<span className="bx bx-chevron-down llm-chat-tool-call-chevron" />
|
||||
</summary>
|
||||
<table className="llm-chat-citations-list">
|
||||
<tbody>
|
||||
{citations.map((citation, idx) => {
|
||||
const title = citation.title || citation.citedText?.slice(0, 80) || `Source ${idx + 1}`;
|
||||
let hostname: string | null = null;
|
||||
if (citation.url) {
|
||||
try {
|
||||
hostname = new URL(citation.url).hostname;
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={idx}>
|
||||
<td className="llm-chat-citation-title">
|
||||
{citation.url ? (
|
||||
<a href={citation.url} target="_blank" rel="noopener noreferrer" title={citation.url}>
|
||||
{title}
|
||||
</a>
|
||||
) : (
|
||||
<span>{title}</span>
|
||||
)}
|
||||
</td>
|
||||
{hostname && (
|
||||
<td className="llm-chat-citation-site">{hostname}</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ChatMessage({ message, isStreaming }: Props) {
|
||||
const roleLabel = message.role === "user" ? t("llm_chat.role_user") : t("llm_chat.role_assistant");
|
||||
const isError = message.type === "error";
|
||||
@@ -95,47 +156,7 @@ export default function ChatMessage({ message, isStreaming }: Props) {
|
||||
)}
|
||||
</div>
|
||||
{message.citations && message.citations.length > 0 && (
|
||||
<div className="llm-chat-citations">
|
||||
<div className="llm-chat-citations-label">
|
||||
<span className="bx bx-link" />
|
||||
{t("llm_chat.sources")}
|
||||
</div>
|
||||
<ul className="llm-chat-citations-list">
|
||||
{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>
|
||||
<CitationsSection citations={message.citations} />
|
||||
)}
|
||||
</div>
|
||||
<div className={`llm-chat-footer llm-chat-footer-${message.role}`}>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Tool call section — individual tool call within a card */
|
||||
|
||||
Reference in New Issue
Block a user