Compare commits

..
Author SHA1 Message Date
Keegan George 943aa7468d FIX: Final summary sometimes missing
This PR fixes an issue where the summary updater sometimes doesn't show the last update. Due to some timing issues with the summary updater and the component this seems to happen. By explicitly calling set raw after the cooked, we ensure the final summary will always be set.

No tests as this timing issue is difficult to test.
2024-11-06 12:00:35 -08:00
270 changed files with 4313 additions and 8828 deletions
+1
View File
@@ -0,0 +1 @@
module.exports = require("@discourse/lint-configs/eslint");
@@ -1,87 +0,0 @@
# frozen_string_literal: true
module DiscourseAi
module AiBot
class ArtifactsController < ApplicationController
requires_plugin DiscourseAi::PLUGIN_NAME
before_action :require_site_settings!
skip_before_action :preload_json, :check_xhr, only: %i[show]
def show
artifact = AiArtifact.find(params[:id])
post = Post.find_by(id: artifact.post_id)
if artifact.metadata&.dig("public")
# no guardian needed
else
raise Discourse::NotFound if !post&.topic&.private_message?
raise Discourse::NotFound if !guardian.can_see?(post)
end
# Prepare the inner (untrusted) HTML document
untrusted_html = <<~HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>#{ERB::Util.html_escape(artifact.name)}</title>
<style>
#{artifact.css}
</style>
</head>
<body>
#{artifact.html}
<script>
#{artifact.js}
</script>
</body>
</html>
HTML
# Prepare the outer (trusted) HTML document
trusted_html = <<~HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>#{ERB::Util.html_escape(artifact.name)}</title>
<style>
html, body, iframe {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
border: 0;
overflow: hidden;
}
iframe {
overflow: auto;
}
</style>
</head>
<body>
<iframe sandbox="allow-scripts allow-forms" height="100%" width="100%" srcdoc="#{ERB::Util.html_escape(untrusted_html)}" frameborder="0"></iframe>
</body>
</html>
HTML
response.headers.delete("X-Frame-Options")
response.headers["Content-Security-Policy"] = "script-src 'unsafe-inline';"
response.headers["X-Robots-Tag"] = "noindex"
# Render the content
render html: trusted_html.html_safe, layout: false, content_type: "text/html"
end
private
def require_site_settings!
if !SiteSetting.discourse_ai_enabled ||
!SiteSetting.ai_artifact_security.in?(%w[lax strict])
raise Discourse::NotFound
end
end
end
end
end
@@ -6,14 +6,6 @@ module DiscourseAi
requires_plugin ::DiscourseAi::PLUGIN_NAME
requires_login
def show_debug_info_by_id
log = AiApiAuditLog.find(params[:id])
raise Discourse::NotFound if !log.topic
guardian.ensure_can_debug_ai_bot_conversation!(log.topic)
render json: AiApiAuditLogSerializer.new(log, root: false), status: 200
end
def show_debug_info
post = Post.find(params[:post_id])
guardian.ensure_can_debug_ai_bot_conversation!(post)
@@ -7,8 +7,7 @@ module DiscourseAi
requires_login only: %i[create update destroy]
before_action :require_site_settings!
skip_before_action :preload_json, :check_xhr, only: %i[show asset]
skip_before_action :verify_authenticity_token, only: ["asset"]
skip_before_action :preload_json, :check_xhr, only: %i[show]
def create
ensure_allowed_create!
@@ -51,30 +50,6 @@ module DiscourseAi
end
end
def asset
no_cookies
name = params[:name]
path, content_type =
if name == "share"
%w[share.css text/css]
elsif name == "highlight"
%w[highlight.min.js application/javascript]
else
raise Discourse::NotFound
end
content = File.read(DiscourseAi.public_asset_path("ai-share/#{path}"))
# note, path contains a ":version" which automatically busts the cache
# based on file content, so this is safe
response.headers["Last-Modified"] = 10.years.ago.httpdate
response.headers["Content-Length"] = content.bytesize.to_s
immutable_for 1.year
render plain: content, disposition: :nil, content_type: content_type
end
def preview
ensure_allowed_preview!
data = SharedAiConversation.build_conversation_data(@topic, include_usernames: true)
@@ -2,29 +2,13 @@
module DiscourseAi
module AiBot
module SharedAiConversationsHelper
# keeping it here for caching
def self.share_asset_url(asset_name)
if !%w[share.css highlight.js].include?(asset_name)
raise StandardError, "unknown asset type #{asset_name}"
end
# bump up version when assets change
# long term we may want to change this cause it is hard to remember
# to bump versions, but for now this does the job
VERSION = "1"
@urls ||= {}
url = @urls[asset_name]
return url if url
path = asset_name
path = "highlight.min.js" if asset_name == "highlight.js"
content = File.read(DiscourseAi.public_asset_path("ai-share/#{path}"))
sha1 = Digest::SHA1.hexdigest(content)
url = "/discourse-ai/ai-bot/shared-ai-conversations/asset/#{sha1}/#{asset_name}"
@urls[asset_name] = GlobalPath.cdn_path(url)
end
def share_asset_url(asset_name)
DiscourseAi::AiBot::SharedAiConversationsHelper.share_asset_url(asset_name)
def share_asset_url(short_path)
::UrlHelper.absolute("/plugins/discourse-ai/ai-share/#{short_path}?#{VERSION}")
end
end
end
+13 -27
View File
@@ -10,48 +10,34 @@ module ::Jobs
return if !SiteSetting.ai_summarization_enabled
return if SiteSetting.ai_summary_backfill_maximum_topics_per_hour.zero?
system_user = Discourse.system_user
# Split budget in 12 intervals, but make sure is at least one.
limit_per_job = [SiteSetting.ai_summary_backfill_maximum_topics_per_hour, 12].max / 12
budget = [current_budget, limit_per_job].min
complete_t = AiSummary.summary_types[:complete]
backfill_candidates(complete_t)
.limit(current_budget(complete_t))
backfill_candidates
.limit(budget)
.each do |topic|
DiscourseAi::Summarization.topic_summary(topic).force_summarize(system_user)
DiscourseAi::Summarization.topic_summary(topic).force_summarize(Discourse.system_user)
end
gist_t = AiSummary.summary_types[:gist]
backfill_candidates(gist_t)
.limit(current_budget(gist_t))
.each { |topic| DiscourseAi::Summarization.topic_gist(topic).force_summarize(system_user) }
end
def backfill_candidates(summary_type)
def backfill_candidates
Topic
.where("topics.word_count >= ?", SiteSetting.ai_summary_backfill_minimum_word_count)
.joins(<<~SQL)
LEFT OUTER JOIN ai_summaries ais ON
topics.id = ais.target_id AND
ais.target_type = 'Topic' AND
ais.summary_type = '#{summary_type}'
SQL
.joins(
"LEFT OUTER JOIN ai_summaries ais ON topics.id = ais.target_id AND ais.target_type = 'Topic'",
)
.where(
"ais.id IS NULL OR UPPER(ais.content_range) < topics.highest_post_number + 1",
) # (1..1) gets stored ad (1..2).
.order("ais.created_at DESC NULLS FIRST, topics.last_posted_at DESC")
end
def current_budget(type)
# Split budget in 12 intervals, but make sure is at least one.
def current_budget
base_budget = SiteSetting.ai_summary_backfill_maximum_topics_per_hour
limit_per_job = [base_budget, 12].max / 12
used_budget = AiSummary.complete.system.where("created_at > ?", 1.hour.ago).count
used_budget =
AiSummary.system.where("created_at > ?", 1.hour.ago).where(summary_type: type).count
current_budget = [(base_budget - used_budget), limit_per_job].min
return 0 if current_budget < 0
current_budget
base_budget - used_budget
end
end
end
-9
View File
@@ -13,15 +13,6 @@ class AiApiAuditLog < ActiveRecord::Base
Cohere = 6
Ollama = 7
SambaNova = 8
Mistral = 9
end
def next_log_id
self.class.where("id > ?", id).where(topic_id: topic_id).order(id: :asc).pluck(:id).first
end
def prev_log_id
self.class.where("id < ?", id).where(topic_id: topic_id).order(id: :desc).pluck(:id).first
end
end
-52
View File
@@ -1,52 +0,0 @@
# frozen_string_literal: true
class AiArtifact < ActiveRecord::Base
belongs_to :user
belongs_to :post
validates :html, length: { maximum: 65_535 }
validates :css, length: { maximum: 65_535 }
validates :js, length: { maximum: 65_535 }
def self.iframe_for(id)
<<~HTML
<div class='ai-artifact'>
<iframe src='#{url(id)}' frameborder="0" height="100%" width="100%"></iframe>
<a href='#{url(id)}' target='_blank'>#{I18n.t("discourse_ai.ai_artifact.link")}</a>
</div>
HTML
end
def self.url(id)
Discourse.base_url + "/discourse-ai/ai-bot/artifacts/#{id}"
end
def self.share_publicly(id:, post:)
artifact = AiArtifact.find_by(id: id)
artifact.update!(metadata: { public: true }) if artifact&.post&.topic&.id == post.topic.id
end
def self.unshare_publicly(id:)
artifact = AiArtifact.find_by(id: id)
artifact&.update!(metadata: { public: false })
end
def url
self.class.url(id)
end
end
# == Schema Information
#
# Table name: ai_artifacts
#
# id :bigint not null, primary key
# user_id :integer not null
# post_id :integer not null
# name :string(255) not null
# html :string(65535)
# css :string(65535)
# js :string(65535)
# metadata :jsonb
# created_at :datetime not null
# updated_at :datetime not null
#
+1 -2
View File
@@ -51,6 +51,5 @@ end
#
# Indexes
#
# idx_on_target_id_target_type_summary_type_3355609fbb (target_id,target_type,summary_type) UNIQUE
# index_ai_summaries_on_target_type_and_target_id (target_type,target_id)
# index_ai_summaries_on_target_type_and_target_id (target_type,target_id)
#
-1
View File
@@ -8,7 +8,6 @@ class CompletionPrompt < ActiveRecord::Base
CUSTOM_PROMPT = -305
EXPLAIN = -306
ILLUSTRATE_POST = -308
DETECT_TEXT_LOCALE = -309
enum :prompt_type, { text: 0, list: 1, diff: 2 }
+1 -20
View File
@@ -26,16 +26,6 @@ class LlmModel < ActiveRecord::Base
},
open_ai: {
organization: :text,
disable_native_tools: :checkbox,
},
mistral: {
disable_native_tools: :checkbox,
},
google: {
disable_native_tools: :checkbox,
},
azure: {
disable_native_tools: :checkbox,
},
hugging_face: {
disable_system_prompt: :checkbox,
@@ -105,16 +95,7 @@ class LlmModel < ActiveRecord::Base
end
def seeded?
id.present? && id < 0
end
def api_key
if seeded?
env_key = "DISCOURSE_AI_SEEDED_LLM_API_KEY_#{id.abs}"
ENV[env_key] || self[:api_key]
else
self[:api_key]
end
id < 0
end
private
+1 -25
View File
@@ -34,12 +34,6 @@ class SharedAiConversation < ActiveRecord::Base
def self.destroy_conversation(conversation)
conversation.destroy
maybe_topic = conversation.target
if maybe_topic.is_a?(Topic)
AiArtifact.where(post: maybe_topic.posts).update_all(metadata: { public: false })
end
::Jobs.enqueue(
:shared_conversation_adjust_upload_security,
target_id: conversation.target_id,
@@ -171,7 +165,7 @@ class SharedAiConversation < ActiveRecord::Base
id: post.id,
user_id: post.user_id,
created_at: post.created_at,
cooked: cook_artifacts(post),
cooked: post.cooked,
}
mapped[:persona] = persona if ai_bot_participant&.id == post.user_id
@@ -181,24 +175,6 @@ class SharedAiConversation < ActiveRecord::Base
}
end
def self.cook_artifacts(post)
html = post.cooked
return html if !%w[lax strict].include?(SiteSetting.ai_artifact_security)
doc = Nokogiri::HTML5.fragment(html)
doc
.css("div.ai-artifact")
.each do |node|
id = node["data-ai-artifact-id"].to_i
if id > 0
AiArtifact.share_publicly(id: id, post: post)
node.replace(AiArtifact.iframe_for(id))
end
end
doc.to_s
end
private
def populate_user_info!(posts)
@@ -12,7 +12,5 @@ class AiApiAuditLogSerializer < ApplicationSerializer
:post_id,
:feature_name,
:language_model,
:created_at,
:prev_log_id,
:next_log_id
:created_at
end
@@ -10,7 +10,7 @@
<meta name="twitter:title" content="<%= I18n.t("discourse_ai.share_ai.title", title: @shared_conversation.title, site_name: SiteSetting.title) %>">
<meta name="twitter:description" content="<%= @shared_conversation.formatted_excerpt %>">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="<%= share_asset_url("share.css") %>">
<link rel="stylesheet" href="<%= ::UrlHelper.absolute("/plugins/discourse-ai/ai-share/share.css") %>">
</head>
<body>
<header class="site-header">
@@ -54,7 +54,7 @@
</article>
<% end %>
</section>
<script src="<%= share_asset_url("highlight.js") %>" nonce="<%= csp_nonce_placeholder %>" ></script>
<script src="<%= share_asset_url("highlight.min.js") %>" nonce="<%= csp_nonce_placeholder %>" ></script>
<script nonce="<%= csp_nonce_placeholder %>">
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
@@ -1,45 +0,0 @@
<div class="cell title">
{{#if this.model.icon}}
{{d-icon this.model.icon}}
{{/if}}
{{this.model.title}}
</div>
<div class="cell value today-count">
<a href={{this.todayLink}}>
{{number this.model.todayCount}}
</a>
</div>
<div
class="cell value yesterday-count {{this.model.yesterdayTrend}}"
title={{this.model.yesterdayCountTitle}}
>
<a href={{this.yesterdayLink}}>
{{number this.model.yesterdayCount}}
</a>
{{d-icon this.model.yesterdayTrendIcon}}
</div>
<div
class="cell value sevendays-count {{this.model.sevenDaysTrend}}"
title={{this.model.sevenDaysCountTitle}}
>
<a href={{this.lastSevenDaysLink}}>
{{number this.model.lastSevenDaysCount}}
</a>
{{d-icon this.model.sevenDaysTrendIcon}}
</div>
<div
class="cell value thirty-days-count {{this.model.thirtyDaysTrend}}"
title={{this.model.thirtyDaysCountTitle}}
>
<a href={{this.lastThirtyDaysLink}}>
{{number this.model.lastThirtyDaysCount}}
</a>
{{#if this.model.canDisplayTrendIcon}}
{{d-icon this.model.thirtyDaysTrendIcon}}
{{/if}}
</div>
@@ -1,39 +0,0 @@
import Component from "@ember/component";
import { attributeBindings, classNames } from "@ember-decorators/component";
import getURL from "discourse-common/lib/get-url";
@classNames("admin-report-counters")
@attributeBindings("model.description:title")
export default class AdminReportEmotion extends Component {
get todayLink() {
let date = moment().format("YYYY-MM-DD");
return this._filterURL(date);
}
get yesterdayLink() {
let date = moment().subtract(1, "day").format("YYYY-MM-DD");
return this._filterURL(date);
}
get lastSevenDaysLink() {
let date = moment().subtract(1, "week").format("YYYY-MM-DD");
return this._filterURL(date);
}
get lastThirtyDaysLink() {
let date = moment().subtract(1, "month").format("YYYY-MM-DD");
return this._filterURL(date);
}
_baseFilter() {
return "/filter?q=activity-after%3A";
}
_model() {
return "%20order%3A" + this.model.type;
}
_filterURL(date) {
return getURL(`${this._baseFilter()}${date}${this._model()}`);
}
}
@@ -1,121 +0,0 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
import htmlClass from "discourse/helpers/html-class";
import getURL from "discourse-common/lib/get-url";
export default class AiArtifactComponent extends Component {
@service siteSettings;
@tracked expanded = false;
@tracked showingArtifact = false;
constructor() {
super(...arguments);
this.keydownHandler = this.handleKeydown.bind(this);
}
willDestroy() {
super.willDestroy(...arguments);
window.removeEventListener("keydown", this.keydownHandler);
}
@action
handleKeydown(event) {
if (event.key === "Escape" || event.key === "Esc") {
this.expanded = false;
}
}
get requireClickToRun() {
if (this.showingArtifact) {
return false;
}
return this.siteSettings.ai_artifact_security === "strict";
}
get artifactUrl() {
return getURL(`/discourse-ai/ai-bot/artifacts/${this.args.artifactId}`);
}
@action
showArtifact() {
this.showingArtifact = true;
}
@action
toggleView() {
this.expanded = !this.expanded;
if (this.expanded) {
window.addEventListener("keydown", this.keydownHandler);
} else {
window.removeEventListener("keydown", this.keydownHandler);
}
}
get wrapperClasses() {
return `ai-artifact__wrapper ${
this.expanded ? "ai-artifact__expanded" : ""
}`;
}
@action
artifactPanelHover() {
// retrrigger animation
const panel = document.querySelector(".ai-artifact__panel");
panel.style.animation = "none"; // Stop the animation
setTimeout(() => {
panel.style.animation = ""; // Re-trigger the animation by removing the none style
}, 0);
}
<template>
{{#if this.expanded}}
{{htmlClass "ai-artifact-expanded"}}
{{/if}}
<div class={{this.wrapperClasses}}>
<div
class="ai-artifact__panel--wrapper"
{{on "mouseleave" this.artifactPanelHover}}
>
<div class="ai-artifact__panel">
<DButton
class="btn-flat btn-icon-text"
@icon="discourse-compress"
@label="discourse_ai.ai_artifact.collapse_view_label"
@action={{this.toggleView}}
/>
</div>
</div>
{{#if this.requireClickToRun}}
<div class="ai-artifact__click-to-run">
<DButton
class="btn btn-primary"
@icon="play"
@label="discourse_ai.ai_artifact.click_to_run_label"
@action={{this.showArtifact}}
/>
</div>
{{else}}
<iframe
title="AI Artifact"
src={{this.artifactUrl}}
width="100%"
frameborder="0"
></iframe>
{{/if}}
{{#unless this.requireClickToRun}}
<div class="ai-artifact__footer">
<DButton
class="btn-flat btn-icon-text ai-artifact__expand-button"
@icon="discourse-expand"
@label="discourse_ai.ai_artifact.expand_view_label"
@action={{this.toggleView}}
/>
</div>
{{/unless}}
</div>
</template>
}
@@ -4,43 +4,35 @@ import { action } from "@ember/object";
import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
import DropdownMenu from "discourse/components/dropdown-menu";
import bodyClass from "discourse/helpers/body-class";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import DMenu from "float-kit/components/d-menu";
import eq from "truth-helpers/helpers/eq";
export default class AiGistToggle extends Component {
@service gists;
@service router;
@service gistPreference;
get shouldShow() {
return this.router.currentRoute.attributes?.list?.topics?.some(
(topic) => topic.ai_topic_gist
);
}
get buttons() {
return [
{
id: "table",
label: "discourse_ai.summarization.topic_list_layout.button.compact",
icon: "discourse-table",
id: "gists_enabled",
label: "discourse_ai.summarization.gists_enabled_long",
icon: "discourse-sparkles",
},
{
id: "table-ai",
label: "discourse_ai.summarization.topic_list_layout.button.expanded",
icon: "discourse-table-sparkles",
description:
"discourse_ai.summarization.topic_list_layout.button.expanded_description",
id: "gists_disabled",
label: "discourse_ai.summarization.gists_disabled",
icon: "far-eye-slash",
},
];
}
get selectedOptionId() {
return this.gists.get("preference");
}
get currentButton() {
const buttonPreference = this.buttons.find(
(button) => button.id === this.selectedOptionId
);
return buttonPreference || this.buttons[0];
}
@action
onRegisterApi(api) {
this.dMenu = api;
@@ -48,22 +40,29 @@ export default class AiGistToggle extends Component {
@action
onSelect(optionId) {
this.gists.setPreference(optionId);
this.gistPreference.setPreference(optionId);
this.dMenu.close();
}
<template>
{{#if this.gists.shouldShow}}
{{bodyClass (concat "topic-list-layout-" this.gists.preference)}}
{{#if this.shouldShow}}
<DMenu
@modalForMobile={{true}}
@autofocus={{true}}
@identifier="topic-list-layout"
@identifier="ai-gists-dropdown"
@onRegisterApi={{this.onRegisterApi}}
@triggerClass="btn-default btn-icon"
@triggerClass="btn-transparent"
>
<:trigger>
{{icon this.currentButton.icon}}
<span class="d-button-label">
{{i18n
(concat
"discourse_ai.summarization." this.gistPreference.preference
)
}}
</span>
{{icon "angle-down"}}
</:trigger>
<:content>
<DropdownMenu as |dropdown|>
@@ -72,17 +71,9 @@ export default class AiGistToggle extends Component {
<DButton
@label={{button.label}}
@icon={{button.icon}}
class="btn-transparent
{{if button.description '--with-description'}}
{{if (eq this.currentButton.id button.id) '--active'}}"
class="btn-transparent"
@action={{fn this.onSelect button.id}}
>
{{#if button.description}}
<div class="btn__description">
{{i18n button.description}}
</div>
{{/if}}
</DButton>
/>
</dropdown.item>
{{/each}}
</DropdownMenu>
@@ -6,7 +6,7 @@ import { on } from "@ember/modifier";
import { action, computed } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { later } from "@ember/runloop";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import { eq } from "truth-helpers";
import DButton from "discourse/components/d-button";
import Avatar from "discourse/helpers/bound-avatar-template";
@@ -2,7 +2,7 @@ import Component from "@glimmer/component";
import { concat, fn } from "@ember/helper";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
@@ -7,7 +7,7 @@ import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { LinkTo } from "@ember/routing";
import { later } from "@ember/runloop";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import BackButton from "discourse/components/back-button";
import DButton from "discourse/components/d-button";
import Textarea from "discourse/components/d-textarea";
@@ -4,7 +4,7 @@ import { fn } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
import DToggleSwitch from "discourse/components/d-toggle-switch";
import concatClass from "discourse/helpers/concat-class";
@@ -3,7 +3,7 @@ import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import { and } from "truth-helpers";
import CookText from "discourse/components/cook-text";
import DButton from "discourse/components/d-button";
@@ -3,7 +3,7 @@ import { tracked } from "@glimmer/tracking";
import { fn } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import categoryBadge from "discourse/helpers/category-badge";
import { ajax } from "discourse/lib/ajax";
@@ -3,7 +3,7 @@ import { tracked } from "@glimmer/tracking";
import { fn } from "@ember/helper";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
@@ -1,6 +1,6 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
@@ -1,5 +1,4 @@
import { observer } from "@ember/object";
import { readOnly } from "@ember/object/computed";
import { computed, observer } from "@ember/object";
import MultiSelectComponent from "select-kit/components/multi-select";
export default MultiSelectComponent.extend({
@@ -7,7 +6,9 @@ export default MultiSelectComponent.extend({
this.selectKit.options.set("disabled", this.get("attrs.disabled.value"));
}),
content: readOnly("tools"),
content: computed("tools", function () {
return this.tools;
}),
value: "",
@@ -1,41 +1,31 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
export default class AiTopicGist extends Component {
@service gists;
@service router;
@service gistPreference;
get shouldShow() {
return this.gists.preference === "table-ai" && this.gists.shouldShow;
get prefersGist() {
return this.gistPreference.preference === "gists_enabled";
}
get hasGist() {
return !!this.gist;
}
get gist() {
return this.args.topic.get("ai_topic_gist");
}
get escapedExceprt() {
return this.args.topic.get("escapedExcerpt");
get showGist() {
return (
this.router.currentRoute.attributes?.filterType === "hot" &&
this.args.topic?.ai_topic_gist &&
!this.args.topic?.excerpt &&
this.prefersGist &&
!this.args.topic?.excerpt
);
}
<template>
{{#if this.shouldShow}}
{{#if this.hasGist}}
<div class="excerpt">
<div class="excerpt__contents">{{this.gist}}</div>
{{#if this.showGist}}
<div class="ai-topic-gist">
<div class="ai-topic-gist__text">
{{@topic.ai_topic_gist}}
</div>
{{else}}
{{#if this.esacpedExceprt}}
<div class="excerpt">
<div class="excerpt__contents">
{{htmlSafe this.escapedExceprt}}
</div>
</div>
{{/if}}
{{/if}}
</div>
{{/if}}
</template>
}
@@ -7,7 +7,6 @@ import { htmlSafe } from "@ember/template";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { clipboardCopy, escapeExpression } from "discourse/lib/utilities";
import i18n from "discourse-common/helpers/i18n";
import discourseLater from "discourse-common/lib/later";
@@ -39,7 +38,7 @@ export default class DebugAiModal extends Component {
} else {
return this.formattedResponse(this.info.raw_response_payload);
}
} catch {
} catch (e) {
return this.info.raw_request_payload;
}
@@ -64,28 +63,6 @@ export default class DebugAiModal extends Component {
this.copy(this.info.raw_response_payload);
}
async loadLog(logId) {
try {
await ajax(`/discourse-ai/ai-bot/show-debug-info/${logId}.json`).then(
(result) => {
this.info = result;
}
);
} catch (e) {
popupAjaxError(e);
}
}
@action
prevLog() {
this.loadLog(this.info.prev_log_id);
}
@action
nextLog() {
this.loadLog(this.info.next_log_id);
}
copy(text) {
clipboardCopy(text);
this.justCopiedText = I18n.t("discourse_ai.ai_bot.conversation_shared");
@@ -96,13 +73,11 @@ export default class DebugAiModal extends Component {
}
loadApiRequestInfo() {
ajax(`/discourse-ai/ai-bot/post/${this.args.model.id}/show-debug-info.json`)
.then((result) => {
this.info = result;
})
.catch((e) => {
popupAjaxError(e);
});
ajax(
`/discourse-ai/ai-bot/post/${this.args.model.id}/show-debug-info.json`
).then((result) => {
this.info = result;
});
}
get requestActive() {
@@ -172,22 +147,6 @@ export default class DebugAiModal extends Component {
@action={{this.copyResponse}}
@label="discourse_ai.ai_bot.debug_ai_modal.copy_response"
/>
{{#if this.info.prev_log_id}}
<DButton
class="btn"
@icon="angles-left"
@action={{this.prevLog}}
@label="discourse_ai.ai_bot.debug_ai_modal.previous_log"
/>
{{/if}}
{{#if this.info.next_log_id}}
<DButton
class="btn"
@icon="angles-right"
@action={{this.nextLog}}
@label="discourse_ai.ai_bot.debug_ai_modal.next_log"
/>
{{/if}}
<span class="ai-debut-modal__just-copied">{{this.justCopiedText}}</span>
</:footer>
</DModal>
@@ -1,7 +1,7 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { inject as service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import CookText from "discourse/components/cook-text";
import DButton from "discourse/components/d-button";

Some files were not shown because too many files have changed in this diff Show More