-
- @if (novelSuggestions === 0 && reusableSuggestions === 0) {
-
- No novel suggestions
-
- }
- @if (suggestions()?.suggested_tags?.length > 0) {
-
Tags
- @for (tag of suggestions().suggested_tags; track tag) {
-
{{ tag }}
- }
- }
- @if (suggestions()?.suggested_document_types?.length > 0) {
-
Document Types
- @for (type of suggestions().suggested_document_types; track type) {
-
{{ type }}
- }
- }
- @if (suggestions()?.suggested_correspondents?.length > 0) {
-
Correspondents
- @for (correspondent of suggestions().suggested_correspondents; track correspondent) {
-
{{ correspondent }}
- }
- }
- @if (reusableSuggestions > 0) {
-
- {reusableSuggestions, plural, =1 {1 existing value suggested below} other {{{reusableSuggestions}} existing values suggested below}}
-
- }
+
+
Suggest using:
+
+
+ ML
+
+
+
+ AI
diff --git a/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.scss b/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.scss
index 19aa1dc7d..eafc734b3 100644
--- a/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.scss
+++ b/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.scss
@@ -1,3 +1,7 @@
.suggestions-dropdown {
min-width: 250px;
}
+
+.btn-link.dropdown-toggle::after {
+ display: none;
+}
diff --git a/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.spec.ts b/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.spec.ts
index 6b93d48af..b43c0cba7 100644
--- a/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.spec.ts
+++ b/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.spec.ts
@@ -1,6 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
+import { SuggestionSource } from 'src/app/data/ui-settings'
import { SuggestionsDropdownComponent } from './suggestions-dropdown.component'
describe('SuggestionsDropdownComponent', () => {
@@ -179,14 +180,71 @@ describe('SuggestionsDropdownComponent', () => {
it('should toggle dropdown when clickSuggest is called and suggestions are not null', () => {
fixture.componentRef.setInput('aiEnabled', true)
+ fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
fixture.detectChanges()
fixture.componentRef.setInput('suggestions', {
suggested_correspondents: [],
suggested_tags: [],
suggested_document_types: [],
})
+ fixture.detectChanges()
component.clickSuggest()
- expect(component.dropdown.open).toBeTruthy()
+ expect(component.dropdown.isOpen()).toBeTruthy()
expect(fixture.nativeElement.textContent).toContain('No novel suggestions')
})
+
+ it('should fetch unfetched sources and show existing suggestions', () => {
+ jest.spyOn(component.getSuggestions, 'emit')
+ fixture.componentRef.setInput('aiEnabled', true)
+ fixture.componentRef.setInput('source', SuggestionSource.Both)
+ fixture.componentRef.setInput('fetchedSources', [SuggestionSource.ML])
+ fixture.componentRef.setInput('suggestions', { tags: [1] })
+ fixture.detectChanges()
+ component.clickSuggest()
+ expect(component.getSuggestions.emit).toHaveBeenCalledWith(
+ SuggestionSource.Both
+ )
+ expect(component.dropdown.isOpen()).toBeTruthy()
+ })
+
+ it('should only show source options when AI is enabled', () => {
+ expect(
+ fixture.nativeElement.querySelector('#suggestionSourceML')
+ ).toBeNull()
+ fixture.componentRef.setInput('aiEnabled', true)
+ fixture.detectChanges()
+ fixture.nativeElement
+ .querySelector('button[title="Suggestion options"]')
+ .click()
+ fixture.detectChanges()
+ expect(
+ fixture.nativeElement.querySelector('#suggestionSourceML')
+ ).not.toBeNull()
+ })
+
+ it('should emit source changes and never allow no source', () => {
+ const emitSpy = jest.spyOn(component.sourceChange, 'emit')
+ component.setSources(true, true)
+ expect(emitSpy).toHaveBeenCalledWith(SuggestionSource.Both)
+ component.setSources(true, false)
+ expect(emitSpy).toHaveBeenCalledWith(SuggestionSource.ML)
+ component.setSources(false, true)
+ expect(emitSpy).toHaveBeenCalledWith(SuggestionSource.AI)
+
+ emitSpy.mockClear()
+ component.setSources(false, false)
+ expect(emitSpy).not.toHaveBeenCalled()
+ })
+
+ it('should indicate a non-default source', () => {
+ fixture.componentRef.setInput('aiEnabled', true)
+ fixture.componentRef.setInput('source', SuggestionSource.AI)
+ fixture.componentRef.setInput('defaultSource', SuggestionSource.AI)
+ fixture.detectChanges()
+ expect(fixture.nativeElement.textContent).not.toContain('Not using default')
+
+ fixture.componentRef.setInput('source', SuggestionSource.Both)
+ fixture.detectChanges()
+ expect(fixture.nativeElement.textContent).toContain('Not using default')
+ })
})
diff --git a/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.ts b/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.ts
index ebb14ff79..1a525f131 100644
--- a/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.ts
+++ b/src-ui/src/app/components/common/suggestions-dropdown/suggestions-dropdown.component.ts
@@ -8,6 +8,7 @@ import {
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { DocumentSuggestions } from 'src/app/data/document-suggestions'
+import { SuggestionSource } from 'src/app/data/ui-settings'
import { pngxPopperOptions } from 'src/app/utils/popper-options'
@Component({
@@ -18,12 +19,16 @@ import { pngxPopperOptions } from 'src/app/utils/popper-options'
})
export class SuggestionsDropdownComponent {
public popperOptions = pngxPopperOptions
+ public readonly SuggestionSource = SuggestionSource
@ViewChild('dropdown') dropdown: NgbDropdown
readonly suggestions = input
(null)
readonly aiEnabled = input(false)
readonly loading = input(false)
readonly disabled = input(false)
+ readonly source = input(SuggestionSource.ML)
+ readonly defaultSource = input(SuggestionSource.ML)
+ readonly fetchedSources = input([])
readonly appliedTags = input([])
readonly appliedCorrespondent = input(null)
@@ -31,8 +36,10 @@ export class SuggestionsDropdownComponent {
readonly appliedStoragePath = input(null)
@Output()
- getSuggestions: EventEmitter =
- new EventEmitter()
+ getSuggestions: EventEmitter = new EventEmitter()
+
+ @Output()
+ sourceChange: EventEmitter = new EventEmitter()
@Output()
addTag: EventEmitter = new EventEmitter()
@@ -53,12 +60,42 @@ export class SuggestionsDropdownComponent {
}
if (!this.suggestions()) {
- this.getSuggestions.emit(this)
+ this.getSuggestions.emit(this.source())
+ } else if (this.hasUnfetchedSources) {
+ // sources changed, fetch the rest and show what we have meanwhile
+ this.getSuggestions.emit(this.source())
+ this.dropdown?.open()
} else {
this.dropdown?.toggle()
}
}
+ get useML(): boolean {
+ return this.source() !== SuggestionSource.AI
+ }
+
+ get useAI(): boolean {
+ return this.source() !== SuggestionSource.ML
+ }
+
+ get hasUnfetchedSources(): boolean {
+ const fetched = this.fetchedSources()
+ return (
+ (this.useML && !fetched.includes(SuggestionSource.ML)) ||
+ (this.useAI && !fetched.includes(SuggestionSource.AI))
+ )
+ }
+
+ public setSources(ml: boolean, ai: boolean) {
+ if (ml && ai) {
+ this.sourceChange.emit(SuggestionSource.Both)
+ } else if (ml) {
+ this.sourceChange.emit(SuggestionSource.ML)
+ } else if (ai) {
+ this.sourceChange.emit(SuggestionSource.AI)
+ }
+ }
+
get novelSuggestions(): number {
return (
(this.suggestions()?.suggested_correspondents?.length ?? 0) +
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html
index c3f7e199d..64d0776aa 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.html
+++ b/src-ui/src/app/components/document-detail/document-detail.component.html
@@ -134,11 +134,15 @@
[loading]="suggestionsLoading()"
[suggestions]="suggestions()"
[aiEnabled]="aiEnabled"
+ [source]="suggestionSource"
+ [defaultSource]="defaultSuggestionSource"
+ [fetchedSources]="fetchedSuggestionSources()"
[appliedTags]="documentForm.value.tags"
[appliedCorrespondent]="documentForm.value.correspondent"
[appliedDocumentType]="documentForm.value.document_type"
[appliedStoragePath]="documentForm.value.storage_path"
- (getSuggestions)="getSuggestions()"
+ (getSuggestions)="getSuggestions($event)"
+ (sourceChange)="suggestionSourceOverride.set($event)"
(addTag)="createTag($event)"
(addDocumentType)="createDocumentType($event)"
(addCorrespondent)="createCorrespondent($event)">
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
index 9b285b1a0..1412ced27 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
@@ -43,7 +43,7 @@ import {
} from 'src/app/data/filter-rule-type'
import { StoragePath } from 'src/app/data/storage-path'
import { Tag } from 'src/app/data/tag'
-import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
+import { SETTINGS_KEYS, SuggestionSource } from 'src/app/data/ui-settings'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
@@ -1528,6 +1528,113 @@ describe('DocumentDetailComponent', () => {
expect(component.suggestionsLoading()).toBeFalsy()
})
+ it('should get and merge ML and AI suggestions when source is both', () => {
+ settingsService.set(
+ SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
+ SuggestionSource.Both
+ )
+ const getSetting = settingsService.get.bind(settingsService)
+ jest
+ .spyOn(settingsService, 'get')
+ .mockImplementation((key) =>
+ key === SETTINGS_KEYS.AI_ENABLED ? true : getSetting(key)
+ )
+ const suggestionsSpy = jest
+ .spyOn(documentService, 'getSuggestions')
+ .mockReturnValue(of({ tags: [42], dates: ['2024-01-01'] }))
+ const aiSuggestionsSpy = jest
+ .spyOn(documentService, 'getAiSuggestions')
+ .mockReturnValue(
+ of({ title: 'AI title', tags: [42, 43], suggested_tags: ['New'] })
+ )
+ initNormally()
+ expect(suggestionsSpy).toHaveBeenCalled()
+ expect(aiSuggestionsSpy).toHaveBeenCalled()
+ expect(component.suggestions().title).toEqual('AI title')
+ expect(component.suggestions().tags).toEqual([42, 43])
+ expect(component.suggestions().suggested_tags).toEqual(['New'])
+ expect(component.suggestions().dates).toEqual(['2024-01-01'])
+ })
+
+ it('should only fetch sources not yet fetched for the document', () => {
+ settingsService.set(SETTINGS_KEYS.DOCUMENT_EDITING_AUTO_SUGGEST, false)
+ settingsService.set(
+ SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
+ SuggestionSource.ML
+ )
+ const getSetting = settingsService.get.bind(settingsService)
+ jest
+ .spyOn(settingsService, 'get')
+ .mockImplementation((key) =>
+ key === SETTINGS_KEYS.AI_ENABLED ? true : getSetting(key)
+ )
+ const suggestionsSpy = jest
+ .spyOn(documentService, 'getSuggestions')
+ .mockReturnValue(of({ tags: [42] }))
+ const aiSuggestionsSpy = jest
+ .spyOn(documentService, 'getAiSuggestions')
+ .mockReturnValue(of({ tags: [43] }))
+ initNormally()
+
+ component.getSuggestions()
+ expect(suggestionsSpy).toHaveBeenCalledTimes(1)
+ expect(aiSuggestionsSpy).not.toHaveBeenCalled()
+
+ component.getSuggestions(SuggestionSource.Both)
+ expect(suggestionsSpy).toHaveBeenCalledTimes(1)
+ expect(aiSuggestionsSpy).toHaveBeenCalledTimes(1)
+ expect(component.suggestions().tags).toEqual([42, 43])
+
+ component.getSuggestions(SuggestionSource.Both)
+ expect(suggestionsSpy).toHaveBeenCalledTimes(1)
+ expect(aiSuggestionsSpy).toHaveBeenCalledTimes(1)
+ })
+
+ it('should use the per-document source override and reset it on document change', () => {
+ settingsService.set(SETTINGS_KEYS.DOCUMENT_EDITING_AUTO_SUGGEST, false)
+ const getSetting = settingsService.get.bind(settingsService)
+ jest
+ .spyOn(settingsService, 'get')
+ .mockImplementation((key) =>
+ key === SETTINGS_KEYS.AI_ENABLED ? true : getSetting(key)
+ )
+ initNormally()
+ expect(component.suggestionSource).toEqual(SuggestionSource.AI)
+ component.suggestionSourceOverride.set(SuggestionSource.ML)
+ expect(component.suggestionSource).toEqual(SuggestionSource.ML)
+
+ jest
+ .spyOn(documentService, 'get')
+ .mockReturnValueOnce(of(Object.assign({}, doc)))
+ ;(component as any).loadDocument(doc.id, true)
+ expect(component.suggestionSourceOverride()).toBeNull()
+ expect(component.fetchedSuggestionSources()).toEqual([])
+ })
+
+ it('should keep suggestions from one source if the other fails', () => {
+ settingsService.set(
+ SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
+ SuggestionSource.Both
+ )
+ const getSetting = settingsService.get.bind(settingsService)
+ jest
+ .spyOn(settingsService, 'get')
+ .mockImplementation((key) =>
+ key === SETTINGS_KEYS.AI_ENABLED ? true : getSetting(key)
+ )
+ const errorSpy = jest.spyOn(toastService, 'showError')
+ jest
+ .spyOn(documentService, 'getSuggestions')
+ .mockReturnValue(of({ tags: [42] }))
+ jest
+ .spyOn(documentService, 'getAiSuggestions')
+ .mockReturnValue(throwError(() => new Error('failed')))
+ initNormally()
+ expect(errorSpy).toHaveBeenCalled()
+ expect(component.suggestions().tags).toEqual([42])
+ expect(component.fetchedSuggestionSources()).toEqual([SuggestionSource.ML])
+ })
+
it('should show error if needed for get suggestions', () => {
const suggestionsSpy = jest.spyOn(documentService, 'getSuggestions')
const errorSpy = jest.spyOn(toastService, 'showError')
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts
index b9ec0bb76..5b12ca98b 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.ts
@@ -28,7 +28,7 @@ import {
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { DeviceDetectorService } from 'ngx-device-detector'
-import { BehaviorSubject, Observable, of, Subject, timer } from 'rxjs'
+import { BehaviorSubject, merge, Observable, of, Subject, timer } from 'rxjs'
import {
catchError,
debounceTime,
@@ -48,7 +48,10 @@ import { DataType } from 'src/app/data/datatype'
import { Document, DocumentVersionInfo } from 'src/app/data/document'
import { DocumentMetadata } from 'src/app/data/document-metadata'
import { DocumentNote } from 'src/app/data/document-note'
-import { DocumentSuggestions } from 'src/app/data/document-suggestions'
+import {
+ DocumentSuggestions,
+ mergeSuggestions,
+} from 'src/app/data/document-suggestions'
import { DocumentType } from 'src/app/data/document-type'
import { FilterRule } from 'src/app/data/filter-rule'
import {
@@ -63,7 +66,7 @@ import {
import { ObjectWithId } from 'src/app/data/object-with-id'
import { StoragePath } from 'src/app/data/storage-path'
import { Tag } from 'src/app/data/tag'
-import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
+import { SETTINGS_KEYS, SuggestionSource } from 'src/app/data/ui-settings'
import { User } from 'src/app/data/user'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
@@ -240,6 +243,10 @@ export class DocumentDetailComponent
private readonly autoSuggestSetting = this.settings.getSignal(
SETTINGS_KEYS.DOCUMENT_EDITING_AUTO_SUGGEST
)
+ private readonly suggestionSourceSetting =
+ this.settings.getSignal(
+ SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE
+ )
private readonly hiddenFieldsSetting = this.settings.getSignal<
DocumentDetailFieldID[]
>(SETTINGS_KEYS.DOCUMENT_DETAILS_HIDDEN_FIELDS)
@@ -261,6 +268,9 @@ export class DocumentDetailComponent
readonly metadata = signal(undefined)
readonly suggestions = signal(undefined)
readonly suggestionsLoading = signal(false)
+ // per-document, resets on navigation
+ readonly suggestionSourceOverride = signal(null)
+ readonly fetchedSuggestionSources = signal([])
readonly users = signal(undefined)
readonly title = signal(undefined)
@@ -365,6 +375,15 @@ export class DocumentDetailComponent
return this.autoSuggestSetting()
}
+ get defaultSuggestionSource(): SuggestionSource {
+ return this.aiEnabled ? this.suggestionSourceSetting() : SuggestionSource.ML
+ }
+
+ get suggestionSource(): SuggestionSource {
+ if (!this.aiEnabled) return SuggestionSource.ML
+ return this.suggestionSourceOverride() ?? this.defaultSuggestionSource
+ }
+
get archiveContentRenderType(): ContentRenderType {
const hasArchiveVersion =
this.metadata()?.has_archive_version ??
@@ -590,6 +609,8 @@ export class DocumentDetailComponent
}
this.documentId.set(doc.id)
this.suggestions.set(null)
+ this.suggestionSourceOverride.set(null)
+ this.fetchedSuggestionSources.set([])
const openDocument = this.openDocumentService.getOpenDocument(
this.documentId()
)
@@ -1077,29 +1098,44 @@ export class DocumentDetailComponent
return this.documentForm.get('custom_fields') as FormArray
}
- getSuggestions() {
+ getSuggestions(source: SuggestionSource = this.suggestionSource) {
+ const sources = (
+ source === SuggestionSource.Both
+ ? [SuggestionSource.ML, SuggestionSource.AI]
+ : [source]
+ ).filter((s) => !this.fetchedSuggestionSources().includes(s))
+ if (!sources.length) return
+
this.suggestionsLoading.set(true)
- const suggestionsObservable = this.aiEnabled
- ? this.documentsService.getAiSuggestions(this.documentId())
- : this.documentsService.getSuggestions(this.documentId())
- suggestionsObservable
+ merge(
+ ...sources.map((s) =>
+ (s === SuggestionSource.AI
+ ? this.documentsService.getAiSuggestions(this.documentId())
+ : this.documentsService.getSuggestions(this.documentId())
+ ).pipe(
+ first(),
+ map((result) => ({ source: s, result })),
+ catchError((error) => {
+ this.toastService.showError(
+ $localize`Error retrieving suggestions.`,
+ error
+ )
+ return of(null)
+ })
+ )
+ )
+ )
.pipe(
- first(),
takeUntil(this.unsubscribeNotifier),
takeUntil(this.docChangeNotifier),
finalize(() => this.suggestionsLoading.set(false))
)
- .subscribe({
- next: (result) => {
- this.suggestions.set(result)
- },
- error: (error) => {
- this.suggestions.set(null)
- this.toastService.showError(
- $localize`Error retrieving suggestions.`,
- error
- )
- },
+ .subscribe((response) => {
+ if (!response) return
+ this.fetchedSuggestionSources.update((f) => [...f, response.source])
+ this.suggestions.set(
+ mergeSuggestions(this.suggestions(), response.result)
+ )
})
}
diff --git a/src-ui/src/app/data/document-suggestions.ts b/src-ui/src/app/data/document-suggestions.ts
index 447c4402b..f29daff57 100644
--- a/src-ui/src/app/data/document-suggestions.ts
+++ b/src-ui/src/app/data/document-suggestions.ts
@@ -15,3 +15,33 @@ export interface DocumentSuggestions {
dates?: string[] // ISO-formatted date string e.g. 2022-11-03
}
+
+const union = (a: T[] = [], b: T[] = []): T[] => [...new Set([...a, ...b])]
+
+export function mergeSuggestions(
+ a: DocumentSuggestions,
+ b: DocumentSuggestions
+): DocumentSuggestions {
+ if (!a) return b
+ return {
+ title: a.title || b.title,
+ tags: union(a.tags, b.tags),
+ suggested_tags: union(a.suggested_tags, b.suggested_tags),
+ correspondents: union(a.correspondents, b.correspondents),
+ suggested_correspondents: union(
+ a.suggested_correspondents,
+ b.suggested_correspondents
+ ),
+ document_types: union(a.document_types, b.document_types),
+ suggested_document_types: union(
+ a.suggested_document_types,
+ b.suggested_document_types
+ ),
+ storage_paths: union(a.storage_paths, b.storage_paths),
+ suggested_storage_paths: union(
+ a.suggested_storage_paths,
+ b.suggested_storage_paths
+ ),
+ dates: union(a.dates, b.dates),
+ }
+}
diff --git a/src-ui/src/app/data/ui-settings.ts b/src-ui/src/app/data/ui-settings.ts
index 5bc500872..32c5242bd 100644
--- a/src-ui/src/app/data/ui-settings.ts
+++ b/src-ui/src/app/data/ui-settings.ts
@@ -20,6 +20,12 @@ export enum GlobalSearchType {
TITLE_CONTENT = 'title-content',
}
+export enum SuggestionSource {
+ ML = 'ml',
+ AI = 'ai',
+ Both = 'both',
+}
+
export enum CollapsibleSection {
ATTRIBUTES = 'attributes',
}
@@ -98,6 +104,8 @@ export const SETTINGS_KEYS = {
'general-settings:document-editing:overlay-thumbnail',
DOCUMENT_EDITING_AUTO_SUGGEST:
'general-settings:document-editing:auto-suggest',
+ DOCUMENT_EDITING_SUGGESTION_SOURCE:
+ 'general-settings:document-editing:suggestion-source',
DOCUMENT_DETAILS_HIDDEN_FIELDS:
'general-settings:document-details:hidden-fields',
SEARCH_DB_ONLY: 'general-settings:search:db-only',
@@ -326,6 +334,11 @@ export const SETTINGS: UiSetting[] = [
type: 'boolean',
default: true,
},
+ {
+ key: SETTINGS_KEYS.DOCUMENT_EDITING_SUGGESTION_SOURCE,
+ type: 'string',
+ default: SuggestionSource.AI,
+ },
{
key: SETTINGS_KEYS.DOCUMENT_DETAILS_HIDDEN_FIELDS,
type: 'array',
diff --git a/src-ui/src/main.ts b/src-ui/src/main.ts
index dc8d8d3f5..83990a3f9 100644
--- a/src-ui/src/main.ts
+++ b/src-ui/src/main.ts
@@ -74,6 +74,7 @@ import {
clipboardCheckFill,
clipboardFill,
clockHistory,
+ cpu,
creditCard,
dash,
dashCircle,
@@ -118,6 +119,7 @@ import {
infoCircle,
journalBookmarkFill,
journals,
+ lightbulb,
link,
list,
listNested,
@@ -322,6 +324,7 @@ const icons = {
clipboardCheckFill,
clipboardFill,
clockHistory,
+ cpu,
cash,
creditCard,
dash,
@@ -367,6 +370,7 @@ const icons = {
infoCircle,
journalBookmarkFill,
journals,
+ lightbulb,
link,
list,
listNested,
diff --git a/src-ui/src/styles.scss b/src-ui/src/styles.scss
index 8c0fec3e8..c135d343b 100644
--- a/src-ui/src/styles.scss
+++ b/src-ui/src/styles.scss
@@ -292,6 +292,7 @@ a.btn-link:active,
a.btn-link:focus-visible,
.btn-link:hover,
.btn-link:active,
+.btn-link.show,
.btn-link:focus-visible {
color: var(--pngx-primary-lighten-10) !important;
.primary-light & {