mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-03-06 17:30:20 +00:00
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
This commit is contained in:
parent
952e0a51d6
commit
99e73f09ff
@ -77,7 +77,7 @@ class EmbeddingDefinition < ActiveRecord::Base
|
|||||||
# indexes, so we downsample to 2000 via API.
|
# indexes, so we downsample to 2000 via API.
|
||||||
{
|
{
|
||||||
preset_id: "text-embedding-3-large",
|
preset_id: "text-embedding-3-large",
|
||||||
display_name: "OpenAI's text-embedding-3-large",
|
display_name: "text-embedding-3-large",
|
||||||
dimensions: 2000,
|
dimensions: 2000,
|
||||||
max_sequence_length: 8191,
|
max_sequence_length: 8191,
|
||||||
pg_function: "<=>",
|
pg_function: "<=>",
|
||||||
@ -91,7 +91,7 @@ class EmbeddingDefinition < ActiveRecord::Base
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
preset_id: "text-embedding-3-small",
|
preset_id: "text-embedding-3-small",
|
||||||
display_name: "OpenAI's text-embedding-3-small",
|
display_name: "text-embedding-3-small",
|
||||||
dimensions: 1536,
|
dimensions: 1536,
|
||||||
max_sequence_length: 8191,
|
max_sequence_length: 8191,
|
||||||
pg_function: "<=>",
|
pg_function: "<=>",
|
||||||
@ -105,7 +105,7 @@ class EmbeddingDefinition < ActiveRecord::Base
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
preset_id: "text-embedding-ada-002",
|
preset_id: "text-embedding-ada-002",
|
||||||
display_name: "OpenAI's text-embedding-ada-002",
|
display_name: "text-embedding-ada-002",
|
||||||
dimensions: 1536,
|
dimensions: 1536,
|
||||||
max_sequence_length: 8191,
|
max_sequence_length: 8191,
|
||||||
pg_function: "<=>",
|
pg_function: "<=>",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
import { Input } from "@ember/component";
|
import { Input, Textarea } from "@ember/component";
|
||||||
import { concat, get } from "@ember/helper";
|
import { concat, fn, get } from "@ember/helper";
|
||||||
import { on } from "@ember/modifier";
|
import { on } from "@ember/modifier";
|
||||||
import { action, computed } from "@ember/object";
|
import { action, computed } from "@ember/object";
|
||||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
@ -13,6 +13,8 @@ import DButton from "discourse/components/d-button";
|
|||||||
import icon from "discourse/helpers/d-icon";
|
import icon from "discourse/helpers/d-icon";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { i18n } from "discourse-i18n";
|
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 ComboBox from "select-kit/components/combo-box";
|
||||||
import DTooltip from "float-kit/components/d-tooltip";
|
import DTooltip from "float-kit/components/d-tooltip";
|
||||||
import not from "truth-helpers/helpers/not";
|
import not from "truth-helpers/helpers/not";
|
||||||
@ -48,7 +50,19 @@ export default class AiEmbeddingEditor extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return this.args.embeddings.resultSetMeta.distance_functions.map((df) => {
|
return this.args.embeddings.resultSetMeta.distance_functions.map((df) => {
|
||||||
return { id: df, name: t(df) };
|
let iconName;
|
||||||
|
|
||||||
|
if (df === "<=>") {
|
||||||
|
iconName = "discourse-spaceship-operator";
|
||||||
|
} else if (df === "<#>") {
|
||||||
|
iconName = "discourse-negative-inner-product";
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: df,
|
||||||
|
name: t(df),
|
||||||
|
icon: iconName,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,12 +71,14 @@ export default class AiEmbeddingEditor extends Component {
|
|||||||
return {
|
return {
|
||||||
name: preset.display_name,
|
name: preset.display_name,
|
||||||
id: preset.preset_id,
|
id: preset.preset_id,
|
||||||
|
provider: preset.provider,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
presets.pushObject({
|
presets.unshiftObject({
|
||||||
name: i18n("discourse_ai.embeddings.configure_manually"),
|
name: i18n("discourse_ai.embeddings.configure_manually"),
|
||||||
id: "manual",
|
id: "manual",
|
||||||
|
provider: "fake",
|
||||||
});
|
});
|
||||||
|
|
||||||
return presets;
|
return presets;
|
||||||
@ -90,11 +106,11 @@ export default class AiEmbeddingEditor extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
configurePreset() {
|
configurePreset(preset) {
|
||||||
this.selectedPreset =
|
this.selectedPreset =
|
||||||
this.args.embeddings.resultSetMeta.presets.findBy(
|
this.args.embeddings.resultSetMeta.presets.findBy(
|
||||||
"preset_id",
|
"preset_id",
|
||||||
this.presetId
|
preset.id
|
||||||
) || {};
|
) || {};
|
||||||
|
|
||||||
this.editingModel = this.store
|
this.editingModel = this.store
|
||||||
@ -185,35 +201,64 @@ export default class AiEmbeddingEditor extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
@action
|
||||||
<BackButton
|
resetForm() {
|
||||||
@route="adminPlugins.show.discourse-ai-embeddings"
|
this.selectedPreset = null;
|
||||||
@label="discourse_ai.embeddings.back"
|
this.editingModel = null;
|
||||||
/>
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
<form
|
<form
|
||||||
{{didInsert this.updateModel @model.id}}
|
{{didInsert this.updateModel @model.id}}
|
||||||
{{didUpdate this.updateModel @model.id}}
|
{{didUpdate this.updateModel @model.id}}
|
||||||
class="form-horizontal ai-embedding-editor"
|
class="form-horizontal ai-embedding-editor"
|
||||||
>
|
>
|
||||||
{{#if this.showPresets}}
|
{{#if this.showPresets}}
|
||||||
<div class="control-group">
|
<BackButton
|
||||||
<label>{{i18n "discourse_ai.embeddings.presets"}}</label>
|
@route="adminPlugins.show.discourse-ai-embeddings"
|
||||||
<ComboBox
|
@label="discourse_ai.embeddings.back"
|
||||||
@value={{this.presetId}}
|
|
||||||
@content={{this.presets}}
|
|
||||||
class="ai-embedding-editor__presets"
|
|
||||||
/>
|
/>
|
||||||
|
<div class="control-group">
|
||||||
|
<h2>{{i18n "discourse_ai.embeddings.presets"}}</h2>
|
||||||
|
<AdminSectionLandingWrapper>
|
||||||
|
{{#each this.presets as |preset|}}
|
||||||
|
<AdminSectionLandingItem
|
||||||
|
@titleLabelTranslated={{preset.name}}
|
||||||
|
@taglineLabel={{concat
|
||||||
|
"discourse_ai.embeddings.providers."
|
||||||
|
preset.provider
|
||||||
|
}}
|
||||||
|
data-preset-id={{preset.id}}
|
||||||
|
class="ai-llms-list-editor__templates-list-item"
|
||||||
|
>
|
||||||
|
<:buttons as |buttons|>
|
||||||
|
<buttons.Default
|
||||||
|
@action={{fn this.configurePreset preset}}
|
||||||
|
@icon="gear"
|
||||||
|
@label="discourse_ai.llms.preconfigured.button"
|
||||||
|
/>
|
||||||
|
</:buttons>
|
||||||
|
</AdminSectionLandingItem>
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
</AdminSectionLandingWrapper>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group ai-llm-editor__action_panel">
|
|
||||||
<DButton
|
|
||||||
@action={{this.configurePreset}}
|
|
||||||
@label="discourse_ai.tools.next.title"
|
|
||||||
class="ai-embedding-editor__next"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
|
{{#if this.editingModel.isNew}}
|
||||||
|
<DButton
|
||||||
|
@action={{this.resetForm}}
|
||||||
|
@label="back_button"
|
||||||
|
@icon="chevron-left"
|
||||||
|
class="btn-flat back-button"
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
<BackButton
|
||||||
|
@route="adminPlugins.show.discourse-ai-embeddings"
|
||||||
|
@label="discourse_ai.embeddings.back"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{i18n "discourse_ai.embeddings.display_name"}}</label>
|
<label>{{i18n "discourse_ai.embeddings.display_name"}}</label>
|
||||||
<Input
|
<Input
|
||||||
@ -295,27 +340,38 @@ export default class AiEmbeddingEditor extends Component {
|
|||||||
@type="checkbox"
|
@type="checkbox"
|
||||||
@checked={{this.editingModel.matryoshka_dimensions}}
|
@checked={{this.editingModel.matryoshka_dimensions}}
|
||||||
/>
|
/>
|
||||||
<label>{{i18n
|
<label>{{i18n "discourse_ai.embeddings.matryoshka_dimensions"}}
|
||||||
"discourse_ai.embeddings.matryoshka_dimensions"
|
</label>
|
||||||
}}</label>
|
<DTooltip
|
||||||
|
@icon="circle-question"
|
||||||
|
@content={{i18n
|
||||||
|
"discourse_ai.embeddings.hints.matryoshka_dimensions"
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{i18n "discourse_ai.embeddings.embed_prompt"}}</label>
|
<label>{{i18n "discourse_ai.embeddings.embed_prompt"}}</label>
|
||||||
<Input
|
<Textarea
|
||||||
@type="text"
|
|
||||||
class="ai-embedding-editor-input ai-embedding-editor__embed_prompt"
|
class="ai-embedding-editor-input ai-embedding-editor__embed_prompt"
|
||||||
@value={{this.editingModel.embed_prompt}}
|
@value={{this.editingModel.embed_prompt}}
|
||||||
/>
|
/>
|
||||||
|
<DTooltip
|
||||||
|
@icon="circle-question"
|
||||||
|
@content={{i18n "discourse_ai.embeddings.hints.embed_prompt"}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{i18n "discourse_ai.embeddings.search_prompt"}}</label>
|
<label>{{i18n "discourse_ai.embeddings.search_prompt"}}</label>
|
||||||
<Input
|
<Textarea
|
||||||
@type="text"
|
|
||||||
class="ai-embedding-editor-input ai-embedding-editor__search_prompt"
|
class="ai-embedding-editor-input ai-embedding-editor__search_prompt"
|
||||||
@value={{this.editingModel.search_prompt}}
|
@value={{this.editingModel.search_prompt}}
|
||||||
/>
|
/>
|
||||||
|
<DTooltip
|
||||||
|
@icon="circle-question"
|
||||||
|
@content={{i18n "discourse_ai.embeddings.hints.search_prompt"}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@ -329,6 +385,10 @@ export default class AiEmbeddingEditor extends Component {
|
|||||||
@value={{this.editingModel.max_sequence_length}}
|
@value={{this.editingModel.max_sequence_length}}
|
||||||
required="true"
|
required="true"
|
||||||
/>
|
/>
|
||||||
|
<DTooltip
|
||||||
|
@icon="circle-question"
|
||||||
|
@content={{i18n "discourse_ai.embeddings.hints.sequence_length"}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@ -338,6 +398,10 @@ export default class AiEmbeddingEditor extends Component {
|
|||||||
@content={{this.distanceFunctions}}
|
@content={{this.distanceFunctions}}
|
||||||
@class="ai-embedding-editor__distance_functions"
|
@class="ai-embedding-editor__distance_functions"
|
||||||
/>
|
/>
|
||||||
|
<DTooltip
|
||||||
|
@icon="circle-question"
|
||||||
|
@content={{i18n "discourse_ai.embeddings.hints.distance_function"}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#each-in this.metaProviderParams as |field type|}}
|
{{#each-in this.metaProviderParams as |field type|}}
|
||||||
|
@ -28,4 +28,47 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__distance_functions.select-kit {
|
||||||
|
.selected-name {
|
||||||
|
.d-icon {
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
+ .name {
|
||||||
|
margin-left: 2.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon-title {
|
||||||
|
width: 2em;
|
||||||
|
top: -0.5em;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-ai-embeddings {
|
||||||
|
.btn-flat.back-button {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fk-d-tooltip__icon {
|
||||||
|
margin-left: 0.25em;
|
||||||
|
color: var(--primary-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea + .fk-d-tooltip__trigger {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-icon-circle-exclamation {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -522,7 +522,11 @@ en:
|
|||||||
failure: "Attempting to generate an embedding resulted in: %{error}"
|
failure: "Attempting to generate an embedding resulted in: %{error}"
|
||||||
hints:
|
hints:
|
||||||
dimensions_warning: "Once saved, this value can't be changed."
|
dimensions_warning: "Once saved, this value can't be changed."
|
||||||
|
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."
|
||||||
|
embed_prompt: "Tells the LLM how to process text to create its numerical summary (embedding) for analysis or comparison."
|
||||||
|
search_prompt: "Tells the LLM how to compare a search query with existing embeddings and find the best matches."
|
||||||
|
sequence_length: "The maximum number of tokens that can be processed at once when creating embeddings or handling a query."
|
||||||
|
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)."
|
||||||
display_name: "Name"
|
display_name: "Name"
|
||||||
provider: "Provider"
|
provider: "Provider"
|
||||||
url: "Embeddings service URL"
|
url: "Embeddings service URL"
|
||||||
@ -536,18 +540,18 @@ en:
|
|||||||
|
|
||||||
distance_function: "Distance function"
|
distance_function: "Distance function"
|
||||||
distance_functions:
|
distance_functions:
|
||||||
<#>: "Negative inner product (<#>)"
|
<#>: "Negative inner product"
|
||||||
<=>: "Cosine distance (<=>)"
|
<=>: "Cosine distance"
|
||||||
providers:
|
providers:
|
||||||
hugging_face: "Hugging Face"
|
hugging_face: "Hugging Face"
|
||||||
open_ai: "OpenAI"
|
open_ai: "OpenAI"
|
||||||
google: "Google"
|
google: "Google"
|
||||||
cloudflare: "Cloudflare"
|
cloudflare: "Cloudflare"
|
||||||
CDCK: "CDCK"
|
CDCK: "CDCK"
|
||||||
|
fake: "Custom"
|
||||||
provider_fields:
|
provider_fields:
|
||||||
model_name: "Model name"
|
model_name: "Model name"
|
||||||
|
|
||||||
|
|
||||||
semantic_search: "Topics (Semantic)"
|
semantic_search: "Topics (Semantic)"
|
||||||
semantic_search_loading: "Searching for more results using AI"
|
semantic_search_loading: "Searching for more results using AI"
|
||||||
semantic_search_results:
|
semantic_search_results:
|
||||||
|
@ -13,10 +13,9 @@ RSpec.describe "Managing Embeddings configurations", type: :system, js: true do
|
|||||||
visit "/admin/plugins/discourse-ai/ai-embeddings"
|
visit "/admin/plugins/discourse-ai/ai-embeddings"
|
||||||
|
|
||||||
find(".ai-embeddings-list-editor__new-button").click()
|
find(".ai-embeddings-list-editor__new-button").click()
|
||||||
select_kit = PageObjects::Components::SelectKit.new(".ai-embedding-editor__presets")
|
|
||||||
select_kit.expand
|
find("[data-preset-id='text-embedding-3-small'] button").click()
|
||||||
select_kit.select_row_by_value(preset)
|
|
||||||
find(".ai-embedding-editor__next").click
|
|
||||||
find("input.ai-embedding-editor__api-key").fill_in(with: api_key)
|
find("input.ai-embedding-editor__api-key").fill_in(with: api_key)
|
||||||
find(".ai-embedding-editor__save").click()
|
find(".ai-embedding-editor__save").click()
|
||||||
|
|
||||||
@ -43,12 +42,10 @@ RSpec.describe "Managing Embeddings configurations", type: :system, js: true do
|
|||||||
visit "/admin/plugins/discourse-ai/ai-embeddings"
|
visit "/admin/plugins/discourse-ai/ai-embeddings"
|
||||||
|
|
||||||
find(".ai-embeddings-list-editor__new-button").click()
|
find(".ai-embeddings-list-editor__new-button").click()
|
||||||
select_kit = PageObjects::Components::SelectKit.new(".ai-embedding-editor__presets")
|
|
||||||
select_kit.expand
|
|
||||||
select_kit.select_row_by_value("manual")
|
|
||||||
find(".ai-embedding-editor__next").click
|
|
||||||
|
|
||||||
find("input.ai-embedding-editor__display-name").fill_in(with: "OpenAI's text-embedding-3-small")
|
find("[data-preset-id='manual'] button").click()
|
||||||
|
|
||||||
|
find("input.ai-embedding-editor__display-name").fill_in(with: "text-embedding-3-small")
|
||||||
|
|
||||||
select_kit = PageObjects::Components::SelectKit.new(".ai-embedding-editor__provider")
|
select_kit = PageObjects::Components::SelectKit.new(".ai-embedding-editor__provider")
|
||||||
select_kit.expand
|
select_kit.expand
|
||||||
@ -63,8 +60,8 @@ RSpec.describe "Managing Embeddings configurations", type: :system, js: true do
|
|||||||
|
|
||||||
embed_prefix = "On creation:"
|
embed_prefix = "On creation:"
|
||||||
search_prefix = "On search:"
|
search_prefix = "On search:"
|
||||||
find("input.ai-embedding-editor__embed_prompt").fill_in(with: embed_prefix)
|
find(".ai-embedding-editor__embed_prompt").fill_in(with: embed_prefix)
|
||||||
find("input.ai-embedding-editor__search_prompt").fill_in(with: search_prefix)
|
find(".ai-embedding-editor__search_prompt").fill_in(with: search_prefix)
|
||||||
|
|
||||||
find("input.ai-embedding-editor__dimensions").fill_in(with: 1536)
|
find("input.ai-embedding-editor__dimensions").fill_in(with: 1536)
|
||||||
find("input.ai-embedding-editor__max_sequence_length").fill_in(with: 8191)
|
find("input.ai-embedding-editor__max_sequence_length").fill_in(with: 8191)
|
||||||
|
@ -13,4 +13,14 @@
|
|||||||
<symbol id="discourse-table-sparkles" viewBox="0 0 512 512">
|
<symbol id="discourse-table-sparkles" viewBox="0 0 512 512">
|
||||||
<path d="M448 309.64V413H64V290h190c12.23 0 18.46-14.71 9.94-23.48-7.56-7.78-14.48-16.2-20.66-25.16-5.36-7.77-14.24-12.36-23.69-12.36H64V93h130c10.65 0 20.14-6.92 23.18-17.12a193.4 193.4 0 0 1 9.74-25.76c4.56-9.86-2.77-21.12-13.63-21.12H64C28.65 29 0 57.65 0 93v320c0 35.35 28.65 64 64 64h384c35.35 0 64-28.65 64-64V263.8c0-9.16-10.7-14.14-17.7-8.25a194.056 194.056 0 0 1-25.76 18.32c-12.66 7.53-20.53 21.05-20.53 35.77Z"/><path d="M247.46 122.15c-6.45 2.44-10.75 8.6-10.75 15.49s4.3 13.05 10.75 15.49l81.01 30.4 30.4 81.01c2.44 6.45 8.6 10.75 15.49 10.75s13.05-4.3 15.49-10.75l30.4-81.01 81.01-30.4c6.45-2.44 10.75-8.6 10.75-15.49s-4.3-13.05-10.75-15.49l-81.01-30.4-30.4-81.01c-2.44-6.45-8.6-10.75-15.49-10.75s-13.05 4.3-15.49 10.75l-30.4 81.01-81.01 30.4Z"/>
|
<path d="M448 309.64V413H64V290h190c12.23 0 18.46-14.71 9.94-23.48-7.56-7.78-14.48-16.2-20.66-25.16-5.36-7.77-14.24-12.36-23.69-12.36H64V93h130c10.65 0 20.14-6.92 23.18-17.12a193.4 193.4 0 0 1 9.74-25.76c4.56-9.86-2.77-21.12-13.63-21.12H64C28.65 29 0 57.65 0 93v320c0 35.35 28.65 64 64 64h384c35.35 0 64-28.65 64-64V263.8c0-9.16-10.7-14.14-17.7-8.25a194.056 194.056 0 0 1-25.76 18.32c-12.66 7.53-20.53 21.05-20.53 35.77Z"/><path d="M247.46 122.15c-6.45 2.44-10.75 8.6-10.75 15.49s4.3 13.05 10.75 15.49l81.01 30.4 30.4 81.01c2.44 6.45 8.6 10.75 15.49 10.75s13.05-4.3 15.49-10.75l30.4-81.01 81.01-30.4c6.45-2.44 10.75-8.6 10.75-15.49s-4.3-13.05-10.75-15.49l-81.01-30.4-30.4-81.01c-2.44-6.45-8.6-10.75-15.49-10.75s-13.05 4.3-15.49 10.75l-30.4 81.01-81.01 30.4Z"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
|
<!-- "Discourse Spaceship Operator" is a Discourse derivative of https://fontawesome.com/icons/equals?f=classic&s=solid and https://fontawesome.com/icons/less-than?f=classic&s=solid -->
|
||||||
|
<symbol id="discourse-spaceship-operator" viewBox="0 0 448 512">
|
||||||
|
<path d="M132.3 195.8c2.7 5.5.5 12.2-5 14.9L36 256.4l91.3 45.7c5.5 2.7 7.7 9.4 5 14.9s-9.4 7.7-14.9 5L6.2 266.4c-3.8-1.9-6.2-5.7-6.2-9.9s2.4-8.1 6.2-9.9L117.5 191c5.5-2.7 12.2-.5 14.9 5ZM315.7 317c-2.7-5.5-.5-12.2 5-14.9l91.3-45.7-91.3-45.7c-5.5-2.7-7.7-9.4-5-14.9s9.4-7.7 14.9-5l111.3 55.6c3.8 1.9 6.2 5.7 6.2 9.9s-2.4 8.1-6.2 9.9l-111.3 55.6c-5.5 2.7-12.2.5-14.9-5ZM169.2 211.5c-5.6 0-10.1 5.3-10.1 11.8s4.5 11.8 10.1 11.8h110.9c5.6 0 10.1-5.3 10.1-11.8s-4.5-11.8-10.1-11.8H169.2Zm0 65.9c-5.6 0-10.1 5.3-10.1 11.8s4.5 11.8 10.1 11.8h110.9c5.6 0 10.1-5.3 10.1-11.8s-4.5-11.8-10.1-11.8H169.2Z"/>
|
||||||
|
</symbol>
|
||||||
|
|
||||||
|
<!-- "Discourse Negative Inner Product" is a Discourse derivative of https://fontawesome.com/icons/hashtag?f=classic&s=solid and https://fontawesome.com/icons/less-than?f=classic&s=solid -->
|
||||||
|
<symbol id="discourse-negative-inner-product" viewBox="0 0 448 512">
|
||||||
|
<path d="M132.3 195.8c2.7 5.5.5 12.2-5 14.9L36 256.4l91.3 45.7c5.5 2.7 7.7 9.4 5 14.9s-9.4 7.7-14.9 5L6.2 266.4c-3.8-1.9-6.2-5.7-6.2-9.9s2.4-8.1 6.2-9.9L117.5 191c5.5-2.7 12.2-.5 14.9 5ZM315.7 317c-2.7-5.5-.5-12.2 5-14.9l91.3-45.7-91.3-45.7c-5.5-2.7-7.7-9.4-5-14.9s9.4-7.7 14.9-5l111.3 55.6c3.8 1.9 6.2 5.7 6.2 9.9s-2.4 8.1-6.2 9.9l-111.3 55.6c-5.5 2.7-12.2.5-14.9-5ZM211.9 187.6c5.2.9 8.8 5.8 7.9 11.1l-3 17.7h28.6l3.5-20.9c.9-5.2 5.8-8.8 11.1-7.9 5.2.9 8.8 5.8 7.9 11.1l-2.9 17.7h17.5c5.3 0 9.6 4.3 9.6 9.6s-4.3 9.6-9.6 9.6h-20.7l-6.4 38.5h17.5c5.3 0 9.6 4.3 9.6 9.6s-4.3 9.6-9.6 9.6h-20.7l-3.5 20.9c-.9 5.2-5.8 8.8-11.1 7.9s-8.8-5.8-7.9-11.1l3-17.7h-28.6l-3.5 20.9c-.9 5.2-5.8 8.8-11.1 7.9-5.2-.9-8.8-5.8-7.9-11.1l2.9-17.7H167c-5.3 0-9.6-4.3-9.6-9.6s4.3-9.6 9.6-9.6h20.7l6.4-38.5h-17.5c-5.3 0-9.6-4.3-9.6-9.6s4.3-9.6 9.6-9.6h20.7l3.5-20.9c.9-5.2 5.8-8.8 11.1-7.9Zm1.7 48-6.4 38.5h28.6l6.4-38.5h-28.6Z"/>
|
||||||
|
</symbol>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.5 KiB |
Loading…
x
Reference in New Issue
Block a user