Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 99e73f0

Browse files
authored
UX: improve embeddings config styles (#1085)
* WIP: improve embeddings config styles * switch to textarea, fix back button * remove log, update button, fix tests * stree * fix spec * spec fix * remove comment
1 parent 952e0a5 commit 99e73f0

File tree

6 files changed

+168
-50
lines changed

6 files changed

+168
-50
lines changed

app/models/embedding_definition.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def presets
7777
# indexes, so we downsample to 2000 via API.
7878
{
7979
preset_id: "text-embedding-3-large",
80-
display_name: "OpenAI's text-embedding-3-large",
80+
display_name: "text-embedding-3-large",
8181
dimensions: 2000,
8282
max_sequence_length: 8191,
8383
pg_function: "<=>",
@@ -91,7 +91,7 @@ def presets
9191
},
9292
{
9393
preset_id: "text-embedding-3-small",
94-
display_name: "OpenAI's text-embedding-3-small",
94+
display_name: "text-embedding-3-small",
9595
dimensions: 1536,
9696
max_sequence_length: 8191,
9797
pg_function: "<=>",
@@ -105,7 +105,7 @@ def presets
105105
},
106106
{
107107
preset_id: "text-embedding-ada-002",
108-
display_name: "OpenAI's text-embedding-ada-002",
108+
display_name: "text-embedding-ada-002",
109109
dimensions: 1536,
110110
max_sequence_length: 8191,
111111
pg_function: "<=>",

assets/javascripts/discourse/components/ai-embedding-editor.gjs

Lines changed: 94 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Component from "@glimmer/component";
22
import { tracked } from "@glimmer/tracking";
3-
import { Input } from "@ember/component";
4-
import { concat, get } from "@ember/helper";
3+
import { Input, Textarea } from "@ember/component";
4+
import { concat, fn, get } from "@ember/helper";
55
import { on } from "@ember/modifier";
66
import { action, computed } from "@ember/object";
77
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
@@ -13,6 +13,8 @@ import DButton from "discourse/components/d-button";
1313
import icon from "discourse/helpers/d-icon";
1414
import { popupAjaxError } from "discourse/lib/ajax-error";
1515
import { i18n } from "discourse-i18n";
16+
import AdminSectionLandingItem from "admin/components/admin-section-landing-item";
17+
import AdminSectionLandingWrapper from "admin/components/admin-section-landing-wrapper";
1618
import ComboBox from "select-kit/components/combo-box";
1719
import DTooltip from "float-kit/components/d-tooltip";
1820
import not from "truth-helpers/helpers/not";
@@ -48,7 +50,19 @@ export default class AiEmbeddingEditor extends Component {
4850
};
4951

5052
return this.args.embeddings.resultSetMeta.distance_functions.map((df) => {
51-
return { id: df, name: t(df) };
53+
let iconName;
54+
55+
if (df === "<=>") {
56+
iconName = "discourse-spaceship-operator";
57+
} else if (df === "<#>") {
58+
iconName = "discourse-negative-inner-product";
59+
}
60+
61+
return {
62+
id: df,
63+
name: t(df),
64+
icon: iconName,
65+
};
5266
});
5367
}
5468

@@ -57,12 +71,14 @@ export default class AiEmbeddingEditor extends Component {
5771
return {
5872
name: preset.display_name,
5973
id: preset.preset_id,
74+
provider: preset.provider,
6075
};
6176
});
6277

