RSS Git Download  Clone
Raw Blame History 9kB 260 lines
import { Component, Inject, OnInit, OnChanges, OnDestroy, SimpleChanges, ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatDividerModule } from '@angular/material/divider';
import { BreakpointObserver } from '@angular/cdk/layout';
import { I18nService } from '../../../services/i18n.service';
import { SocketService } from '../../../services/socket.service';
import { CommonService } from '../../../services/common.service';
import { JsonViewDialogService } from '../../../dialogs/json-view-dialog.service';
import { KeyNewOrSetDialogService } from '../../../dialogs/key-new-or-set-dialog.service';
import { MainCommandService } from '../../../services/main-command.service';
import { RedisStateService } from '../../../services/redis-state.service';
import { SettingsService } from '../../../services/settings.service';
import { KeyTypeBase } from './key-type-base';
import { KeyPaging } from './key-paging';
import { KeyPagerInlineComponent } from './key-pager-inline.component';
import { P3xrAccordionComponent } from '../../../components/p3xr-accordion.component';
import { P3xrButtonComponent } from '../../../components/p3xr-button.component';

@Component({
    selector: 'p3xr-key-vectorset',
    standalone: true,
    imports: [
        CommonModule, FormsModule,
        MatButtonModule, MatIconModule, MatTooltipModule,
        MatFormFieldModule, MatInputModule,
        MatListModule, MatDividerModule,
        P3xrAccordionComponent, P3xrButtonComponent, KeyPagerInlineComponent,
    ],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
    templateUrl: './key-vectorset.component.html',
    encapsulation: ViewEncapsulation.None,
})
export class KeyVectorsetComponent extends KeyTypeBase implements OnInit, OnChanges, OnDestroy {

    infoItems: Array<{ key: string; value: any }> = [];
    elements: Array<{ element: string; score: number }> = [];
    pagedElements: Array<{ element: string; score: number }> = [];
    paging: KeyPaging;
    simResults: Array<{ element: string; score: number }> = [];

    elementInput = '';
    vectorInput = '';
    simQueryInput = '';
    simCountInput = 10;
    simFilterInput = '';
    simMode: 'element' | 'vector' = 'element';

    autoRefresh = false;
    private autoRefreshInterval: any = null;
    readonly = false;
    showAddForm = false;

    constructor(
        @Inject(I18nService) i18n: I18nService,
        @Inject(SocketService) socket: SocketService,
        @Inject(CommonService) common: CommonService,
        @Inject(JsonViewDialogService) jsonViewDialog: JsonViewDialogService,
        @Inject(KeyNewOrSetDialogService) keyNewOrSetDialog: KeyNewOrSetDialogService,
        @Inject(BreakpointObserver) breakpointObserver: BreakpointObserver,
        @Inject(MainCommandService) cmd: MainCommandService,
        @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef,
        @Inject(RedisStateService) redisState: RedisStateService,
        @Inject(SettingsService) settingsService: SettingsService,
    ) {
        super(i18n, socket, common, jsonViewDialog, keyNewOrSetDialog, breakpointObserver, cmd, cdr, redisState, settingsService);
        this.paging = new KeyPaging({ settingsService });
    }

    updatePagedElements(): void {
        this.pagedElements = this.elements.slice(this.paging.startIndex, this.paging.endIndex);
    }

    get strings() {
        return this.i18n.strings;
    }

    ngOnInit() {
        this.readonly = this.redisState.connection()?.readonly === true;
        this.parseInfo();
        this.loadElements();
    }

    ngOnChanges(changes: SimpleChanges) {
        if (changes['p3xrValue']) {
            this.parseInfo();
        }
    }

    ngOnDestroy() {
        this.stopAutoRefresh();
        this.destroyBase();
    }

    toggleAutoRefresh(): void {
        this.autoRefresh = !this.autoRefresh;
        if (this.autoRefresh) {
            this.startAutoRefresh();
        } else {
            this.stopAutoRefresh();
        }
        this.cdr.markForCheck();
    }

