rapport spec as attachment
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
{{! A loader while the file is being made, a download link when it is ready }}
|
||||
<div class="chat-attachment au-u-margin-top-tiny">
|
||||
{{#if @document.url}}
|
||||
<a href={{@document.url}} class="au-c-link" download={{@document.name}}>
|
||||
{{@document.name}}
|
||||
{{! One row of the bijlagen overview: its name, what it holds, and how to
|
||||
get it. The service is synchronous per turn: a bijlage already has its
|
||||
file when the message is served. The file service serves
|
||||
/files/<uuid>/download. }}
|
||||
<div class="chat-attachment chat-attachment--row au-u-margin-top-tiny">
|
||||
<div class="chat-attachment__info">
|
||||
<strong>{{@document.filename}}</strong>
|
||||
<span class="chat-attachment__what">{{this.what}}</span>
|
||||
</div>
|
||||
<a href={{this.href}} class="au-c-link" download={{@document.filename}}>
|
||||
Download
|
||||
</a>
|
||||
{{else if @late}}
|
||||
<AuAlert @skin="warning" @size="small" @title={{@document.name}}>
|
||||
Dit duurt langer dan verwacht. Vernieuw de pagina om opnieuw te kijken.
|
||||
</AuAlert>
|
||||
{{else}}
|
||||
<AuLoader @inline={{true}}>{{@document.name}} wordt gemaakt</AuLoader>
|
||||
{{/if}}
|
||||
</div>
|
||||
21
app/components/chat/attachment.js
Normal file
21
app/components/chat/attachment.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
// One row of the bijlagen overview: what it is, and where to get it.
|
||||
export default class ChatAttachment extends Component {
|
||||
// The file service serves every bijlage at /files/<uuid>/download.
|
||||
get href() {
|
||||
return `/files/${this.uuid}/download`;
|
||||
}
|
||||
|
||||
get uuid() {
|
||||
return String(this.args.document.uri || '').split('/').pop();
|
||||
}
|
||||
|
||||
// What the document holds, in Dutch, so the overview explains itself.
|
||||
get what() {
|
||||
const format = this.args.document.format || '';
|
||||
if (format.includes('turtle')) return 'De specificatie van het rapport als Turtle-bestand';
|
||||
if (format.includes('csv')) return 'De resultaten van het rapport als CSV-bestand';
|
||||
return 'Bestand';
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,31 @@
|
||||
{{! Rendered markdown. Assistant content only; the user's own text stays
|
||||
plain. The value is sanitized (DOMPurify) and marked safe in the js class. }}
|
||||
<div class="chat-md">{{this.html}}</div>
|
||||
{{#each @message.attachments as |document|}}
|
||||
<Chat::Attachment @document={{document}} @late={{this.late}} />
|
||||
{{/each}}
|
||||
plain. The value is sanitized (DOMPurify) and marked safe in the js class.
|
||||
Attachments get their own tab, "bijlagen"; the text refers the user there. }}
|
||||
{{#if this.attachments.length}}
|
||||
<div class="chat-bijlagen-tabs">
|
||||
<button
|
||||
type="button"
|
||||
class="chat-bijlagen-tab {{unless this.showBijlagen 'chat-bijlagen-tab--active'}}"
|
||||
{{on "click" this.showText}}
|
||||
>Bericht</button>
|
||||
<button
|
||||
type="button"
|
||||
class="chat-bijlagen-tab {{if this.showBijlagen 'chat-bijlagen-tab--active'}}"
|
||||
{{on "click" this.openBijlagen}}
|
||||
>Bijlagen ({{this.attachments.length}})</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.showBijlagen}}
|
||||
{{#each this.attachments as |document|}}
|
||||
<Chat::Attachment @document={{document}} />
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<div class="chat-md">{{this.html}}</div>
|
||||
{{#if this.attachments.length}}
|
||||
<AuAlert @skin="info" @size="small" @title="Bijlagen beschikbaar" {{on "click" this.openBijlagen}}>
|
||||
Dit antwoord bevat {{this.attachments.length}} bijlage(n), zoals de specifieke
|
||||
specificatie van het rapport of de resultaten. Klik op Bijlagen voor de
|
||||
uitleg per bijlage en om ze te downloaden.
|
||||
</AuAlert>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { htmlSafe } from '@ember/template';
|
||||
import { renderMarkdown } from 'frontend-lblod-chat/utils/markdown';
|
||||
|
||||
export default class AssistantMessage extends Component {
|
||||
@tracked showBijlagen = false;
|
||||
|
||||
// The string is DOMPurify-sanitized in utils/markdown; htmlSafe only marks
|
||||
// it as rendered HTML.
|
||||
get html() {
|
||||
return htmlSafe(renderMarkdown(this.args.message.content));
|
||||
}
|
||||
|
||||
get attachments() {
|
||||
return this.args.message.attachments || [];
|
||||
}
|
||||
|
||||
@action
|
||||
openBijlagen(event) {
|
||||
event.preventDefault();
|
||||
this.showBijlagen = true;
|
||||
}
|
||||
|
||||
@action
|
||||
showText() {
|
||||
this.showBijlagen = false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{{@message.content}}
|
||||
{{#each @message.attachments as |document|}}
|
||||
<Chat::Attachment @document={{document}} @late={{this.late}} />
|
||||
<Chat::Attachment @document={{document}} />
|
||||
{{/each}}
|
||||
@@ -1,10 +0,0 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { FILE_CEILING_MS } from 'frontend-lblod-chat/utils/ceilings';
|
||||
|
||||
export default class InstantMessage extends Component {
|
||||
get late() {
|
||||
return (
|
||||
this.args.now - this.args.message.created.getTime() >= FILE_CEILING_MS
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,7 @@ import { action } from '@ember/object';
|
||||
import { task, restartableTask, timeout } from 'ember-concurrency';
|
||||
import ENV from 'frontend-lblod-chat/config/environment';
|
||||
import { setting } from 'frontend-lblod-chat/utils/setting';
|
||||
import {
|
||||
ANSWER_CEILING_MS,
|
||||
FILE_CEILING_MS,
|
||||
} from 'frontend-lblod-chat/utils/ceilings';
|
||||
import { ANSWER_CEILING_MS } from 'frontend-lblod-chat/utils/ceilings';
|
||||
|
||||
const POLL_MS = 3000;
|
||||
|
||||
@@ -60,17 +57,8 @@ export default class ChatWindow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
// Waiting for a file: an attachment has no url yet.
|
||||
get waitingForFile() {
|
||||
return this.messages.some(
|
||||
(m) =>
|
||||
this.age(m.created) < FILE_CEILING_MS &&
|
||||
m.attachments.some((a) => !a.url),
|
||||
);
|
||||
}
|
||||
|
||||
get waiting() {
|
||||
return this.waitingForAnswer || this.waitingForFile;
|
||||
return this.waitingForAnswer;
|
||||
}
|
||||
|
||||
// One loop. Runs while there is something to wait for, and not otherwise.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import Model, { attr } from '@warp-drive/legacy/model';
|
||||
|
||||
export default class ChatDocument extends Model {
|
||||
@attr uri;
|
||||
@attr name;
|
||||
@attr mediaType;
|
||||
@attr url;
|
||||
}
|
||||
@@ -12,5 +12,5 @@ export default class ChatMessage extends Model {
|
||||
as: 'chat-message',
|
||||
})
|
||||
conversation;
|
||||
@hasMany('chat-document', { async: false, inverse: null }) attachments;
|
||||
@hasMany('file', { async: false, inverse: null }) attachments;
|
||||
}
|
||||
|
||||
13
app/models/file.js
Normal file
13
app/models/file.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Model, { attr } from '@warp-drive/legacy/model';
|
||||
|
||||
// A bijlage of the report assistant: a real file in the semantic.works
|
||||
// file data object model (logical nfo:FileDataObject over a physical
|
||||
// share:// part), served by the file service at /files/<uuid>/download.
|
||||
// dct:type marks what it holds.
|
||||
export default class File extends Model {
|
||||
@attr uri;
|
||||
@attr filename;
|
||||
@attr format;
|
||||
@attr size;
|
||||
@attr('date') created;
|
||||
}
|
||||
@@ -130,3 +130,41 @@
|
||||
color: var(--au-gray-700);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* The attachment tab inside an assistant message */
|
||||
.chat-bijlagen-tabs {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.chat-bijlagen-tab {
|
||||
border: 1px solid var(--au-gray-300);
|
||||
background: transparent;
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
padding: 0.15rem 0.6rem;
|
||||
font-size: 0.85em;
|
||||
cursor: pointer;
|
||||
color: var(--au-gray-700);
|
||||
}
|
||||
.chat-bijlagen-tab--active {
|
||||
background: var(--au-gray-100);
|
||||
font-weight: 600;
|
||||
}
|
||||
.chat-attachment--row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding: 0.375rem 0;
|
||||
border-top: 1px solid var(--au-gray-300);
|
||||
}
|
||||
.chat-attachment__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
.chat-attachment__what,
|
||||
.chat-attachment__pending {
|
||||
color: var(--au-gray-500);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
// The two ceilings, in one place. The window stops polling past them; the
|
||||
// file card shows its warning past its own.
|
||||
// The answer ceiling, in one place. The window stops polling past it.
|
||||
export const ANSWER_CEILING_MS = 10 * 60 * 1000;
|
||||
export const FILE_CEILING_MS = 30 * 60 * 1000;
|
||||
|
||||
Reference in New Issue
Block a user