From 63630eb55a71754adb2f92037a7f90d237d879f0 Mon Sep 17 00:00:00 2001 From: vegard Date: Wed, 18 Mar 2026 02:55:23 +0000 Subject: [PATCH] =?UTF-8?q?Fullf=C3=B8rer=20oppgave=2014.16:=20Presentasjo?= =?UTF-8?q?nselementer=20som=20noder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publisert tittel, ingress, OG-bilde og undertittel er nå egne noder koblet til artikler via title/subtitle/summary/og_image-edges. Rendering bruker presentasjonselementer med fallback til artikkelfelt. Backend: - Ny query: GET /query/presentation_elements?article_id=... - render_article_to_cas henter presentasjonselementer via edges - fetch_article + fetch_index_articles bruker pres.elementer - Batch-henting for forsideartikler (én SQL-spørring) - ArticleData utvides med subtitle + og_image - Alle fire temaer viser subtitle og OG-bilde - SEO og_image-tag fylles fra presentasjonselement Frontend: - PresentationEditor.svelte: opprett/rediger tittel, undertittel, ingress, OG-bilde med variantvelger (editorial/ai/social/rss) - Integrert i PublishDialog via
-seksjon - API-klient: fetchPresentationElements(), deleteNode() Grunnlag for A/B-testing (oppgave 14.17): edge-metadata støtter ab_status/impressions/clicks/ctr, best_of() prioriterer winner > testing. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/concepts/publisering.md | 7 + frontend/src/lib/api.ts | 45 +++ .../lib/components/PresentationEditor.svelte | 349 ++++++++++++++++++ .../src/lib/components/PublishDialog.svelte | 11 + maskinrommet/src/main.rs | 1 + maskinrommet/src/publishing.rs | 246 +++++++++++- maskinrommet/src/queries.rs | 142 +++++++ maskinrommet/src/templates/avis/article.html | 2 + maskinrommet/src/templates/blogg/article.html | 2 + .../src/templates/magasin/article.html | 2 + .../src/templates/tidsskrift/article.html | 2 + tasks.md | 3 +- 12 files changed, 802 insertions(+), 10 deletions(-) create mode 100644 frontend/src/lib/components/PresentationEditor.svelte diff --git a/docs/concepts/publisering.md b/docs/concepts/publisering.md index 533cc2a..a34742d 100644 --- a/docs/concepts/publisering.md +++ b/docs/concepts/publisering.md @@ -248,6 +248,13 @@ samtale, ikke en workflow-tilstand. ## Presentasjonselementer er noder +> **Status:** Implementert i oppgave 14.16. Backend: query-endpoint +> (`/query/presentation_elements`), rendering bruker presentasjonselementer +> (title, subtitle, summary, og_image) med fallback til artikkelnoden. +> Frontend: PresentationEditor-komponent integrert i PublishDialog. +> A/B-testing (automatisk rotasjon, impression-logging) er spesifisert +> men ikke implementert (planlagt oppgave 14.17). + En ingress er en tekst. En overskrift er en tekst. Et forsidebilde er et bilde. Alt som vises *om* en artikkel på forsiden er en *ting med egen forfatter, eget tidspunkt, og potensielt flere varianter*. Det diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 1aab86b..1a23373 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -567,6 +567,51 @@ export async function audioInfo(accessToken: string, hash: string): Promise; + edge_metadata: Record; + created_at: string; +} + +export interface PresentationElementsResponse { + article_id: string; + elements: PresentationElement[]; +} + +/** Hent presentasjonselementer (tittel, undertittel, ingress, OG-bilde) for en artikkel. */ +export async function fetchPresentationElements( + accessToken: string, + articleId: string +): Promise { + const res = await fetch( + `${BASE_URL}/query/presentation_elements?article_id=${encodeURIComponent(articleId)}`, + { headers: { Authorization: `Bearer ${accessToken}` } } + ); + if (!res.ok) { + const body = await res.text(); + throw new Error(`presentation_elements failed (${res.status}): ${body}`); + } + return res.json(); +} + +/** Slett en node (brukes for å fjerne presentasjonselementer). */ +export function deleteNode( + accessToken: string, + nodeId: string +): Promise<{ deleted: boolean }> { + return post(accessToken, '/intentions/delete_node', { node_id: nodeId }); +} + /** Anvend brukerens per-segment-valg etter re-transkripsjon. */ export function resolveRetranscription( accessToken: string, diff --git a/frontend/src/lib/components/PresentationEditor.svelte b/frontend/src/lib/components/PresentationEditor.svelte new file mode 100644 index 0000000..a3e48a3 --- /dev/null +++ b/frontend/src/lib/components/PresentationEditor.svelte @@ -0,0 +1,349 @@ + + +
+ {#if loading} +

Laster presentasjonselementer...

+ {:else} + {#if error} +
+ {error} +
+ {/if} + + +
+ + +
+ + +
+ Publisert tittel + {#each titles as el} +
+ {getDisplayValue(el)} + {getVariant(el)} + {#if getAbStatus(el)} + {getAbStatus(el)} + {/if} + +
+ {/each} +
+ + +
+
+ + +
+ Undertittel + {#each subtitles as el} +
+ {getDisplayValue(el)} + {getVariant(el)} + +
+ {/each} +
+ + +
+
+ + +
+ Ingress + {#each summaries as el} +
+ {getDisplayValue(el)} + {getVariant(el)} + +
+ {/each} +
+ + +
+
+ + +
+ OG-bilde (forside/deling) + {#each ogImages as el} +
+ {#if el.metadata?.cas_hash} + OG-bilde + {/if} + {getVariant(el)} + +
+ {/each} +
+ +
+
+ + {#if elements.length > 1} +

+ Flere varianter av samme type aktiverer automatisk A/B-testing. +

+ {/if} + {/if} +
diff --git a/frontend/src/lib/components/PublishDialog.svelte b/frontend/src/lib/components/PublishDialog.svelte index f7596d4..19e8b0a 100644 --- a/frontend/src/lib/components/PublishDialog.svelte +++ b/frontend/src/lib/components/PublishDialog.svelte @@ -1,6 +1,7 @@