    private startAutoRefresh(): void {
        this.stopAutoRefresh();
        this.autoRefreshInterval = setInterval(() => {
            this.refresh();
        }, 10000);
    }

    private stopAutoRefresh(): void {
        if (this.autoRefreshInterval) {
            clearInterval(this.autoRefreshInterval);
            this.autoRefreshInterval = null;
        }
    }

    private parseInfo() {
        this.infoItems = [];
        try {
            const val = this.p3xrValue;
            let info: any;
            if (typeof val === 'object' && val !== null && !ArrayBuffer.isView(val)) {
                info = val;
            } else if (typeof val === 'string') {
                info = JSON.parse(val);
            } else if (ArrayBuffer.isView(val)) {
                info = JSON.parse(new TextDecoder().decode(val as any));
            }
            if (info && typeof info === 'object') {
                this.infoItems = Object.entries(info).map(([key, value]) => ({ key, value }));
            }
        } catch {
            // ignore parse errors
        }
    }

    async loadElements() {
        try {
            const response = await this.socket.request({
                action: 'vectorset/elements',
                payload: {
                    key: this.p3xrKey,
                },
            });
            this.elements = (response as any).elements || [];
            this.paging.figurePaging(this.elements.length);
            this.updatePagedElements();
        } catch {
            this.elements = [];
            this.pagedElements = [];
        }
        this.cdr.markForCheck();
    }

    async searchSimilar(queryValue?: string, mode?: 'element' | 'vector') {
        const query = queryValue || this.simQueryInput;
        const searchMode = mode || this.simMode;
        if (!query.trim()) return;
        try {
            const response = await this.socket.request({
                action: 'vectorset/sim',
                payload: {
                    key: this.p3xrKey,
                    query: query.trim(),
                    count: this.simCountInput,
                    mode: searchMode,
                    filter: this.simFilterInput.trim() || undefined,
                },
            });
            this.simResults = (response as any).results || [];
            this.common.toast(this.strings()?.page?.key?.vectorset?.searchComplete);
        } catch (e: any) {
            this.common.toast(e.message || 'Error');
            this.simResults = [];
        }
        this.cdr.markForCheck();
    }

    searchSimilarByElement(element: string) {
        this.simQueryInput = element;
        this.simMode = 'element';
        this.searchSimilar(element, 'element');
    }

    async addElement() {
        if (!this.elementInput.trim() || !this.vectorInput.trim()) return;
        try {
            await this.socket.request({
                action: 'vectorset/add',
                payload: {
                    key: this.p3xrKey,
                    element: this.elementInput.trim(),
                    vector: this.vectorInput.trim(),
                },
            });
            this.common.toast(this.strings()?.page?.key?.vectorset?.addedSuccessfully);
            this.elementInput = '';
            this.vectorInput = '';
            this.refresh();
        } catch (e: any) {
            this.common.toast(e.message || 'Error');
        }
        this.cdr.markForCheck();
    }

    async removeElement(element: string) {
        try {
            await this.common.confirm({
                message: this.strings()?.confirm?.delete,
            });
            await this.socket.request({
                action: 'vectorset/remove',
                payload: {
                    key: this.p3xrKey,
                    element: element,
                },
            });
            this.common.toast(this.strings()?.page?.key?.vectorset?.removedSuccessfully);
            this.refresh();
        } catch (e: any) {
            if (e?.message) this.common.toast(e.message);
        }
        this.cdr.markForCheck();
    }

    async getAttributes(element: string) {
        try {
            const response = await this.socket.request({
                action: 'vectorset/getattr',
                payload: {
                    key: this.p3xrKey,
                    element: element,
                },
            });
            const attrs = (response as any).attributes;
            if (attrs) {
                this.common.toast(`${element}: ${JSON.stringify(attrs)}`);
            } else {
                this.common.toast(`${element}: ${this.strings()?.page?.key?.vectorset?.noAttributes}`);
            }
        } catch (e: any) {
            this.common.toast(e.message || 'Error');
        }
        this.cdr.markForCheck();
    }

    refresh() {
        this.cmd.refreshKey$.next();
        this.loadElements();
    }
}