diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 796e4c08fa..9fbf88bc26 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -310,7 +310,12 @@ "description_placeholder": "Add a description (optional)", "revision_saved": "Note revision has been saved.", "edit_description": "Edit description", - "description_updated": "Revision description has been updated." + "description_updated": "Revision description has been updated.", + "source_auto": "Auto", + "source_manual": "Manual", + "source_etapi": "ETAPI", + "source_llm": "LLM", + "source_restore": "Restore" }, "sort_child_notes": { "sort_children_by": "Sort children by...", diff --git a/apps/client/src/widgets/dialogs/revisions.tsx b/apps/client/src/widgets/dialogs/revisions.tsx index 06a44ed859..b3d3803960 100644 --- a/apps/client/src/widgets/dialogs/revisions.tsx +++ b/apps/client/src/widgets/dialogs/revisions.tsx @@ -200,7 +200,23 @@ function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: Re active={currentRevision && item.revisionId === currentRevision.revisionId} >
- {item.dateCreated && item.dateCreated.substr(0, 16)} ({item.contentLength && utils.formatSize(item.contentLength)}) +
+ {item.dateCreated && item.dateCreated.substr(0, 16)} + {item.source && item.source !== "auto" && ( + + {t(`revisions.source_${item.source}`)} + + )} +
+
+ {item.contentLength && utils.formatSize(item.contentLength)} +
{item.description && (
{item.description} diff --git a/apps/server/src/assets/db/schema.sql b/apps/server/src/assets/db/schema.sql index 361a6ccdf4..60f0485471 100644 --- a/apps/server/src/assets/db/schema.sql +++ b/apps/server/src/assets/db/schema.sql @@ -49,6 +49,7 @@ CREATE TABLE IF NOT EXISTS "revisions" (`revisionId` TEXT NOT NULL PRIMARY KEY, mime TEXT DEFAULT '' NOT NULL, `title` TEXT NOT NULL, `description` TEXT DEFAULT '' NOT NULL, + `source` TEXT DEFAULT 'auto' NOT NULL, `isProtected` INT NOT NULL DEFAULT 0, blobId TEXT DEFAULT NULL, `utcDateLastEdited` TEXT NOT NULL, diff --git a/apps/server/src/becca/entities/bnote.ts b/apps/server/src/becca/entities/bnote.ts index 63b447c2b3..56251e76ea 100644 --- a/apps/server/src/becca/entities/bnote.ts +++ b/apps/server/src/becca/entities/bnote.ts @@ -1,4 +1,4 @@ -import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow } from "@triliumnext/commons"; +import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow, RevisionSource } from "@triliumnext/commons"; import { dayjs, getNoteIcon } from "@triliumnext/commons"; import cloningService from "../../services/cloning.js"; @@ -1543,7 +1543,7 @@ class BNote extends AbstractBeccaEntity { return !(this.noteId in this.becca.notes) || this.isBeingDeleted; } - saveRevision(description?: string): BRevision { + saveRevision(opts: { description?: string; source?: RevisionSource } = {}): BRevision { return sql.transactional(() => { let noteContent = this.getContent(); @@ -1552,7 +1552,8 @@ class BNote extends AbstractBeccaEntity { noteId: this.noteId, // title and text should be decrypted now title: this.title, - description: description || "", + description: opts.description || "", + source: opts.source || "auto", type: this.type, mime: this.mime, isProtected: this.isProtected, diff --git a/apps/server/src/becca/entities/brevision.ts b/apps/server/src/becca/entities/brevision.ts index d9bf2e1c72..4e90471cd2 100644 --- a/apps/server/src/becca/entities/brevision.ts +++ b/apps/server/src/becca/entities/brevision.ts @@ -7,7 +7,7 @@ import becca from "../becca.js"; import AbstractBeccaEntity from "./abstract_becca_entity.js"; import sql from "../../services/sql.js"; import BAttachment from "./battachment.js"; -import type { AttachmentRow, NoteType, RevisionPojo, RevisionRow } from "@triliumnext/commons"; +import type { AttachmentRow, NoteType, RevisionPojo, RevisionRow, RevisionSource } from "@triliumnext/commons"; import eraseService from "../../services/erase.js"; interface ContentOpts { @@ -31,7 +31,7 @@ class BRevision extends AbstractBeccaEntity { return "revisionId"; } static get hashedProperties() { - return ["revisionId", "noteId", "title", "description", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; + return ["revisionId", "noteId", "title", "description", "source", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; } revisionId?: string; @@ -40,6 +40,7 @@ class BRevision extends AbstractBeccaEntity { mime!: string; title!: string; description!: string; + source!: RevisionSource; dateLastEdited?: string; utcDateLastEdited?: string; contentLength?: number; @@ -63,6 +64,7 @@ class BRevision extends AbstractBeccaEntity { this.isProtected = !!row.isProtected; this.title = row.title; this.description = row.description || ""; + this.source = row.source || "auto"; this.blobId = row.blobId; this.dateLastEdited = row.dateLastEdited; this.dateCreated = row.dateCreated; @@ -196,6 +198,7 @@ class BRevision extends AbstractBeccaEntity { isProtected: this.isProtected, title: this.title, description: this.description, + source: this.source, blobId: this.blobId, dateLastEdited: this.dateLastEdited, dateCreated: this.dateCreated, diff --git a/apps/server/src/etapi/mappers.ts b/apps/server/src/etapi/mappers.ts index bff50af5c6..04e59b1000 100644 --- a/apps/server/src/etapi/mappers.ts +++ b/apps/server/src/etapi/mappers.ts @@ -74,6 +74,7 @@ function mapRevisionToPojo(revision: BRevision) { isProtected: revision.isProtected, title: revision.title, description: revision.description, + source: revision.source, blobId: revision.blobId, dateLastEdited: revision.dateLastEdited, dateCreated: revision.dateCreated, diff --git a/apps/server/src/etapi/notes.ts b/apps/server/src/etapi/notes.ts index 96206f2dc6..8edc583435 100644 --- a/apps/server/src/etapi/notes.ts +++ b/apps/server/src/etapi/notes.ts @@ -193,7 +193,7 @@ function register(router: Router) { const note = eu.getAndCheckNote(req.params.noteId); const description = req.body?.description || ""; - const revision = note.saveRevision(description); + const revision = note.saveRevision({ description, source: "etapi" }); res.status(201).json(mappers.mapRevisionToPojo(revision)); }); diff --git a/apps/server/src/migrations/migrations.ts b/apps/server/src/migrations/migrations.ts index 93b25be8f1..0c9dd4e8d1 100644 --- a/apps/server/src/migrations/migrations.ts +++ b/apps/server/src/migrations/migrations.ts @@ -11,6 +11,7 @@ const MIGRATIONS: (SqlMigration | JsMigration)[] = [ version: 238, sql: /*sql*/` ALTER TABLE revisions ADD COLUMN description TEXT DEFAULT '' NOT NULL; + ALTER TABLE revisions ADD COLUMN source TEXT DEFAULT 'auto' NOT NULL; `, ignoreErrors: true }, diff --git a/apps/server/src/routes/api/notes.ts b/apps/server/src/routes/api/notes.ts index 9cca431f91..8aec006a09 100644 --- a/apps/server/src/routes/api/notes.ts +++ b/apps/server/src/routes/api/notes.ts @@ -352,7 +352,7 @@ function forceSaveRevision(req: Request<{ noteId: string }>) { } const description = req.body?.description || ""; - const revision = note.saveRevision(description); + const revision = note.saveRevision({ description, source: "manual" }); return { revisionId: revision.revisionId diff --git a/apps/server/src/routes/api/revisions.ts b/apps/server/src/routes/api/revisions.ts index 8598810791..12e1c990ac 100644 --- a/apps/server/src/routes/api/revisions.ts +++ b/apps/server/src/routes/api/revisions.ts @@ -137,7 +137,7 @@ function restoreRevision(req: Request<{ revisionId: string }>) { const note = revision.getNote(); sql.transactional(() => { - note.saveRevision(); + note.saveRevision({ source: "restore" }); for (const oldNoteAttachment of note.getAttachments()) { oldNoteAttachment.markAsDeleted(); diff --git a/apps/server/src/services/llm/tools/note_tools.ts b/apps/server/src/services/llm/tools/note_tools.ts index 8a354175f3..d1ce83e3e0 100644 --- a/apps/server/src/services/llm/tools/note_tools.ts +++ b/apps/server/src/services/llm/tools/note_tools.ts @@ -116,7 +116,7 @@ export const noteTools = defineTools({ return { error: `Cannot update content for note type: ${note.type}` }; } - note.saveRevision(); + note.saveRevision({ source: "llm" }); setNoteContentFromLlm(note, content); return { success: true, @@ -158,7 +158,7 @@ export const noteTools = defineTools({ newContent = existingContent + (existingContent.endsWith("\n") ? "" : "\n") + content; } - note.saveRevision(); + note.saveRevision({ source: "llm" }); note.setContent(newContent); return { success: true, diff --git a/packages/commons/src/lib/rows.ts b/packages/commons/src/lib/rows.ts index 977120c21f..5f198aa02f 100644 --- a/packages/commons/src/lib/rows.ts +++ b/packages/commons/src/lib/rows.ts @@ -21,6 +21,9 @@ export interface AttachmentRow { encoding?: "base64"; } +export const REVISION_SOURCES = ["auto", "manual", "etapi", "llm", "restore"] as const; +export type RevisionSource = (typeof REVISION_SOURCES)[number]; + export interface RevisionRow { revisionId?: string; noteId: string; @@ -29,6 +32,7 @@ export interface RevisionRow { isProtected?: boolean; title: string; description?: string; + source?: RevisionSource; blobId?: string; dateLastEdited?: string; dateCreated?: string; diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index c43b155094..63e5c63680 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -1,4 +1,4 @@ -import { AttachmentRow, AttributeRow, BranchRow, NoteRow, NoteType } from "./rows.js"; +import { AttachmentRow, AttributeRow, BranchRow, NoteRow, NoteType, RevisionSource } from "./rows.js"; type Response = { success: true, @@ -34,6 +34,7 @@ export interface RevisionItem { type: NoteType; title: string; description?: string; + source?: RevisionSource; isProtected?: boolean; mime: string; } @@ -46,6 +47,7 @@ export interface RevisionPojo { isProtected?: boolean; title: string; description?: string; + source?: RevisionSource; blobId?: string; dateLastEdited?: string; dateCreated?: string;