From 9106863dbb4ed0e17f3e5059f40be69014cf1e98 Mon Sep 17 00:00:00 2001 From: awesomerobot Date: Wed, 12 Mar 2025 13:07:43 -0400 Subject: [PATCH 1/9] REFACTOR: update embeddings to formkit --- .../components/ai-embedding-editor.gjs | 630 +++++++++++------- 1 file changed, 376 insertions(+), 254 deletions(-) diff --git a/assets/javascripts/discourse/components/ai-embedding-editor.gjs b/assets/javascripts/discourse/components/ai-embedding-editor.gjs index 98c32983b..d98574059 100644 --- a/assets/javascripts/discourse/components/ai-embedding-editor.gjs +++ b/assets/javascripts/discourse/components/ai-embedding-editor.gjs @@ -1,23 +1,19 @@ import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; -import { Input, Textarea } from "@ember/component"; +import { cached, tracked } from "@glimmer/tracking"; import { concat, fn, get } from "@ember/helper"; -import { on } from "@ember/modifier"; -import { action, computed } from "@ember/object"; -import didInsert from "@ember/render-modifiers/modifiers/did-insert"; -import didUpdate from "@ember/render-modifiers/modifiers/did-update"; +import { action } from "@ember/object"; import { later } from "@ember/runloop"; import { service } from "@ember/service"; +import { eq, not } from "truth-helpers"; import BackButton from "discourse/components/back-button"; +import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; import DButton from "discourse/components/d-button"; +import Form from "discourse/components/form"; import icon from "discourse/helpers/d-icon"; import { popupAjaxError } from "discourse/lib/ajax-error"; import { i18n } from "discourse-i18n"; import AdminSectionLandingItem from "admin/components/admin-section-landing-item"; import AdminSectionLandingWrapper from "admin/components/admin-section-landing-wrapper"; -import ComboBox from "select-kit/components/combo-box"; -import DTooltip from "float-kit/components/d-tooltip"; -import not from "truth-helpers/helpers/not"; export default class AiEmbeddingEditor extends Component { @service toasts; @@ -27,12 +23,35 @@ export default class AiEmbeddingEditor extends Component { @tracked isSaving = false; @tracked selectedPreset = null; - @tracked testRunning = false; @tracked testResult = null; @tracked testError = null; - @tracked apiKeySecret = true; - @tracked editingModel = null; + @tracked currentProvider = null; + + constructor() { + super(...arguments); + if (this.args.model) { + this.currentProvider = this.args.model.provider; + } + } + + @cached + get formData() { + let data; + + if (this.selectedPreset) { + data = this.store + .createRecord("ai-embedding", this.selectedPreset) + .workingCopy(); + } else { + data = this.args.model.workingCopy(); + } + + const originalData = JSON.parse(JSON.stringify(data)); + this._originalFormData = originalData; + + return originalData; + } get selectedProviders() { const t = (provName) => { @@ -88,13 +107,17 @@ export default class AiEmbeddingEditor extends Component { return !this.selectedPreset && this.args.model.isNew; } - @computed("editingModel.provider") get metaProviderParams() { - return ( - this.args.embeddings.resultSetMeta.provider_params[ - this.editingModel?.provider - ] || {} - ); + const provider = this.currentProvider; + if (!provider) { + return {}; + } + + const embeddings = this.args.embeddings || {}; + const meta = embeddings.resultSetMeta || {}; + const providerParams = meta.provider_params || {}; + + return providerParams[provider] || {}; } get testErrorMessage() { @@ -105,6 +128,10 @@ export default class AiEmbeddingEditor extends Component { return this.testRunning || this.testResult !== null; } + get seeded() { + return this.args.model.id < 0; + } + @action configurePreset(preset) { this.selectedPreset = @@ -113,36 +140,96 @@ export default class AiEmbeddingEditor extends Component { preset.id ) || {}; - this.editingModel = this.store - .createRecord("ai-embedding", this.selectedPreset) - .workingCopy(); + if (this.selectedPreset.provider) { + this.currentProvider = this.selectedPreset.provider; + } } @action - updateModel() { - this.editingModel = this.args.model.workingCopy(); + setProvider(provider, { set }) { + set("provider", provider); + + this.currentProvider = provider; + + const providerParams = + this.args.embeddings?.resultSetMeta?.provider_params || {}; + const params = providerParams[provider] || {}; + + const initialParams = {}; + + if (params) { + const keys = Object.keys(params); + keys.forEach((key) => { + initialParams[key] = null; + }); + } + + set("provider_params", initialParams); } - @action - makeApiKeySecret() { - this.apiKeySecret = true; + get providerParamsForCurrentProvider() { + const normalizeParam = (value) => { + if (!value) { + return { type: "text" }; + } + + if (typeof value === "string") { + return { type: value }; + } + + return { + type: value.type || "text", + values: (value.values || []).map((v) => ({ id: v, name: v })), + default: value.default, + }; + }; + + return Object.entries(this.metaProviderParams).reduce( + (acc, [field, value]) => { + acc[field] = normalizeParam(value); + return acc; + }, + {} + ); } @action - toggleApiKeySecret() { - this.apiKeySecret = !this.apiKeySecret; + resetForm() { + this.selectedPreset = null; + this.currentProvider = null; } @action - async save() { + async save(formData) { this.isSaving = true; const isNew = this.args.model.isNew; try { - await this.editingModel.save(); + const dataToSave = Object.entries(formData).reduce( + (acc, [key, newVal]) => { + const originalVal = this._originalFormData?.[key]; + if (newVal !== originalVal) { + acc[key] = newVal; + } + return acc; + }, + {} + ); + + if (this.selectedPreset) { + // new embeddings from presets + const newModel = this.store.createRecord("ai-embedding", { + ...this.selectedPreset, + ...dataToSave, + }); + await newModel.save(); + this.args.embeddings.addObject(newModel); + } else { + // existing embeddings + await this.args.model.save(dataToSave); + } if (isNew) { - this.args.embeddings.addObject(this.editingModel); this.router.transitionTo( "adminPlugins.show.discourse-ai-embeddings.index" ); @@ -162,11 +249,11 @@ export default class AiEmbeddingEditor extends Component { } @action - async test() { + async test(data) { this.testRunning = true; try { - const configTestResult = await this.editingModel.testConfig(); + const configTestResult = await this.args.model.testConfig(data); this.testResult = configTestResult.success; if (this.testResult) { @@ -191,7 +278,7 @@ export default class AiEmbeddingEditor extends Component { return this.args.model .destroyRecord() .then(() => { - this.args.llms.removeObject(this.args.model); + this.args.embeddings.removeObject(this.args.model); this.router.transitionTo( "adminPlugins.show.discourse-ai-embeddings.index" ); @@ -201,52 +288,44 @@ export default class AiEmbeddingEditor extends Component { }); } - @action - resetForm() { - this.selectedPreset = null; - this.editingModel = null; - } -