63-
presets.pushObject({
78+
presets.unshiftObject({
6479
name: i18n("discourse_ai.embeddings.configure_manually"),
6580
id: "manual",
81+
provider: "fake",
6682
});
6783

6884
return presets;
@@ -90,11 +106,11 @@ export default class AiEmbeddingEditor extends Component {
90106
}
91107

92108
@action
93-
configurePreset() {
109+
configurePreset(preset) {
94110
this.selectedPreset =
95111
this.args.embeddings.resultSetMeta.presets.findBy(
96112
"preset_id",
97-
this.presetId
113+
preset.id
98114
) || {};
99115

100116
this.editingModel = this.store
@@ -185,35 +201,64 @@ export default class AiEmbeddingEditor extends Component {
185201
});
186202
}
187203

188-
<template>
189-
<BackButton
190-
@route="adminPlugins.show.discourse-ai-embeddings"
191-
@label="discourse_ai.embeddings.back"
192-
/>
204+
@action
205+
resetForm() {
206+
this.selectedPreset = null;
207+
this.editingModel = null;
208+
}
193209

210+
<template>
194211
<form
195212
{{didInsert this.updateModel @model.id}}
196213
{{didUpdate this.updateModel @model.id}}
197214
class="form-horizontal ai-embedding-editor"
198215
>
199216
{{#if this.showPresets}}
217+
<BackButton
218+
@route="adminPlugins.show.discourse-ai-embeddings"
219+
@label="discourse_ai.embeddings.back"
220+
/>
200221
<div class="control-group">
201-
<label>{{i18n "discourse_ai.embeddings.presets"}}</label>
202-
<ComboBox
203-
@value={{this.presetId}}
204-
@content={{this.presets}}
205-
class="ai-embedding-editor__presets"
206-
/>
222+
<h2>{{i18n "discourse_ai.embeddings.presets"}}</h2>
223+
<AdminSectionLandingWrapper>
224+
{{#each this.presets as |preset|}}
225+
<AdminSectionLandingItem
226+
@titleLabelTranslated={{preset.name}}
227+
@taglineLabel={{concat
228+
"discourse_ai.embeddings.providers."
229+
preset.provider
230+
}}
231+
data-preset-id={{preset.id}}
232+
class="ai-llms-list-editor__templates-list-item"
233+
>
234+
<:buttons as |buttons|>
235+
<buttons.Default
236+
@action={{fn this.configurePreset preset}}
237+
@icon="gear"
238+
@label="discourse_ai.llms.preconfigured.button"
239+
/>
240+
</:buttons>
241+
</AdminSectionLandingItem>
242+
243+
{{/each}}
244+
</AdminSectionLandingWrapper>
245+
207246
</div>
208247

209-
<div class="control-group ai-llm-editor__action_panel">
248+
{{else}}
249+
{{#if this.editingModel.isNew}}
210250
<DButton
211-
@action={{this.configurePreset}}
212-
@label="discourse_ai.tools.next.title"
213-
class="ai-embedding-editor__next"
251+
@action={{this.resetForm}}
252+
@label="back_button"
253+
@icon="chevron-left"
254+
class="btn-flat back-button"
214255
/>
215-
</div>
216-
{{else}}
256+
{{else}}
257+
<BackButton
258+
@route="adminPlugins.show.discourse-ai-embeddings"
259+
@label="discourse_ai.embeddings.back"
260+
/>
261+
{{/if}}
217262
<div class="control-group">
218263
<label>{{i18n "discourse_ai.embeddings.display_name"}}</label>
219264
<Input
@@ -295,27 +340,38 @@ export default class AiEmbeddingEditor extends Component {
295340
@type="checkbox"
296341
@checked={{this.editingModel.matryoshka_dimensions}}
297342
/>
298-
<label>{{i18n
299-
"discourse_ai.embeddings.matryoshka_dimensions"
300-
}}</label>
343+
<label>{{i18n "discourse_ai.embeddings.matryoshka_dimensions"}}
344+
</label>
345+
<DTooltip
346+
@icon="circle-question"
347+
@content={{i18n
348+
"discourse_ai.embeddings.hints.matryoshka_dimensions"
349+
}}
350+
/>
301351
</div>
302352

303353
<div class="control-group">
304354
<label>{{i18n "discourse_ai.embeddings.embed_prompt"}}</label>
305-
<Input
306-
@type="text"
355+
<Textarea
307356
class="ai-embedding-editor-input ai-embedding-editor__embed_prompt"
308357
@value={{this.editingModel.embed_prompt}}
309358
/>
359+
<DTooltip
360+
@icon="circle-question"
361+
@content={{i18n "discourse_ai.embeddings.hints.embed_prompt"}}
362+
/>
310363
</div>
311364

312365
<div class="control-group">
313366
<label>{{i18n "discourse_ai.embeddings.search_prompt"}}</label>
314-
<Input
315-
@type="text"
367+
<Textarea
316368
class="ai-embedding-editor-input ai-embedding-editor__search_prompt"
317369
@value={{this.editingModel.search_prompt}}
318370
/>
371+
<DTooltip
372+
@icon="circle-question"
373+
@content={{i18n "discourse_ai.embeddings.hints.search_prompt"}}
374+
/>
319375
</div>
320376

321377
<div class="control-group">
@@ -329,6 +385,10 @@ export default class AiEmbeddingEditor extends Component {
329385
@value={{this.editingModel.max_sequence_length}}
330386
required="true"
331387
/>
388+
<DTooltip
389+
@icon="circle-question"
390+
@content={{i18n "discourse_ai.embeddings.hints.sequence_length"}}
391+
/>
332392
</div>
333393

334394
<div class="control-group">
@@ -338,6 +398,10 @@ export default class AiEmbeddingEditor extends Component {
338398
@content={{this.distanceFunctions}}
339399
@class="ai-embedding-editor__distance_functions"
340400
/>
401+
<DTooltip
402+
@icon="circle-question"
403+
@content={{i18n "discourse_ai.embeddings.hints.distance_function"}}
404+
/>
341405
</div>
342406

343407
{{#each-in this.metaProviderParams as |field type|}}

assets/stylesheets/modules/embeddings/common/ai-embedding-editor.scss

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,47 @@
2828
display: flex;
2929
align-items: flex-start;
3030
}
31+
32+
&__distance_functions.select-kit {
33+
.selected-name {
34+
.d-icon {
35+
width: 2em;
36+
height: 2em;
37+
position: absolute;
38+
39+
+ .name {
40+
margin-left: 2.25em;
41+
}
42+
}
43+
}
44+
45+
.svg-icon-title {
46+
width: 2em;
47+
top: -0.5em;
48+
49+
svg {
50+
width: 2em;
51+
height: 2em;
52+
}
53+
}
54+
}
55+
}
56+
57+
.discourse-ai-embeddings {
58+
.btn-flat.back-button {
59+
padding-left: 0;
60+
}
61+
62+
.fk-d-tooltip__icon {
63+
margin-left: 0.25em;
64+
color: var(--primary-medium);
65+
}
66+
67+
textarea + .fk-d-tooltip__trigger {
68+
vertical-align: top;
69+
}
70+
71+
.d-icon-circle-exclamation {
72+
color: var(--danger);
73+
}
3174
}

config/locales/client.en.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -515,14 +515,18 @@ en:
515515
configure_manually: "Configure manually"
516516
edit: "Edit"
517517
seeded_warning: "This is pre-configured on your site and cannot be edited."
518-
tests:
518+
tests:
519519
title: "Run test"
520520
running: "Running test..."
521521
success: "Success!"
522522
failure: "Attempting to generate an embedding resulted in: %{error}"
523523
hints:
524524
dimensions_warning: "Once saved, this value can't be changed."
525-
525+
matryoshka_dimensions: "Defines the size of nested embeddings used for hierarchical or multi-layered representation of data, similar to how nested dolls fit within each other."
526+
embed_prompt: "Tells the LLM how to process text to create its numerical summary (embedding) for analysis or comparison."
527+
search_prompt: "Tells the LLM how to compare a search query with existing embeddings and find the best matches."
528+
sequence_length: "The maximum number of tokens that can be processed at once when creating embeddings or handling a query."
529+
distance_function: "Determines how similarity between embeddings is calculated, using either cosine distance (measuring the angle between vectors) or negative inner product (measuring overlap of vector values)."
526530
display_name: "Name"
527531
provider: "Provider"
528532
url: "Embeddings service URL"
@@ -536,18 +540,18 @@ en:
536540

537541
distance_function: "Distance function"
538542
distance_functions:
539-
<#>: "Negative inner product (<#>)"
540-
<=>: "Cosine distance (<=>)"
543+
<#>: "Negative inner product"
544+
<=>: "Cosine distance"
541545
providers:
542546
hugging_face: "Hugging Face"
543547
open_ai: "OpenAI"
544548
google: "Google"
545549
cloudflare: "Cloudflare"
546550
CDCK: "CDCK"
551+
fake: "Custom"
547552
provider_fields:
548553
model_name: "Model name"
549554

550-
551555
semantic_search: "Topics (Semantic)"
552556
semantic_search_loading: "Searching for more results using AI"
553557
semantic_search_results:

spec/system/embeddings/ai_embedding_definition_spec.rb

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@
1313
visit "/admin/plugins/discourse-ai/ai-embeddings"
1414

1515
find(".ai-embeddings-list-editor__new-button").click()
16-
select_kit = PageObjects::Components::SelectKit.new(".ai-embedding-editor__presets")
17-
select_kit.expand
18-
select_kit.select_row_by_value(preset)
19-
find(".ai-embedding-editor__next").click
16+
17+
find("[data-preset-id='text-embedding-3-small'] button").click()
18+
2019
find("input.ai-embedding-editor__api-key").fill_in(with: api_key)
2120
find(".ai-embedding-editor__save").click()
2221

@@ -43,12 +42,10 @@
4342
visit "/admin/plugins/discourse-ai/ai-embeddings"
4443

4544
find(".ai-embeddings-list-editor__new-button").click()
46-
select_kit = PageObjects::Components::SelectKit.new(".ai-embedding-editor__presets")
47-
select_kit.expand
48-
select_kit.select_row_by_value("manual")
49-
find(".ai-embedding-editor__next").click
5045

51-
find("input.ai-embedding-editor__display-name").fill_in(with: "OpenAI's text-embedding-3-small")
46+
find("[data-preset-id='manual'] button").click()
47+
48+
find("input.ai-embedding-editor__display-name").fill_in(with: "text-embedding-3-small")
5249

5350
select_kit = PageObjects::Components::SelectKit.new(".ai-embedding-editor__provider")
5451
select_kit.expand
@@ -63,8 +60,8 @@
6360

6461
embed_prefix = "On creation:"
6562
search_prefix = "On search:"
66-
find("input.ai-embedding-editor__embed_prompt").fill_in(with: embed_prefix)
67-
find("input.ai-embedding-editor__search_prompt").fill_in(with: search_prefix)
63+
find(".ai-embedding-editor__embed_prompt").fill_in(with: embed_prefix)
64+
find(".ai-embedding-editor__search_prompt").fill_in(with: search_prefix)
6865

6966
find("input.ai-embedding-editor__dimensions").fill_in(with: 1536)
7067
find("input.ai-embedding-editor__max_sequence_length").fill_in(with: 8191)

svg-icons/icons-sprite.svg

Lines changed: 11 additions & 1 deletion
Loading

0 commit comments

Comments
 (0)