From 4c81c21261371064721269b12f85bbb634ba9658 Mon Sep 17 00:00:00 2001 From: felix Date: Sat, 26 Sep 2026 16:54:49 +0200 Subject: [PATCH] rapport spec as attachment --- app/components/chat/attachment.hbs | 27 +++++++------ app/components/chat/attachment.js | 21 ++++++++++ .../chat/types/assistant-message.hbs | 35 ++++++++++++++--- .../chat/types/assistant-message.js | 21 +++++++++- app/components/chat/types/instant-message.hbs | 4 +- app/components/chat/types/instant-message.js | 10 ----- app/components/chat/window.js | 16 +------- app/models/chat-document.js | 8 ---- app/models/chat-message.js | 2 +- app/models/file.js | 13 +++++++ app/styles/app.scss | 38 +++++++++++++++++++ app/utils/ceilings.js | 4 +- 12 files changed, 141 insertions(+), 58 deletions(-) create mode 100644 app/components/chat/attachment.js delete mode 100644 app/components/chat/types/instant-message.js delete mode 100644 app/models/chat-document.js create mode 100644 app/models/file.js diff --git a/app/components/chat/attachment.hbs b/app/components/chat/attachment.hbs index d4017c6..04065ce 100644 --- a/app/components/chat/attachment.hbs +++ b/app/components/chat/attachment.hbs @@ -1,14 +1,13 @@ -{{! A loader while the file is being made, a download link when it is ready }} -
- {{#if @document.url}} - - {{@document.name}} - - {{else if @late}} - - Dit duurt langer dan verwacht. Vernieuw de pagina om opnieuw te kijken. - - {{else}} - {{@document.name}} wordt gemaakt - {{/if}} -
\ No newline at end of file +{{! 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//download. }} +
+
+ {{@document.filename}} + {{this.what}} +
+ + Download + +
diff --git a/app/components/chat/attachment.js b/app/components/chat/attachment.js new file mode 100644 index 0000000..b5c860f --- /dev/null +++ b/app/components/chat/attachment.js @@ -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//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'; + } +} diff --git a/app/components/chat/types/assistant-message.hbs b/app/components/chat/types/assistant-message.hbs index 16031de..778e0d3 100644 --- a/app/components/chat/types/assistant-message.hbs +++ b/app/components/chat/types/assistant-message.hbs @@ -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. }} -
{{this.html}}
-{{#each @message.attachments as |document|}} - -{{/each}} \ No newline at end of file + 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}} +
+ + +
+{{/if}} +{{#if this.showBijlagen}} + {{#each this.attachments as |document|}} + + {{/each}} +{{else}} +
{{this.html}}
+ {{#if this.attachments.length}} + + 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. + + {{/if}} +{{/if}} diff --git a/app/components/chat/types/assistant-message.js b/app/components/chat/types/assistant-message.js index f043675..9977b14 100644 --- a/app/components/chat/types/assistant-message.js +++ b/app/components/chat/types/assistant-message.js @@ -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)); } -} \ No newline at end of file + + get attachments() { + return this.args.message.attachments || []; + } + + @action + openBijlagen(event) { + event.preventDefault(); + this.showBijlagen = true; + } + + @action + showText() { + this.showBijlagen = false; + } +} diff --git a/app/components/chat/types/instant-message.hbs b/app/components/chat/types/instant-message.hbs index 63959ba..62e3011 100644 --- a/app/components/chat/types/instant-message.hbs +++ b/app/components/chat/types/instant-message.hbs @@ -1,4 +1,4 @@ {{@message.content}} {{#each @message.attachments as |document|}} - -{{/each}} \ No newline at end of file + +{{/each}} diff --git a/app/components/chat/types/instant-message.js b/app/components/chat/types/instant-message.js deleted file mode 100644 index a7fb230..0000000 --- a/app/components/chat/types/instant-message.js +++ /dev/null @@ -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 - ); - } -} diff --git a/app/components/chat/window.js b/app/components/chat/window.js index fc5ae22..05d1ebc 100644 --- a/app/components/chat/window.js +++ b/app/components/chat/window.js @@ -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. diff --git a/app/models/chat-document.js b/app/models/chat-document.js deleted file mode 100644 index 3591774..0000000 --- a/app/models/chat-document.js +++ /dev/null @@ -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; -} diff --git a/app/models/chat-message.js b/app/models/chat-message.js index a656a45..b0ca05f 100644 --- a/app/models/chat-message.js +++ b/app/models/chat-message.js @@ -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; } diff --git a/app/models/file.js b/app/models/file.js new file mode 100644 index 0000000..3153e17 --- /dev/null +++ b/app/models/file.js @@ -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//download. +// dct:type marks what it holds. +export default class File extends Model { + @attr uri; + @attr filename; + @attr format; + @attr size; + @attr('date') created; +} diff --git a/app/styles/app.scss b/app/styles/app.scss index c2fd0ce..dbf3dbe 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -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; +} diff --git a/app/utils/ceilings.js b/app/utils/ceilings.js index e941ca5..5f0e8d6 100644 --- a/app/utils/ceilings.js +++ b/app/utils/ceilings.js @@ -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;