mirror of
https://github.com/zadam/trilium.git
synced 2026-03-29 08:10:13 +02:00
Compare commits
7 Commits
main
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b551f0fe2d | ||
|
|
f6e8bdb0fd | ||
|
|
9029ea8085 | ||
|
|
d61ade9fe9 | ||
|
|
aa1fe549c7 | ||
|
|
e3701bbcb4 | ||
|
|
fb7fc4bf0c |
@@ -43,7 +43,7 @@
|
||||
"@univerjs/preset-sheets-note": "0.18.0",
|
||||
"@univerjs/preset-sheets-sort": "0.18.0",
|
||||
"@univerjs/presets": "0.18.0",
|
||||
"@zumer/snapdom": "2.7.0",
|
||||
"@zumer/snapdom": "2.6.0",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.8",
|
||||
"boxicons": "2.1.4",
|
||||
|
||||
@@ -18,7 +18,7 @@ const RELATION = "relation";
|
||||
* end user. Those types should be used only for checking against, they are
|
||||
* not for direct use.
|
||||
*/
|
||||
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "spreadsheet";
|
||||
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "spreadsheet" | "llmChat";
|
||||
|
||||
export interface NotePathRecord {
|
||||
isArchived: boolean;
|
||||
|
||||
105
apps/client/src/services/llm_chat.ts
Normal file
105
apps/client/src/services/llm_chat.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import server from "./server.js";
|
||||
|
||||
export interface ChatMessage {
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface ChatConfig {
|
||||
provider?: string;
|
||||
model?: string;
|
||||
systemPrompt?: string;
|
||||
enableWebSearch?: boolean;
|
||||
}
|
||||
|
||||
export interface Citation {
|
||||
url: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface StreamCallbacks {
|
||||
onChunk: (text: string) => void;
|
||||
onToolUse?: (toolName: string, input: Record<string, unknown>) => void;
|
||||
onToolResult?: (toolName: string, result: string) => void;
|
||||
onCitation?: (citation: Citation) => void;
|
||||
onError: (error: string) => void;
|
||||
onDone: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream a chat completion from the LLM API using Server-Sent Events.
|
||||
*/
|
||||
export async function streamChatCompletion(
|
||||
messages: ChatMessage[],
|
||||
config: ChatConfig,
|
||||
callbacks: StreamCallbacks
|
||||
): Promise<void> {
|
||||
const headers = await server.getHeaders();
|
||||
|
||||
const response = await fetch(`${window.glob.baseApiUrl}llm-chat/stream`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Type": "application/json"
|
||||
} as HeadersInit,
|
||||
body: JSON.stringify({ messages, config })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
callbacks.onError(`HTTP ${response.status}: ${response.statusText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
callbacks.onError("No response body");
|
||||
return;
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = "";
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop() || "";
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("data: ")) {
|
||||
try {
|
||||
const data = JSON.parse(line.slice(6));
|
||||
|
||||
switch (data.type) {
|
||||
case "text":
|
||||
callbacks.onChunk(data.content);
|
||||
break;
|
||||
case "tool_use":
|
||||
callbacks.onToolUse?.(data.toolName, data.toolInput);
|
||||
break;
|
||||
case "tool_result":
|
||||
callbacks.onToolResult?.(data.toolName, data.result);
|
||||
break;
|
||||
case "citation":
|
||||
callbacks.onCitation?.({ url: data.url, title: data.title });
|
||||
break;
|
||||
case "error":
|
||||
callbacks.onError(data.error);
|
||||
break;
|
||||
case "done":
|
||||
callbacks.onDone();
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore JSON parse errors for partial data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ export const NOTE_TYPES: NoteTypeMapping[] = [
|
||||
{ type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), icon: "bxs-network-chart" },
|
||||
|
||||
// Misc note types
|
||||
{ type: "llmChat", mime: "application/json", title: t("note_types.llm-chat"), icon: "bx-message-square-dots" },
|
||||
{ type: "render", mime: "", title: t("note_types.render-note"), icon: "bx-extension" },
|
||||
{ type: "search", title: t("note_types.saved-search"), icon: "bx-file-find", static: true },
|
||||
{ type: "webView", mime: "", title: t("note_types.web-view"), icon: "bx-globe-alt" },
|
||||
|
||||
@@ -1599,6 +1599,7 @@
|
||||
"geo-map": "Geo Map",
|
||||
"beta-feature": "Beta",
|
||||
"ai-chat": "AI Chat",
|
||||
"llm-chat": "AI Chat",
|
||||
"task-list": "Task List",
|
||||
"new-feature": "New",
|
||||
"collections": "Collections",
|
||||
@@ -1610,6 +1611,15 @@
|
||||
"toggle-on-hint": "Note is not protected, click to make it protected",
|
||||
"toggle-off-hint": "Note is protected, click to make it unprotected"
|
||||
},
|
||||
"llm_chat": {
|
||||
"placeholder": "Type a message...",
|
||||
"send": "Send",
|
||||
"sending": "Sending...",
|
||||
"empty_state": "Start a conversation by typing a message below.",
|
||||
"searching_web": "Searching the web...",
|
||||
"web_search": "Web search",
|
||||
"sources": "Sources"
|
||||
},
|
||||
"shared_switch": {
|
||||
"shared": "Shared",
|
||||
"toggle-on-title": "Share the note",
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TypeWidgetProps } from "./type_widgets/type_widget";
|
||||
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
|
||||
* for protected session or attachment information.
|
||||
*/
|
||||
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "sqlConsole";
|
||||
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code" | "llmChat"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "sqlConsole" | "llmChat";
|
||||
|
||||
export type TypeWidget = ((props: TypeWidgetProps) => VNode | JSX.Element | undefined);
|
||||
type NoteTypeView = () => (Promise<{ default: TypeWidget } | TypeWidget> | TypeWidget);
|
||||
@@ -147,5 +147,11 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
|
||||
className: "note-detail-spreadsheet",
|
||||
printable: true,
|
||||
isFullHeight: true
|
||||
},
|
||||
llmChat: {
|
||||
view: () => import("./type_widgets/llm_chat/LlmChat"),
|
||||
className: "note-detail-llm-chat",
|
||||
printable: true,
|
||||
isFullHeight: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@ export function NoteContextMenu({ note, noteContext, itemsAtStart, itemsNearNote
|
||||
);
|
||||
const isElectron = getIsElectron();
|
||||
const isMac = getIsMac();
|
||||
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "spreadsheet"].includes(noteType);
|
||||
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "spreadsheet", "llmChat"].includes(noteType);
|
||||
const isSearchOrBook = ["search", "book"].includes(noteType);
|
||||
const isHelpPage = note.noteId.startsWith("_help");
|
||||
const [syncServerHost] = useTriliumOption("syncServerHost");
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { useMemo } from "preact/hooks";
|
||||
import { marked } from "marked";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import type { Citation } from "../../../services/llm_chat.js";
|
||||
import "./LlmChat.css";
|
||||
|
||||
// Configure marked for safe rendering
|
||||
marked.setOptions({
|
||||
breaks: true, // Convert \n to <br>
|
||||
gfm: true // GitHub Flavored Markdown
|
||||
});
|
||||
|
||||
interface StoredMessage {
|
||||
id: string;
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
createdAt: string;
|
||||
citations?: Citation[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
message: StoredMessage;
|
||||
isStreaming?: boolean;
|
||||
}
|
||||
|
||||
export default function ChatMessage({ message, isStreaming }: Props) {
|
||||
const roleLabel = message.role === "user" ? "You" : "Assistant";
|
||||
|
||||
// Only render markdown for assistant messages
|
||||
const renderedContent = useMemo(() => {
|
||||
if (message.role === "assistant") {
|
||||
return marked.parse(message.content) as string;
|
||||
}
|
||||
return null;
|
||||
}, [message.content, message.role]);
|
||||
|
||||
return (
|
||||
<div className={`llm-chat-message llm-chat-message-${message.role}`}>
|
||||
<div className="llm-chat-message-role">
|
||||
{roleLabel}
|
||||
</div>
|
||||
<div className="llm-chat-message-content">
|
||||
{message.role === "assistant" ? (
|
||||
<>
|
||||
<div
|
||||
className="llm-chat-markdown"
|
||||
dangerouslySetInnerHTML={{ __html: renderedContent || "" }}
|
||||
/>
|
||||
{isStreaming && <span className="llm-chat-cursor" />}
|
||||
</>
|
||||
) : (
|
||||
message.content
|
||||
)}
|
||||
</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) => (
|
||||
<li key={idx}>
|
||||
<a
|
||||
href={citation.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={citation.url}
|
||||
>
|
||||
{citation.title || new URL(citation.url).hostname}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
367
apps/client/src/widgets/type_widgets/llm_chat/LlmChat.css
Normal file
367
apps/client/src/widgets/type_widgets/llm_chat/LlmChat.css
Normal file
@@ -0,0 +1,367 @@
|
||||
.llm-chat-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.llm-chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.llm-chat-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--muted-text-color);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.llm-chat-message {
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
max-width: 85%;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.llm-chat-message-user {
|
||||
background: var(--accented-background-color);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.llm-chat-message-assistant {
|
||||
background: var(--main-background-color);
|
||||
border: 1px solid var(--main-border-color);
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.llm-chat-message-role {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.llm-chat-message-content {
|
||||
word-wrap: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Preserve whitespace only for user messages (plain text) */
|
||||
.llm-chat-message-user .llm-chat-message-content {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.llm-chat-cursor {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 1.1em;
|
||||
background: currentColor;
|
||||
margin-left: 2px;
|
||||
vertical-align: text-bottom;
|
||||
animation: llm-chat-blink 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes llm-chat-blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* Tool activity indicator */
|
||||
.llm-chat-tool-activity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 8px;
|
||||
background: var(--accented-background-color);
|
||||
color: var(--muted-text-color);
|
||||
font-size: 0.9rem;
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
.llm-chat-tool-spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--muted-text-color);
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: llm-chat-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes llm-chat-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Citations */
|
||||
.llm-chat-citations {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
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-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 {
|
||||
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 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Error */
|
||||
.llm-chat-error {
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 8px;
|
||||
background: var(--danger-background-color, #fee);
|
||||
border: 1px solid var(--danger-border-color, #fcc);
|
||||
color: var(--danger-text-color, #c00);
|
||||
}
|
||||
|
||||
/* Input form */
|
||||
.llm-chat-input-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.llm-chat-input-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.llm-chat-input {
|
||||
flex: 1;
|
||||
min-height: 60px;
|
||||
max-height: 200px;
|
||||
resize: vertical;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 8px;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
background: var(--main-background-color);
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.llm-chat-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--main-selection-color);
|
||||
box-shadow: 0 0 0 2px var(--main-selection-color-soft, rgba(0, 123, 255, 0.25));
|
||||
}
|
||||
|
||||
.llm-chat-input:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.llm-chat-send-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: var(--button-background-color);
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
color: var(--button-text-color);
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.llm-chat-send-btn:hover:not(:disabled) {
|
||||
background: var(--button-hover-background-color, var(--button-background-color));
|
||||
}
|
||||
|
||||
.llm-chat-send-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Options row */
|
||||
.llm-chat-options {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
.llm-chat-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted-text-color);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.llm-chat-toggle input[type="checkbox"] {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.llm-chat-toggle .bx {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.llm-chat-toggle:has(input:checked) {
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.llm-chat-toggle:has(input:disabled) {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Markdown styles */
|
||||
.llm-chat-markdown {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.llm-chat-markdown p {
|
||||
margin: 0 0 0.75em 0;
|
||||
}
|
||||
|
||||
.llm-chat-markdown p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.llm-chat-markdown h1,
|
||||
.llm-chat-markdown h2,
|
||||
.llm-chat-markdown h3,
|
||||
.llm-chat-markdown h4,
|
||||
.llm-chat-markdown h5,
|
||||
.llm-chat-markdown h6 {
|
||||
margin: 1em 0 0.5em 0;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.llm-chat-markdown h1:first-child,
|
||||
.llm-chat-markdown h2:first-child,
|
||||
.llm-chat-markdown h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.llm-chat-markdown h1 { font-size: 1.4em; }
|
||||
.llm-chat-markdown h2 { font-size: 1.25em; }
|
||||
.llm-chat-markdown h3 { font-size: 1.1em; }
|
||||
|
||||
.llm-chat-markdown ul,
|
||||
.llm-chat-markdown ol {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.llm-chat-markdown li {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
.llm-chat-markdown code {
|
||||
background: var(--accented-background-color);
|
||||
padding: 0.15em 0.4em;
|
||||
border-radius: 4px;
|
||||
font-family: var(--monospace-font-family, monospace);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.llm-chat-markdown pre {
|
||||
background: var(--accented-background-color);
|
||||
padding: 0.75em 1em;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
|
||||
.llm-chat-markdown pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.llm-chat-markdown blockquote {
|
||||
margin: 0.75em 0;
|
||||
padding: 0.5em 1em;
|
||||
border-left: 3px solid var(--main-border-color);
|
||||
background: var(--accented-background-color);
|
||||
}
|
||||
|
||||
.llm-chat-markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.llm-chat-markdown a {
|
||||
color: var(--link-color, #007bff);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.llm-chat-markdown a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.llm-chat-markdown hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.llm-chat-markdown table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
|
||||
.llm-chat-markdown th,
|
||||
.llm-chat-markdown td {
|
||||
border: 1px solid var(--main-border-color);
|
||||
padding: 0.5em 0.75em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.llm-chat-markdown th {
|
||||
background: var(--accented-background-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.llm-chat-markdown strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.llm-chat-markdown em {
|
||||
font-style: italic;
|
||||
}
|
||||
249
apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx
Normal file
249
apps/client/src/widgets/type_widgets/llm_chat/LlmChat.tsx
Normal file
@@ -0,0 +1,249 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import { streamChatCompletion, type ChatMessage as ChatMessageData, type Citation } from "../../../services/llm_chat.js";
|
||||
import { useEditorSpacedUpdate } from "../../react/hooks.js";
|
||||
import { TypeWidgetProps } from "../type_widget.js";
|
||||
import ChatMessage from "./ChatMessage.js";
|
||||
import "./LlmChat.css";
|
||||
|
||||
interface StoredMessage {
|
||||
id: string;
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
createdAt: string;
|
||||
citations?: Citation[];
|
||||
}
|
||||
|
||||
interface LlmChatContent {
|
||||
version: 1;
|
||||
messages: StoredMessage[];
|
||||
enableWebSearch?: boolean;
|
||||
}
|
||||
|
||||
export default function LlmChat({ note, ntxId, noteContext }: TypeWidgetProps) {
|
||||
const [messages, setMessages] = useState<StoredMessage[]>([]);
|
||||
const [input, setInput] = useState("");
|
||||
const [isStreaming, setIsStreaming] = useState(false);
|
||||
const [streamingContent, setStreamingContent] = useState("");
|
||||
const [toolActivity, setToolActivity] = useState<string | null>(null);
|
||||
const [pendingCitations, setPendingCitations] = useState<Citation[]>([]);
|
||||
const [enableWebSearch, setEnableWebSearch] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [shouldSave, setShouldSave] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const scrollToBottom = useCallback(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages, streamingContent, toolActivity, scrollToBottom]);
|
||||
|
||||
// Use a ref to store the latest messages for getData
|
||||
const messagesRef = useRef(messages);
|
||||
messagesRef.current = messages;
|
||||
|
||||
const enableWebSearchRef = useRef(enableWebSearch);
|
||||
enableWebSearchRef.current = enableWebSearch;
|
||||
|
||||
const spacedUpdate = useEditorSpacedUpdate({
|
||||
note,
|
||||
noteType: "llmChat",
|
||||
noteContext,
|
||||
getData: () => {
|
||||
// Use refs to get the latest values, avoiding stale closure issues
|
||||
const content: LlmChatContent = {
|
||||
version: 1,
|
||||
messages: messagesRef.current,
|
||||
enableWebSearch: enableWebSearchRef.current
|
||||
};
|
||||
return { content: JSON.stringify(content) };
|
||||
},
|
||||
onContentChange: (content) => {
|
||||
if (!content) {
|
||||
setMessages([]);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed: LlmChatContent = JSON.parse(content);
|
||||
setMessages(parsed.messages || []);
|
||||
if (typeof parsed.enableWebSearch === "boolean") {
|
||||
setEnableWebSearch(parsed.enableWebSearch);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to parse LLM chat content:", e);
|
||||
setMessages([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger save after state updates when shouldSave is set
|
||||
useEffect(() => {
|
||||
if (shouldSave) {
|
||||
setShouldSave(false);
|
||||
spacedUpdate.scheduleUpdate();
|
||||
}
|
||||
}, [shouldSave, spacedUpdate]);
|
||||
|
||||
const handleSubmit = useCallback(async (e: Event) => {
|
||||
e.preventDefault();
|
||||
if (!input.trim() || isStreaming) return;
|
||||
|
||||
setError(null);
|
||||
setToolActivity(null);
|
||||
setPendingCitations([]);
|
||||
|
||||
const userMessage: StoredMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
role: "user",
|
||||
content: input.trim(),
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
const newMessages = [...messages, userMessage];
|
||||
setMessages(newMessages);
|
||||
setInput("");
|
||||
setIsStreaming(true);
|
||||
setStreamingContent("");
|
||||
|
||||
let assistantContent = "";
|
||||
const citations: Citation[] = [];
|
||||
|
||||
const apiMessages: ChatMessageData[] = newMessages.map(m => ({
|
||||
role: m.role,
|
||||
content: m.content
|
||||
}));
|
||||
|
||||
await streamChatCompletion(
|
||||
apiMessages,
|
||||
{ enableWebSearch },
|
||||
{
|
||||
onChunk: (text) => {
|
||||
assistantContent += text;
|
||||
setStreamingContent(assistantContent);
|
||||
setToolActivity(null); // Clear tool activity when text starts
|
||||
},
|
||||
onToolUse: (toolName, _input) => {
|
||||
const toolLabel = toolName === "web_search"
|
||||
? t("llm_chat.searching_web")
|
||||
: `Using ${toolName}...`;
|
||||
setToolActivity(toolLabel);
|
||||
},
|
||||
onCitation: (citation) => {
|
||||
citations.push(citation);
|
||||
setPendingCitations([...citations]);
|
||||
},
|
||||
onError: (errorMsg) => {
|
||||
console.error("Chat error:", errorMsg);
|
||||
setError(errorMsg);
|
||||
setIsStreaming(false);
|
||||
setToolActivity(null);
|
||||
},
|
||||
onDone: () => {
|
||||
if (assistantContent) {
|
||||
const assistantMessage: StoredMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
role: "assistant",
|
||||
content: assistantContent,
|
||||
createdAt: new Date().toISOString(),
|
||||
citations: citations.length > 0 ? citations : undefined
|
||||
};
|
||||
setMessages(prev => [...prev, assistantMessage]);
|
||||
}
|
||||
setStreamingContent("");
|
||||
setPendingCitations([]);
|
||||
setIsStreaming(false);
|
||||
setToolActivity(null);
|
||||
// Trigger save after state updates via useEffect
|
||||
setShouldSave(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
}, [input, isStreaming, messages, enableWebSearch, spacedUpdate]);
|
||||
|
||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSubmit(e);
|
||||
}
|
||||
}, [handleSubmit]);
|
||||
|
||||
const toggleWebSearch = useCallback(() => {
|
||||
setEnableWebSearch(prev => !prev);
|
||||
setShouldSave(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="llm-chat-container">
|
||||
<div className="llm-chat-messages">
|
||||
{messages.length === 0 && !isStreaming && (
|
||||
<div className="llm-chat-empty">
|
||||
{t("llm_chat.empty_state")}
|
||||
</div>
|
||||
)}
|
||||
{messages.map(msg => (
|
||||
<ChatMessage key={msg.id} message={msg} />
|
||||
))}
|
||||
{toolActivity && (
|
||||
<div className="llm-chat-tool-activity">
|
||||
<span className="llm-chat-tool-spinner" />
|
||||
{toolActivity}
|
||||
</div>
|
||||
)}
|
||||
{isStreaming && streamingContent && (
|
||||
<ChatMessage
|
||||
message={{
|
||||
id: "streaming",
|
||||
role: "assistant",
|
||||
content: streamingContent,
|
||||
createdAt: new Date().toISOString(),
|
||||
citations: pendingCitations.length > 0 ? pendingCitations : undefined
|
||||
}}
|
||||
isStreaming
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
<div className="llm-chat-error">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
<form className="llm-chat-input-form" onSubmit={handleSubmit}>
|
||||
<div className="llm-chat-input-row">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className="llm-chat-input"
|
||||
value={input}
|
||||
onInput={(e) => setInput((e.target as HTMLTextAreaElement).value)}
|
||||
placeholder={t("llm_chat.placeholder")}
|
||||
disabled={isStreaming}
|
||||
onKeyDown={handleKeyDown}
|
||||
rows={3}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="llm-chat-send-btn"
|
||||
disabled={isStreaming || !input.trim()}
|
||||
>
|
||||
{isStreaming ? t("llm_chat.sending") : t("llm_chat.send")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="llm-chat-options">
|
||||
<label className="llm-chat-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enableWebSearch}
|
||||
onChange={toggleWebSearch}
|
||||
disabled={isStreaming}
|
||||
/>
|
||||
<span className="bx bx-globe" />
|
||||
{t("llm_chat.web_search")}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
"proxy-nginx-subdir": "docker run --name trilium-nginx-subdir --rm --network=host -v ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro nginx:latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"better-sqlite3": "12.8.0",
|
||||
"html-to-text": "9.0.5",
|
||||
"node-html-parser": "7.1.0",
|
||||
|
||||
@@ -55,7 +55,16 @@ export default async function buildApp() {
|
||||
});
|
||||
|
||||
if (!utils.isElectron) {
|
||||
app.use(compression()); // HTTP compression
|
||||
app.use(compression({
|
||||
// Skip compression for SSE endpoints to enable real-time streaming
|
||||
filter: (req, res) => {
|
||||
// Skip compression for LLM chat streaming endpoint
|
||||
if (req.path === "/api/llm-chat/stream") {
|
||||
return false;
|
||||
}
|
||||
return compression.filter(req, res);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
let resourcePolicy = config["Network"]["corsResourcePolicy"] as 'same-origin' | 'same-site' | 'cross-origin' | undefined;
|
||||
|
||||
62
apps/server/src/routes/api/llm_chat.ts
Normal file
62
apps/server/src/routes/api/llm_chat.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { Request, Response } from "express";
|
||||
import { getProvider, type LlmMessage, type LlmProviderConfig } from "../../services/llm/index.js";
|
||||
|
||||
interface ChatRequest {
|
||||
messages: LlmMessage[];
|
||||
config?: LlmProviderConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* SSE endpoint for streaming chat completions.
|
||||
*
|
||||
* Response format (Server-Sent Events):
|
||||
* data: {"type":"text","content":"Hello"}
|
||||
* data: {"type":"text","content":" world"}
|
||||
* data: {"type":"done"}
|
||||
*
|
||||
* On error:
|
||||
* data: {"type":"error","error":"Error message"}
|
||||
*/
|
||||
async function streamChat(req: Request, res: Response) {
|
||||
const { messages, config = {} } = req.body as ChatRequest;
|
||||
|
||||
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
||||
res.status(400).json({ error: "messages array is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up SSE headers - disable compression and buffering for real-time streaming
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
res.setHeader("Cache-Control", "no-cache, no-transform");
|
||||
res.setHeader("Connection", "keep-alive");
|
||||
res.setHeader("X-Accel-Buffering", "no"); // Disable nginx buffering
|
||||
res.setHeader("Content-Encoding", "none"); // Disable compression
|
||||
res.flushHeaders();
|
||||
|
||||
// Mark response as handled to prevent double-handling by apiResultHandler
|
||||
(res as any).triliumResponseHandled = true;
|
||||
|
||||
// Type assertion for flush method (available when compression is used)
|
||||
const flushableRes = res as Response & { flush?: () => void };
|
||||
|
||||
try {
|
||||
const provider = getProvider(config.provider || "anthropic");
|
||||
|
||||
for await (const chunk of provider.streamCompletion(messages, config)) {
|
||||
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
||||
// Flush immediately to ensure real-time streaming
|
||||
if (typeof flushableRes.flush === "function") {
|
||||
flushableRes.flush();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
res.write(`data: ${JSON.stringify({ type: "error", error: errorMessage })}\n\n`);
|
||||
} finally {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
streamChat
|
||||
};
|
||||
@@ -34,6 +34,7 @@ import fontsRoute from "./api/fonts.js";
|
||||
import imageRoute from "./api/image.js";
|
||||
import importRoute from "./api/import.js";
|
||||
import keysRoute from "./api/keys.js";
|
||||
import llmChatRoute from "./api/llm_chat.js";
|
||||
import loginApiRoute from "./api/login.js";
|
||||
import metricsRoute from "./api/metrics.js";
|
||||
import noteMapRoute from "./api/note_map.js";
|
||||
@@ -323,6 +324,9 @@ function register(app: express.Application) {
|
||||
apiRoute(PST, "/api/script/bundle/:noteId", scriptRoute.getBundle);
|
||||
apiRoute(GET, "/api/script/relation/:noteId/:relationName", scriptRoute.getRelationBundles);
|
||||
|
||||
// LLM chat streaming endpoint (SSE)
|
||||
asyncRoute(PST, "/api/llm-chat/stream", [auth.checkApiAuth, csrfMiddleware], llmChatRoute.streamChat, null);
|
||||
|
||||
// no CSRF since this is called from android app
|
||||
route(PST, "/api/sender/login", [loginRateLimiter], loginApiRoute.token, apiResultHandler);
|
||||
asyncRoute(PST, "/api/sender/image", [auth.checkEtapiToken, uploadMiddlewareWithErrorHandling], senderRoute.uploadImage, apiResultHandler);
|
||||
|
||||
26
apps/server/src/services/llm/index.ts
Normal file
26
apps/server/src/services/llm/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { LlmProvider } from "./types.js";
|
||||
import { AnthropicProvider } from "./providers/anthropic.js";
|
||||
|
||||
const providers: Record<string, () => LlmProvider> = {
|
||||
anthropic: () => new AnthropicProvider()
|
||||
// Future providers can be added here
|
||||
};
|
||||
|
||||
let cachedProviders: Record<string, LlmProvider> = {};
|
||||
|
||||
export function getProvider(name: string = "anthropic"): LlmProvider {
|
||||
if (!cachedProviders[name]) {
|
||||
const factory = providers[name];
|
||||
if (!factory) {
|
||||
throw new Error(`Unknown LLM provider: ${name}. Available: ${Object.keys(providers).join(", ")}`);
|
||||
}
|
||||
cachedProviders[name] = factory();
|
||||
}
|
||||
return cachedProviders[name];
|
||||
}
|
||||
|
||||
export function clearProviderCache(): void {
|
||||
cachedProviders = {};
|
||||
}
|
||||
|
||||
export * from "./types.js";
|
||||
110
apps/server/src/services/llm/providers/anthropic.ts
Normal file
110
apps/server/src/services/llm/providers/anthropic.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import type { LlmProvider, LlmMessage, LlmStreamChunk, LlmProviderConfig } from "../types.js";
|
||||
|
||||
const DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
||||
const DEFAULT_MAX_TOKENS = 8096;
|
||||
|
||||
export class AnthropicProvider implements LlmProvider {
|
||||
name = "anthropic";
|
||||
private client: Anthropic;
|
||||
|
||||
constructor() {
|
||||
const apiKey = process.env.ANTHROPIC_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error("ANTHROPIC_API_KEY environment variable is required");
|
||||
}
|
||||
this.client = new Anthropic({ apiKey });
|
||||
}
|
||||
|
||||
async *streamCompletion(
|
||||
messages: LlmMessage[],
|
||||
config: LlmProviderConfig
|
||||
): AsyncIterable<LlmStreamChunk> {
|
||||
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[] = [];
|
||||
if (config.enableWebSearch) {
|
||||
tools.push({
|
||||
type: "web_search_20250305",
|
||||
name: "web_search",
|
||||
max_uses: 5 // Limit searches per request
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
system: systemPrompt,
|
||||
messages: chatMessages.map(m => ({
|
||||
role: m.role as "user" | "assistant",
|
||||
content: m.content
|
||||
}))
|
||||
};
|
||||
|
||||
if (tools.length > 0) {
|
||||
(streamParams as any).tools = tools;
|
||||
}
|
||||
|
||||
const stream = this.client.messages.stream(streamParams);
|
||||
|
||||
for await (const event of stream) {
|
||||
// Handle different event types
|
||||
if (event.type === "content_block_start") {
|
||||
const block = event.content_block;
|
||||
if (block.type === "tool_use") {
|
||||
yield {
|
||||
type: "tool_use",
|
||||
toolName: block.name,
|
||||
toolInput: {} // Input comes in deltas
|
||||
};
|
||||
}
|
||||
} else if (event.type === "content_block_delta") {
|
||||
const delta = event.delta;
|
||||
if (delta.type === "text_delta") {
|
||||
yield { type: "text", content: delta.text };
|
||||
} 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
|
||||
}
|
||||
} else if (event.type === "content_block_stop") {
|
||||
// Content block finished
|
||||
// For server-side tools, results come in subsequent blocks
|
||||
}
|
||||
|
||||
// Handle server-side tool results (for web_search)
|
||||
// These appear as special content blocks in the response
|
||||
if (event.type === "message_delta") {
|
||||
// Check for citations in stop_reason or other metadata
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield { type: "done" };
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown error";
|
||||
yield { type: "error", error: message };
|
||||
}
|
||||
}
|
||||
}
|
||||
43
apps/server/src/services/llm/types.ts
Normal file
43
apps/server/src/services/llm/types.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* LLM Provider types for chat integration.
|
||||
* Provider-agnostic interfaces to support multiple LLM backends.
|
||||
*/
|
||||
|
||||
export interface LlmMessage {
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream chunk types for real-time updates.
|
||||
*/
|
||||
export type LlmStreamChunk =
|
||||
| { type: "text"; 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: "error"; error: string }
|
||||
| { type: "done" };
|
||||
|
||||
export interface LlmProviderConfig {
|
||||
provider?: string;
|
||||
model?: string;
|
||||
maxTokens?: number;
|
||||
temperature?: number;
|
||||
systemPrompt?: string;
|
||||
/** Enable web search tool */
|
||||
enableWebSearch?: boolean;
|
||||
}
|
||||
|
||||
export interface LlmProvider {
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Stream a chat completion response.
|
||||
* Yields chunks as they arrive from the LLM.
|
||||
*/
|
||||
streamCompletion(
|
||||
messages: LlmMessage[],
|
||||
config: LlmProviderConfig
|
||||
): AsyncIterable<LlmStreamChunk>;
|
||||
}
|
||||
@@ -15,7 +15,8 @@ const noteTypes = [
|
||||
{ type: "doc", defaultMime: "" },
|
||||
{ type: "contentWidget", defaultMime: "" },
|
||||
{ type: "mindMap", defaultMime: "application/json" },
|
||||
{ type: "spreadsheet", defaultMime: "application/json" }
|
||||
{ type: "spreadsheet", defaultMime: "application/json" },
|
||||
{ type: "llmChat", defaultMime: "application/json" }
|
||||
];
|
||||
|
||||
function getDefaultMimeForNoteType(typeName: string) {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.3.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.3.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.3.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.3.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"ckeditor5-metadata.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.3.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "55.2.0",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
|
||||
@@ -21,7 +21,8 @@ export const NOTE_TYPE_ICONS = {
|
||||
doc: "bx bxs-file-doc",
|
||||
contentWidget: "bx bxs-widget",
|
||||
mindMap: "bx bx-sitemap",
|
||||
spreadsheet: "bx bx-table"
|
||||
spreadsheet: "bx bx-table",
|
||||
llmChat: "bx bx-message-square-dots"
|
||||
};
|
||||
|
||||
const FILE_MIME_MAPPINGS = {
|
||||
|
||||
@@ -122,7 +122,8 @@ export const ALLOWED_NOTE_TYPES = [
|
||||
"webView",
|
||||
"code",
|
||||
"mindMap",
|
||||
"spreadsheet"
|
||||
"spreadsheet",
|
||||
"llmChat"
|
||||
] as const;
|
||||
export type NoteType = (typeof ALLOWED_NOTE_TYPES)[number];
|
||||
|
||||
|
||||
140
pnpm-lock.yaml
generated
140
pnpm-lock.yaml
generated
@@ -267,8 +267,8 @@ importers:
|
||||
specifier: 0.18.0
|
||||
version: 0.18.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
|
||||
'@zumer/snapdom':
|
||||
specifier: 2.7.0
|
||||
version: 2.7.0
|
||||
specifier: 2.6.0
|
||||
version: 2.6.0
|
||||
autocomplete.js:
|
||||
specifier: 0.38.1
|
||||
version: 0.38.1
|
||||
@@ -552,6 +552,9 @@ importers:
|
||||
|
||||
apps/server:
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk':
|
||||
specifier: ^0.39.0
|
||||
version: 0.39.0(encoding@0.1.13)
|
||||
better-sqlite3:
|
||||
specifier: 12.8.0
|
||||
version: 12.8.0
|
||||
@@ -947,8 +950,8 @@ importers:
|
||||
packages/ckeditor5-admonition:
|
||||
devDependencies:
|
||||
'@ckeditor/ckeditor5-dev-build-tools':
|
||||
specifier: 55.3.0
|
||||
version: 55.3.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
specifier: 55.2.0
|
||||
version: 55.2.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
'@ckeditor/ckeditor5-inspector':
|
||||
specifier: '>=4.1.0'
|
||||
version: 5.0.0
|
||||
@@ -1007,8 +1010,8 @@ importers:
|
||||
packages/ckeditor5-footnotes:
|
||||
devDependencies:
|
||||
'@ckeditor/ckeditor5-dev-build-tools':
|
||||
specifier: 55.3.0
|
||||
version: 55.3.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
specifier: 55.2.0
|
||||
version: 55.2.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
'@ckeditor/ckeditor5-inspector':
|
||||
specifier: '>=4.1.0'
|
||||
version: 5.0.0
|
||||
@@ -1067,8 +1070,8 @@ importers:
|
||||
packages/ckeditor5-keyboard-marker:
|
||||
devDependencies:
|
||||
'@ckeditor/ckeditor5-dev-build-tools':
|
||||
specifier: 55.3.0
|
||||
version: 55.3.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
specifier: 55.2.0
|
||||
version: 55.2.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
'@ckeditor/ckeditor5-inspector':
|
||||
specifier: '>=4.1.0'
|
||||
version: 5.0.0
|
||||
@@ -1134,8 +1137,8 @@ importers:
|
||||
version: 0.109.0
|
||||
devDependencies:
|
||||
'@ckeditor/ckeditor5-dev-build-tools':
|
||||
specifier: 55.3.0
|
||||
version: 55.3.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
specifier: 55.2.0
|
||||
version: 55.2.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
'@ckeditor/ckeditor5-inspector':
|
||||
specifier: '>=4.1.0'
|
||||
version: 5.0.0
|
||||
@@ -1201,8 +1204,8 @@ importers:
|
||||
version: 4.17.23
|
||||
devDependencies:
|
||||
'@ckeditor/ckeditor5-dev-build-tools':
|
||||
specifier: 55.3.0
|
||||
version: 55.3.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
specifier: 55.2.0
|
||||
version: 55.2.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)
|
||||
'@ckeditor/ckeditor5-inspector':
|
||||
specifier: '>=4.1.0'
|
||||
version: 5.0.0
|
||||
@@ -1543,6 +1546,9 @@ packages:
|
||||
'@antfu/install-pkg@1.1.0':
|
||||
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
|
||||
|
||||
'@anthropic-ai/sdk@0.39.0':
|
||||
resolution: {integrity: sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==}
|
||||
|
||||
'@apidevtools/json-schema-ref-parser@9.1.2':
|
||||
resolution: {integrity: sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==}
|
||||
|
||||
@@ -1972,8 +1978,8 @@ packages:
|
||||
'@ckeditor/ckeditor5-core@47.6.1':
|
||||
resolution: {integrity: sha512-6dtnquhjymLkNhdC9T6gk/Mf2bDnHSTZrhkByaXC96CbmQDriCgfcaAVY6pQgDNxBQ6fZrev0TnKBLfTItrMsg==}
|
||||
|
||||
'@ckeditor/ckeditor5-dev-build-tools@55.3.0':
|
||||
resolution: {integrity: sha512-87WlVerNpgc0xlnnPTKX+1Z/LrTWeueaOQK/XWns/AKJDoGbwUyQo6rhlRsEvDGKdKXOdHXgQijxgh9Yo1I9KQ==}
|
||||
'@ckeditor/ckeditor5-dev-build-tools@55.2.0':
|
||||
resolution: {integrity: sha512-pUa3GqCOEb7m5xhbUPV6gKLIgsX/TI3MXT51u0wa+A822ZFVbaXoGd2LissPkuK9WcGfmgU1gT8TzcyFTCTYig==}
|
||||
engines: {node: '>=24.11.0', npm: '>=5.7.1'}
|
||||
hasBin: true
|
||||
|
||||
@@ -6173,12 +6179,18 @@ packages:
|
||||
'@types/mute-stream@0.0.4':
|
||||
resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==}
|
||||
|
||||
'@types/node-fetch@2.6.13':
|
||||
resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==}
|
||||
|
||||
'@types/node-forge@1.3.14':
|
||||
resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==}
|
||||
|
||||
'@types/node@16.9.1':
|
||||
resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==}
|
||||
|
||||
'@types/node@18.19.130':
|
||||
resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==}
|
||||
|
||||
'@types/node@20.19.25':
|
||||
resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==}
|
||||
|
||||
@@ -7207,8 +7219,8 @@ packages:
|
||||
resolution: {integrity: sha512-0fztsk/0ryJ+2PPr9EyXS5/Co7OK8q3zY/xOoozEWaUsL5x+C0cyZ4YyMuUffOO2Dx/rAdq4JMPqW0VUtm+vzA==}
|
||||
engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'}
|
||||
|
||||
'@zumer/snapdom@2.7.0':
|
||||
resolution: {integrity: sha512-ZiELKzDszeFOazPQ/ExXzgtdoW9jADVjDjInr5XDAlVdCx0RbNsFiG7RLyM48XnA7EyCA9yTvmXSc3ElDrTRqA==}
|
||||
'@zumer/snapdom@2.6.0':
|
||||
resolution: {integrity: sha512-JpPPkuMzozRVX6KArgCiMgLpgVW82kWgyoFk5DWGKE5msWGEshXEUdQHLLEyZRO7qioI1pI+yaBJz81tEP9gPg==}
|
||||
|
||||
abab@2.0.6:
|
||||
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
||||
@@ -9729,6 +9741,9 @@ packages:
|
||||
foreach@2.0.6:
|
||||
resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==}
|
||||
|
||||
form-data-encoder@1.7.2:
|
||||
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
|
||||
|
||||
form-data-encoder@4.1.0:
|
||||
resolution: {integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -9741,6 +9756,10 @@ packages:
|
||||
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
|
||||
engines: {node: '>=0.4.x'}
|
||||
|
||||
formdata-node@4.4.1:
|
||||
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
|
||||
engines: {node: '>= 12.20'}
|
||||
|
||||
formdata-node@6.0.3:
|
||||
resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -15025,6 +15044,9 @@ packages:
|
||||
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
@@ -15530,6 +15552,10 @@ packages:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
web-streams-polyfill@4.0.0-beta.3:
|
||||
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
webdriver@9.27.0:
|
||||
resolution: {integrity: sha512-w07ThZND48SIr0b4S7eFougYUyclmoUwdmju8yXvEJiXYjDjeYUpl8wZrYPEYRBylxpSx+sBHfEUBrPQkcTTRQ==}
|
||||
engines: {node: '>=18.20.0'}
|
||||
@@ -15996,6 +16022,18 @@ snapshots:
|
||||
package-manager-detector: 1.3.0
|
||||
tinyexec: 1.0.4
|
||||
|
||||
'@anthropic-ai/sdk@0.39.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@types/node': 18.19.130
|
||||
'@types/node-fetch': 2.6.13
|
||||
abort-controller: 3.0.0
|
||||
agentkeepalive: 4.6.0
|
||||
form-data-encoder: 1.7.2
|
||||
formdata-node: 4.4.1
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@apidevtools/json-schema-ref-parser@9.1.2':
|
||||
dependencies:
|
||||
'@jsdevtools/ono': 7.1.3
|
||||
@@ -16781,6 +16819,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.6.1
|
||||
'@ckeditor/ckeditor5-upload': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-ai@47.6.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
|
||||
dependencies:
|
||||
@@ -16922,12 +16962,16 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-cloud-services@47.6.1':
|
||||
dependencies:
|
||||
'@ckeditor/ckeditor5-core': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-code-block@47.6.1(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||
dependencies:
|
||||
@@ -16996,7 +17040,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-dev-build-tools@55.3.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)':
|
||||
'@ckeditor/ckeditor5-dev-build-tools@55.2.0(@swc/helpers@0.5.17)(postcss@8.5.8)(tslib@2.8.1)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@rollup/plugin-commonjs': 28.0.9(rollup@4.52.0)
|
||||
'@rollup/plugin-json': 6.1.0(rollup@4.52.0)
|
||||
@@ -17125,6 +17169,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-editor-decoupled@47.6.1':
|
||||
dependencies:
|
||||
@@ -17134,6 +17180,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-editor-inline@47.6.1':
|
||||
dependencies:
|
||||
@@ -17143,6 +17191,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-editor-multi-root@47.6.1':
|
||||
dependencies:
|
||||
@@ -17190,8 +17240,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.6.1
|
||||
'@ckeditor/ckeditor5-engine': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-essentials@47.6.1':
|
||||
dependencies:
|
||||
@@ -17223,8 +17271,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-export-word@47.6.1':
|
||||
dependencies:
|
||||
@@ -17249,6 +17295,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-font@47.6.1':
|
||||
dependencies:
|
||||
@@ -17324,6 +17372,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-html-embed@47.6.1':
|
||||
dependencies:
|
||||
@@ -17350,6 +17400,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-icons@47.6.1': {}
|
||||
|
||||
@@ -17381,8 +17433,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-indent@47.6.1':
|
||||
dependencies:
|
||||
@@ -17508,8 +17558,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-merge-fields@47.6.1':
|
||||
dependencies:
|
||||
@@ -17522,8 +17570,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-minimap@47.6.1':
|
||||
dependencies:
|
||||
@@ -17532,8 +17578,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-operations-compressor@47.6.1':
|
||||
dependencies:
|
||||
@@ -17586,8 +17630,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-pagination@47.6.1':
|
||||
dependencies:
|
||||
@@ -17695,8 +17737,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-slash-command@47.6.1':
|
||||
dependencies:
|
||||
@@ -17709,8 +17749,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-source-editing-enhanced@47.6.1':
|
||||
dependencies:
|
||||
@@ -17758,8 +17796,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-table@47.6.1':
|
||||
dependencies:
|
||||
@@ -17772,8 +17808,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-template@47.6.1':
|
||||
dependencies:
|
||||
@@ -17883,8 +17917,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-engine': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-widget@47.6.1':
|
||||
dependencies:
|
||||
@@ -17904,8 +17936,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@codemirror/autocomplete@6.18.6':
|
||||
dependencies:
|
||||
@@ -20234,7 +20264,7 @@ snapshots:
|
||||
detect-libc: 2.1.2
|
||||
is-glob: 4.0.3
|
||||
node-addon-api: 7.1.1
|
||||
picomatch: 4.0.3
|
||||
picomatch: 4.0.4
|
||||
optionalDependencies:
|
||||
'@parcel/watcher-android-arm64': 2.5.6
|
||||
'@parcel/watcher-darwin-arm64': 2.5.6
|
||||
@@ -22272,12 +22302,21 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 24.12.0
|
||||
|
||||
'@types/node-fetch@2.6.13':
|
||||
dependencies:
|
||||
'@types/node': 24.12.0
|
||||
form-data: 4.0.5
|
||||
|
||||
'@types/node-forge@1.3.14':
|
||||
dependencies:
|
||||
'@types/node': 24.12.0
|
||||
|
||||
'@types/node@16.9.1': {}
|
||||
|
||||
'@types/node@18.19.130':
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
'@types/node@20.19.25':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -24376,7 +24415,7 @@ snapshots:
|
||||
|
||||
'@zip.js/zip.js@2.8.11': {}
|
||||
|
||||
'@zumer/snapdom@2.7.0': {}
|
||||
'@zumer/snapdom@2.6.0': {}
|
||||
|
||||
abab@2.0.6: {}
|
||||
|
||||
@@ -27603,6 +27642,8 @@ snapshots:
|
||||
|
||||
foreach@2.0.6: {}
|
||||
|
||||
form-data-encoder@1.7.2: {}
|
||||
|
||||
form-data-encoder@4.1.0: {}
|
||||
|
||||
form-data@4.0.5:
|
||||
@@ -27615,6 +27656,11 @@ snapshots:
|
||||
|
||||
format@0.2.2: {}
|
||||
|
||||
formdata-node@4.4.1:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 4.0.0-beta.3
|
||||
|
||||
formdata-node@6.0.3: {}
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
@@ -33861,6 +33907,8 @@ snapshots:
|
||||
has-symbols: 1.1.0
|
||||
which-boxed-primitive: 1.1.1
|
||||
|
||||
undici-types@5.26.5: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
@@ -34375,6 +34423,8 @@ snapshots:
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
web-streams-polyfill@4.0.0-beta.3: {}
|
||||
|
||||
webdriver@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5):
|
||||
dependencies:
|
||||
'@types/node': 20.19.25
|
||||
|
||||
Reference in New Issue
Block a user