From 8875830f6a0c682ada51e996c4634c42109d71ed Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 3 May 2024 11:53:17 -0700 Subject: [PATCH] FEATURE: Insert footnote from explained result (#591) --- .../ai-helper-options-menu.gjs | 76 ++++++++++++++++++- config/locales/client.en.yml | 2 + spec/system/ai_helper/ai_post_helper_spec.rb | 36 +++++++-- .../components/ai_helper_post_options.rb | 4 + 4 files changed, 109 insertions(+), 9 deletions(-) diff --git a/assets/javascripts/discourse/connectors/post-text-buttons/ai-helper-options-menu.gjs b/assets/javascripts/discourse/connectors/post-text-buttons/ai-helper-options-menu.gjs index fce1b497..8020b074 100644 --- a/assets/javascripts/discourse/connectors/post-text-buttons/ai-helper-options-menu.gjs +++ b/assets/javascripts/discourse/connectors/post-text-buttons/ai-helper-options-menu.gjs @@ -11,10 +11,11 @@ import FastEdit from "discourse/components/fast-edit"; import FastEditModal from "discourse/components/modal/fast-edit"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import { sanitize } from "discourse/lib/text"; import { clipboardCopy } from "discourse/lib/utilities"; import { bind } from "discourse-common/utils/decorators"; +import I18n from "discourse-i18n"; import eq from "truth-helpers/helpers/eq"; -import not from "truth-helpers/helpers/not"; import AiHelperCustomPrompt from "../../components/ai-helper-custom-prompt"; import AiHelperLoading from "../../components/ai-helper-loading"; import { showPostAIHelper } from "../../lib/show-ai-helper"; @@ -42,6 +43,9 @@ export default class AIHelperOptionsMenu extends Component { @tracked showAiButtons = true; @tracked originalPostHTML = null; @tracked postHighlighted = false; + @tracked streaming = false; + @tracked lastSelectedOption = null; + @tracked isSavingFootnote = false; MENU_STATES = { triggers: "TRIGGERS", @@ -170,6 +174,7 @@ export default class AIHelperOptionsMenu extends Component { @bind _updateResult(result) { + this.streaming = !result.done; this.suggestion = result.result; } @@ -189,6 +194,7 @@ export default class AIHelperOptionsMenu extends Component { @action async performAISuggestion(option) { this.menuState = this.MENU_STATES.loading; + this.lastSelectedOption = option; if (option.name === "explain") { this.menuState = this.MENU_STATES.result; @@ -306,6 +312,58 @@ export default class AIHelperOptionsMenu extends Component { await this.args.outletArgs.data.hideToolbar(); } + sanitizeForFootnote(text) { + // Remove line breaks (line-breaks breaks the inline footnote display) + text = text.replace(/[\r\n]+/g, " "); + + // Remove headings (headings don't work in inline footnotes) + text = text.replace(/^(#+)\s+/gm, ""); + + // Trim excess space + text = text.trim(); + + return sanitize(text); + } + + @action + async insertFootnote() { + this.isSavingFootnote = true; + + if (this.allowInsertFootnote) { + try { + const result = await ajax(`/posts/${this.args.outletArgs.post.id}`); + const sanitizedSuggestion = this.sanitizeForFootnote(this.suggestion); + const credits = I18n.t( + "discourse_ai.ai_helper.post_options_menu.footnote_credits" + ); + const withFootnote = `${this.selectedText} ^[${sanitizedSuggestion} (${credits})]`; + const newRaw = result.raw.replace(this.selectedText, withFootnote); + + await this.args.outletArgs.post.save({ raw: newRaw }); + } catch (error) { + popupAjaxError(error); + } finally { + this.isSavingFootnote = false; + this.menu.close(); + } + } + } + + get allowInsertFootnote() { + const siteSettings = this.siteSettings; + const canEditPost = this.args.outletArgs.data.canEditPost; + + if ( + !siteSettings?.enable_markdown_footnotes || + !siteSettings?.display_footnotes_inline || + !canEditPost + ) { + return false; + } + + return this.lastSelectedOption?.name === "explain"; + } +