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

Commit d55aad3

Browse files
committed
implement full screen mode
1 parent 7a93496 commit d55aad3

File tree

10 files changed

+234
-114
lines changed

10 files changed

+234
-114
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import Component from "@glimmer/component";
2+
import { tracked } from "@glimmer/tracking";
3+
import { on } from "@ember/modifier";
4+
import { action } from "@ember/object";
5+
import DButton from "discourse/components/d-button";
6+
import htmlClass from "discourse/helpers/html-class";
7+
import getURL from "discourse-common/lib/get-url";
8+
9+
export default class AiArtifactComponent extends Component {
10+
@tracked expanded = false;
11+
12+
constructor() {
13+
super(...arguments);
14+
this.keydownHandler = this.handleKeydown.bind(this);
15+
}
16+
17+
willDestroy() {
18+
super.willDestroy(...arguments);
19+
window.removeEventListener("keydown", this.keydownHandler);
20+
}
21+
22+
@action
23+
handleKeydown(event) {
24+
if (event.key === "Escape" || event.key === "Esc") {
25+
this.expanded = false;
26+
}
27+
}
28+
29+
get artifactUrl() {
30+
return getURL(`/discourse-ai/ai-bot/artifacts/${this.args.artifactId}`);
31+
}
32+
33+
@action
34+
toggleView() {
35+
this.expanded = !this.expanded;
36+
if (this.expanded) {
37+
window.addEventListener("keydown", this.keydownHandler);
38+
} else {
39+
window.removeEventListener("keydown", this.keydownHandler);
40+
}
41+
}
42+
43+
get wrapperClasses() {
44+
return `ai-artifact__wrapper ${
45+
this.expanded ? "ai-artifact__expanded" : ""
46+
}`;
47+
}
48+
49+
@action
50+
artifactPanelHover() {
51+
// retrrigger animation
52+
const panel = document.querySelector('.ai-artifact__panel');
53+
panel.style.animation = 'none'; // Stop the animation
54+
setTimeout(() => {
55+
panel.style.animation = ''; // Re-trigger the animation by removing the none style
56+
}, 0);
57+
}
58+
59+
<template>
60+
{{#if this.expanded}}
61+
{{htmlClass "ai-artifact-expanded"}}
62+
{{/if}}
63+
<div class={{this.wrapperClasses}}>
64+
<div
65+
class="ai-artifact__panel--wrapper"
66+
{{on "mouseleave" this.artifactPanelHover}}
67+
>
68+
<div class="ai-artifact__panel">
69+
<DButton
70+
class="btn-flat btn-icon-text"
71+
@icon="discourse-compress"
72+
@label="discourse_ai.ai_artifact.collapse_view_label"
73+
@action={{this.toggleView}}
74+
/>
75+
</div>
76+
</div>
77+
<iframe
78+
title="AI Artifact"
79+
src={{this.artifactUrl}}
80+
width="100%"
81+
frameborder="0"
82+
sandbox="allow-scripts allow-forms"
83+
></iframe>
84+
<div class="ai-artifact__footer">
85+
<DButton
86+
class="btn-flat btn-icon-text ai-artifact__expand-button"
87+
@icon="discourse-expand"
88+
@label="discourse_ai.ai_artifact.expand_view_label"
89+
@action={{this.toggleView}}
90+
/>
91+
</div>
92+
</div>
93+
</template>
94+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { withPluginApi } from "discourse/lib/plugin-api";
2+
import AiArtifact from "../discourse/components/ai-artifact";
3+
4+
function initializeAiArtifacts(api) {
5+
api.decorateCookedElement(
6+
(element, helper) => {
7+
if (!helper.renderGlimmer) {
8+
return;
9+
}
10+
11+
[...element.querySelectorAll("div.ai-artifact")].forEach((artifactElement) => {
12+
const artifactId = artifactElement.getAttribute("data-ai-artifact-id");
13+
14+
helper.renderGlimmer(artifactElement, <template>
15+
<AiArtifact @artifactId={{artifactId}} />
16+
</template>);
17+
});
18+
},
19+
{
20+
id: "ai-artifact",
21+
onlyStream: true,
22+
}
23+
);
24+
}
25+
26+
export default {
27+
name: "ai-artifact",
28+
initialize() {
29+
withPluginApi("0.8.7", initializeAiArtifacts);
30+
},
31+
};

assets/javascripts/initializers/ai-artifacts.js

Lines changed: 0 additions & 54 deletions
This file was deleted.
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
export function setup(helper) {
22
helper.allowList(["details[class=ai-quote]"]);
3-
helper.allowList(["div[class=ai-artifact]"]);
4-
helper.allowList(["div[class=ai-artifact-tab]"]);
5-
helper.allowList(["div[class=ai-artifact-tabs]"]);
6-
helper.allowList(["div[class=ai-artifact-panels]"]);
7-
helper.allowList(["div[class=ai-artifact-panel]"]);
3+
helper.allowList(["div[class=ai-artifact]", "div[data-ai-artifact-id]"]);
84
}
Lines changed: 91 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,102 @@
1-
.ai-artifact {
2-
margin: 1em 0;
3-
4-
.ai-artifact-tabs {
5-
display: flex;
6-
gap: 0.20em;
7-
border-bottom: 2px solid var(--primary-low);
8-
padding: 0 0.2em;
9-
10-
.ai-artifact-tab {
11-
margin-bottom: -2px;
12-
13-
&[data-selected] {
14-
a {
15-
color: var(--tertiary);
16-
font-weight: 500;
17-
border-bottom: 2px solid var(--tertiary);
18-
}
19-
}
1+
.ai-artifact__wrapper {
2+
iframe {
3+
width: 100%;
4+
height: calc(100% - 2em);
5+
}
6+
height: 500px;
7+
padding-bottom: 2em;
8+
}
209

21-
&:hover:not([data-selected]) {
22-
a {
23-
color: var(--primary);
24-
background: var(--primary-very-low);
25-
}
26-
}
10+
.ai-artifact__panel {
11+
display: none;
12+
}
2713

28-
a {
29-
display: block;
30-
padding: 0.5em 1em;
31-
color: var(--primary-medium);
32-
text-decoration: none;
33-
cursor: pointer;
34-
border-bottom: 2px solid transparent;
35-
}
36-
}
37-
}
14+
.ai-artifact__expand-button {
15+
//transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
16+
}
17+
18+
html.ai-artifact-expanded {
19+
overflow: hidden;
20+
}
3821

39-
.ai-artifact-panels {
40-
padding: 1em 0 0 0;
41-
background: var(--blend-primary-secondary-5);
22+
.ai-artifact__footer {
23+
display: flex;
24+
justify-content: space-between;
25+
align-items: center;
26+
.ai-artifact__expand-button {
27+
margin-left: auto;
28+
}
29+
}
4230

43-
.ai-artifact-panel {
44-
display: none;
45-
min-height: 400px;
31+
.ai-artifact__expanded {
32+
.ai-artifact__footer {
33+
display: none;
34+
}
4635

47-
&[data-selected] {
48-
display: block;
36+
.ai-artifact__panel--wrapper {
37+
display: block;
38+
position: fixed;
39+
top: 0;
40+
left: 0;
41+
right: 0;
42+
height: 4em;
43+
z-index: 1000000;
44+
&:hover {
45+
.ai-artifact__panel {
46+
transform: translateY(0) !important;
47+
animation: none;
4948
}
49+
}
50+
}
5051

51-
pre {
52-
margin: 0;
52+
.ai-artifact__panel {
53+
display: block;
54+
position: fixed;
55+
top: 0;
56+
left: 0;
57+
right: 0;
58+
height: 2em;
59+
transition: transform 0.5s ease-in-out;
60+
animation: slideUp 0.5s 3s forwards;
61+
background-color: var(--secondary-low);
62+
opacity: 0.9;
63+
transform: translateY(0);
64+
button {
65+
width: 100%;
66+
text-align: left;
67+
box-sizing: border-box;
68+
justify-content: flex-start;
69+
color: var(--secondary-very-high);
70+
&:hover {
71+
color: var(--secondary-very-high);
72+
.d-icon {
73+
color: var(--secondary-high);
74+
}
75+
//color: var(--secondary-vary-low);
5376
}
5477
}
5578
}
79+
@keyframes slideUp {
80+
to {
81+
transform: translateY(-100%);
82+
}
83+
}
84+
85+
iframe {
86+
position: fixed;
87+
top: 0;
88+
height: 100%;
89+
left: 0;
90+
right: 0;
91+
bottom: 0;
92+
z-index: z("fullscreen");
93+
}
94+
95+
position: fixed;
96+
top: 0;
97+
left: 0;
98+
height: 100%;
99+
width: 100%;
100+
z-index: z("fullscreen");
101+
background-color: var(--secondary);
56102
}

config/locales/client.en.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ en:
165165
saved: "Persona saved"
166166
enabled: "Enabled?"
167167
tools: "Enabled tools"
168-
forced_tools: "Forced fools"
168+
forced_tools: "Forced tools"
169169
allowed_groups: "Allowed groups"
170170
confirm_delete: "Are you sure you want to delete this persona?"
171171
new: "New persona"
@@ -399,6 +399,10 @@ en:
399399
quick_search:
400400
suffix: "in all topics and posts with AI"
401401

402+
ai_artifact:
403+
expand_view_label: "Expand view"
404+
collapse_view_label: "Exit Fullscreen (ESC)"
405+
402406
ai_bot:
403407
pm_warning: "AI chatbot messages are monitored regularly by moderators."
404408
cancel_streaming: "Stop reply"

lib/ai_bot/tools/create_artifact.rb

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@ def update_custom_html(artifact = nil)
9292
css = parameters[:css].to_s
9393
js = parameters[:js].to_s
9494

95-
iframe =
96-
"<iframe src=\"#{Discourse.base_url}/discourse-ai/ai-bot/artifacts/#{artifact.id}\" width=\"100%\" height=\"500\" frameborder=\"0\"></iframe>" if artifact
95+
artifact_div = "<div class=\"ai-artifact\" data-ai-artifact-id=#{artifact.id}></div>" if artifact
9796

9897
content = []
9998

@@ -103,7 +102,7 @@ def update_custom_html(artifact = nil)
103102

104103
content << [:js, "### JavaScript\n\n```javascript\n#{js}\n```"] if js.present?
105104

106-
content << [:preview, "### Preview\n\n#{iframe}"] if iframe
105+
content << [:preview, "### Preview\n\n#{artifact_div}"] if artifact_div
107106

108107
content.sort_by! { |c| c[0] === @selected_tab ? 1 : 0 } if !artifact
109108

@@ -112,13 +111,10 @@ def update_custom_html(artifact = nil)
112111

113112
def success_response(artifact)
114113
@chain_next_response = false
115-
iframe_url = "#{Discourse.base_url}/discourse-ai/ai-bot/artifacts/#{artifact.id}"
116114

117115
{
118116
status: "success",
119117
artifact_id: artifact.id,
120-
iframe_html:
121-
"<iframe src=\"#{iframe_url}\" width=\"100%\" height=\"500\" frameborder=\"0\"></iframe>",
122118
message: "Artifact created successfully and rendered to user.",
123119
}
124120
end

0 commit comments

Comments
 (0)