DEV: Cancel popup should abort request (#497)

This commit is contained in:
Keegan George 2024-02-28 13:32:45 -08:00 committed by GitHub
parent 484fd1435b
commit 6a30b06a55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 81 additions and 46 deletions

View File

@ -11,12 +11,9 @@ import DTextarea from "discourse/components/d-textarea";
import autoFocus from "discourse/modifiers/auto-focus"; import autoFocus from "discourse/modifiers/auto-focus";
import icon from "discourse-common/helpers/d-icon"; import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n"; import i18n from "discourse-common/helpers/i18n";
import { IMAGE_MARKDOWN_REGEX } from "../../lib/utilities";
export default class AiImageCaptionContainer extends Component { export default class AiImageCaptionContainer extends Component {
@service imageCaptionPopup; @service imageCaptionPopup;
@service appEvents;
@service composer;
@action @action
updateCaption(event) { updateCaption(event) {
@ -26,22 +23,7 @@ export default class AiImageCaptionContainer extends Component {
@action @action
saveCaption() { saveCaption() {
const index = this.imageCaptionPopup.imageIndex; this.imageCaptionPopup.updateCaption();
const matchingPlaceholder =
this.composer.model.reply.match(IMAGE_MARKDOWN_REGEX);
if (matchingPlaceholder) {
const match = matchingPlaceholder[index];
const replacement = match.replace(
IMAGE_MARKDOWN_REGEX,
`![${this.imageCaptionPopup.newCaption}|$2$3$4]($5)`
);
if (match) {
this.appEvents.trigger("composer:replace-text", match, replacement);
}
}
this.hidePopup(); this.hidePopup();
} }
@ -63,6 +45,11 @@ export default class AiImageCaptionContainer extends Component {
@action @action
hidePopup() { hidePopup() {
this.imageCaptionPopup.showPopup = false; this.imageCaptionPopup.showPopup = false;
if (this.imageCaptionPopup._request) {
this.imageCaptionPopup._request.abort();
this.imageCaptionPopup._request = null;
this.imageCaptionPopup.toggleLoadingState(false);
}
} }
<template> <template>
@ -91,7 +78,7 @@ export default class AiImageCaptionContainer extends Component {
@action={{this.saveCaption}} @action={{this.saveCaption}}
/> />
<DButton <DButton
class="btn-flat" class="btn-flat cancel-request"
@label="cancel" @label="cancel"
@action={{this.hidePopup}} @action={{this.hidePopup}}
/> />

View File

@ -1,10 +1,43 @@
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import Service from "@ember/service"; import Service, { inject as service } from "@ember/service";
import { IMAGE_MARKDOWN_REGEX } from "../lib/utilities";
export default class ImageCaptionPopup extends Service { export default class ImageCaptionPopup extends Service {
@service composer;
@service appEvents;
@tracked showPopup = false; @tracked showPopup = false;
@tracked imageIndex = null; @tracked imageIndex = null;
@tracked imageSrc = null; @tracked imageSrc = null;
@tracked newCaption = null; @tracked newCaption = null;
@tracked loading = false; @tracked loading = false;
@tracked popupTrigger = null;
@tracked _request = null;
updateCaption() {
const matchingPlaceholder =
this.composer.model.reply.match(IMAGE_MARKDOWN_REGEX);
if (matchingPlaceholder) {
const match = matchingPlaceholder[this.imageIndex];
const replacement = match.replace(
IMAGE_MARKDOWN_REGEX,
`![${this.newCaption}|$2$3$4]($5)`
);
if (match) {
this.appEvents.trigger("composer:replace-text", match, replacement);
}
}
}
toggleLoadingState(loading) {
if (loading) {
this.popupTrigger?.classList.add("disabled");
return (this.loading = true);
}
this.popupTrigger?.classList.remove("disabled");
return (this.loading = false);
}
} }

View File

@ -2,7 +2,6 @@ import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { apiInitializer } from "discourse/lib/api"; import { apiInitializer } from "discourse/lib/api";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
import { IMAGE_MARKDOWN_REGEX } from "../discourse/lib/utilities";
export default apiInitializer("1.25.0", (api) => { export default apiInitializer("1.25.0", (api) => {
const buttonAttrs = { const buttonAttrs = {
@ -10,10 +9,7 @@ export default apiInitializer("1.25.0", (api) => {
icon: "discourse-sparkles", icon: "discourse-sparkles",
class: "generate-caption", class: "generate-caption",
}; };
const imageCaptionPopup = api.container.lookup("service:imageCaptionPopup");
const settings = api.container.lookup("service:site-settings"); const settings = api.container.lookup("service:site-settings");
const appEvents = api.container.lookup("service:app-events");
const site = api.container.lookup("site:main");
if (!settings.ai_helper_enabled_features.includes("image_caption")) { if (!settings.ai_helper_enabled_features.includes("image_caption")) {
return; return;
@ -23,7 +19,15 @@ export default apiInitializer("1.25.0", (api) => {
buttonAttrs.class, buttonAttrs.class,
buttonAttrs.icon, buttonAttrs.icon,
(event) => { (event) => {
if (event.target.classList.contains("generate-caption")) { const imageCaptionPopup = api.container.lookup(
"service:imageCaptionPopup"
);
imageCaptionPopup.popupTrigger = event.target;
if (
imageCaptionPopup.popupTrigger.classList.contains("generate-caption")
) {
const buttonWrapper = event.target.closest(".button-wrapper"); const buttonWrapper = event.target.closest(".button-wrapper");
const imageIndex = parseInt( const imageIndex = parseInt(
buttonWrapper.getAttribute("data-image-index"), buttonWrapper.getAttribute("data-image-index"),
@ -34,20 +38,24 @@ export default apiInitializer("1.25.0", (api) => {
.querySelector("img") .querySelector("img")
.getAttribute("src"); .getAttribute("src");
imageCaptionPopup.loading = true; imageCaptionPopup.toggleLoadingState(true);
const site = api.container.lookup("site:main");
if (!site.mobileView) { if (!site.mobileView) {
imageCaptionPopup.showPopup = !imageCaptionPopup.showPopup; imageCaptionPopup.showPopup = !imageCaptionPopup.showPopup;
} }
event.target.classList.add("disabled"); imageCaptionPopup._request = ajax(
`/discourse-ai/ai-helper/caption_image`,
{
method: "POST",
data: {
image_url: imageSrc,
},
}
);
ajax(`/discourse-ai/ai-helper/caption_image`, { imageCaptionPopup._request
method: "POST",
data: {
image_url: imageSrc,
},
})
.then(({ caption }) => { .then(({ caption }) => {
imageCaptionPopup.imageSrc = imageSrc; imageCaptionPopup.imageSrc = imageSrc;
imageCaptionPopup.imageIndex = imageIndex; imageCaptionPopup.imageIndex = imageIndex;
@ -55,22 +63,12 @@ export default apiInitializer("1.25.0", (api) => {
if (site.mobileView) { if (site.mobileView) {
// Auto-saves caption on mobile view // Auto-saves caption on mobile view
const composer = api.container.lookup("service:composer"); imageCaptionPopup.updateCaption();
const matchingPlaceholder =
composer.model.reply.match(IMAGE_MARKDOWN_REGEX);
const match = matchingPlaceholder[imageIndex];
const replacement = match.replace(
IMAGE_MARKDOWN_REGEX,
`![${imageCaptionPopup.newCaption}|$2$3$4]($5)`
);
appEvents.trigger("composer:replace-text", match, replacement);
} }
}) })
.catch(popupAjaxError) .catch(popupAjaxError)
.finally(() => { .finally(() => {
imageCaptionPopup.loading = false; imageCaptionPopup.toggleLoadingState(false);
event.target.classList.remove("disabled");
}); });
} }
} }

View File

@ -36,6 +36,15 @@ RSpec.describe "AI image caption", type: :system, js: true do
wait_for { page.find(".image-wrapper img")["alt"] == caption_with_attrs } wait_for { page.find(".image-wrapper img")["alt"] == caption_with_attrs }
expect(page.find(".image-wrapper img")["alt"]).to eq(caption_with_attrs) expect(page.find(".image-wrapper img")["alt"]).to eq(caption_with_attrs)
end end
it "should allow you to cancel a caption request" do
visit("/latest")
page.find("#create-topic").click
attach_file([file_path]) { composer.click_toolbar_button("upload") }
popup.click_generate_caption
popup.cancel_caption
expect(popup).to have_no_disabled_generate_button
end
end end
context "when triggering caption with AI on mobile", mobile: true do context "when triggering caption with AI on mobile", mobile: true do

View File

@ -18,6 +18,14 @@ module PageObjects
def save_caption def save_caption
find("#{CAPTION_POPUP_SELECTOR} .btn-primary").click find("#{CAPTION_POPUP_SELECTOR} .btn-primary").click
end end
def cancel_caption
find("#{CAPTION_POPUP_SELECTOR} .cancel-request").click
end
def has_no_disabled_generate_button?
page.has_no_css?("#{GENERATE_CAPTION_SELECTOR}.disabled", visible: false)
end
end end
end end
end end