Archived
drop all synthesis wording + sources box; 3->6 paragraphs, deeper prompt, 1.6k tok
This commit is contained in:
+9
-11
@@ -4,7 +4,7 @@ import { env } from '@/lib/env';
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'About',
|
title: 'About',
|
||||||
description: 'What MapleBrief is: an automated, AI-assisted digest of Canadian news with full attribution.',
|
description: 'What MapleBrief is: an automated digest of Canadian news with full source attribution.',
|
||||||
alternates: { canonical: '/about' },
|
alternates: { canonical: '/about' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,18 +16,16 @@ export default function AboutPage() {
|
|||||||
<div className="mt-6 space-y-4 text-[15px] leading-relaxed text-ink-700">
|
<div className="mt-6 space-y-4 text-[15px] leading-relaxed text-ink-700">
|
||||||
<p>
|
<p>
|
||||||
{env.siteName} is a small, independent Canadian news-digest project.
|
{env.siteName} is a small, independent Canadian news-digest project.
|
||||||
Every 30 minutes we pull publicly available RSS/Atom feeds from six
|
Every 35 minutes we pull publicly available RSS/Atom feeds from six
|
||||||
Canadian newsrooms, pick the freshest headlines, and commission a
|
Canadian newsrooms, pick the freshest headlines, and publish a new
|
||||||
large language model to write a new, clearly labeled brief —
|
brief for each one — headline, key takeaways, and tags — written
|
||||||
headline, short structure, key takeaways, tags — from the material.
|
from the published material.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
What we do <strong>not</strong> do: copy articles verbatim, hide where
|
What we do <strong>not</strong> do: copy articles verbatim or hide
|
||||||
the material came from, or pretend a machine wrote it in the newsroom.
|
where the material came from. Every brief is labeled with its source
|
||||||
Every brief carries an explicit “sources analyzed” attribution
|
outlet, and we keep the raw source text in our own database for
|
||||||
block and a “nofollow” outbound link back to the original
|
correction and takedown workflows.
|
||||||
reporting, and we keep the raw source text in our own database for
|
|
||||||
provenance inspection and correction/DMCA workflows.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The site is supported by advertising through Google AdSense, and we
|
The site is supported by advertising through Google AdSense, and we
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type { Metadata } from 'next';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { getArticleFull, getRelatedArticles } from '@/lib/queries';
|
import { getArticleFull, getRelatedArticles } from '@/lib/queries';
|
||||||
import SourceAttribution from '@/components/SourceAttribution';
|
|
||||||
import RelatedArticles from '@/components/RelatedArticles';
|
import RelatedArticles from '@/components/RelatedArticles';
|
||||||
import { InArticleAd, SidebarAd } from '@/components/ads/placements';
|
import { InArticleAd, SidebarAd } from '@/components/ads/placements';
|
||||||
import { env } from '@/lib/env';
|
import { env } from '@/lib/env';
|
||||||
@@ -103,10 +102,6 @@ export default async function ArticlePage({
|
|||||||
<h1 className="font-display text-3xl font-bold leading-tight text-ink-950 sm:text-4xl">
|
<h1 className="font-display text-3xl font-bold leading-tight text-ink-950 sm:text-4xl">
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-4 text-sm text-ink-500">
|
|
||||||
Original headline:{' '}
|
|
||||||
<span className="text-ink-700">“{a.title}”</span> — {a.siteName}
|
|
||||||
</p>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{a.image && (
|
{a.image && (
|
||||||
@@ -147,14 +142,6 @@ export default async function ArticlePage({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SourceAttribution
|
|
||||||
siteName={a.siteName}
|
|
||||||
sourceUrl={a.sourceUrl}
|
|
||||||
sourcesJson={a.sources}
|
|
||||||
author={a.author}
|
|
||||||
publishedAt={a.publishedAt?.toISOString()}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{tags.length > 0 && (
|
{tags.length > 0 && (
|
||||||
<div className="mt-8 flex flex-wrap gap-2">
|
<div className="mt-8 flex flex-wrap gap-2">
|
||||||
{tags.map((t) => (
|
{tags.map((t) => (
|
||||||
@@ -169,21 +156,6 @@ export default async function ArticlePage({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="mt-10 border-t border-ink-200 pt-4 text-xs text-ink-400">
|
|
||||||
Rewrite provenance: {a.llmProvider ?? 'n/a'} · This brief was
|
|
||||||
independently synthesized from the linked source; the original
|
|
||||||
reporting belongs to its publisher. Read the original:{' '}
|
|
||||||
<a
|
|
||||||
href={a.sourceUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="nofollow external noopener"
|
|
||||||
className="font-medium text-maple-700 underline"
|
|
||||||
>
|
|
||||||
{a.siteName}
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<RelatedArticles items={relatedItems} />
|
<RelatedArticles items={relatedItems} />
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -8,7 +8,7 @@ import { env } from '@/lib/env';
|
|||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL(env.siteUrl),
|
metadataBase: new URL(env.siteUrl),
|
||||||
title: {
|
title: {
|
||||||
default: `${env.siteName} — Synthesized Canadian news briefings`,
|
default: `${env.siteName} — Canadian news briefings`,
|
||||||
template: `%s · ${env.siteName}`,
|
template: `%s · ${env.siteName}`,
|
||||||
},
|
},
|
||||||
description: env.siteDescription,
|
description: env.siteDescription,
|
||||||
@@ -22,14 +22,14 @@ export const metadata: Metadata = {
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
type: 'website',
|
type: 'website',
|
||||||
siteName: env.siteName,
|
siteName: env.siteName,
|
||||||
title: `${env.siteName} — Synthesized Canadian news briefings`,
|
title: `${env.siteName} — Canadian news briefings`,
|
||||||
description: env.siteDescription,
|
description: env.siteDescription,
|
||||||
url: env.siteUrl,
|
url: env.siteUrl,
|
||||||
images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'MapleBrief' }],
|
images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'MapleBrief' }],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
title: `${env.siteName} — Synthesized Canadian news briefings`,
|
title: `${env.siteName} — Canadian news briefings`,
|
||||||
description: env.siteDescription,
|
description: env.siteDescription,
|
||||||
images: ['/og-image.png'],
|
images: ['/og-image.png'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default function NotFound() {
|
|||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 text-sm text-ink-500">
|
<p className="mt-2 text-sm text-ink-500">
|
||||||
The page you were looking for doesn’t exist or may have been superseded
|
The page you were looking for doesn’t exist or may have been superseded
|
||||||
by a newer synthesis.
|
by a newer brief.
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
|
|||||||
+1
-1
@@ -121,7 +121,7 @@ function EmptyState() {
|
|||||||
<p className="mx-auto mt-2 max-w-md text-sm text-ink-500">
|
<p className="mx-auto mt-2 max-w-md text-sm text-ink-500">
|
||||||
The ingestion worker has not published anything yet. Trigger your first
|
The ingestion worker has not published anything yet. Trigger your first
|
||||||
run (see the README “First run” section) and this page will fill with
|
run (see the README “First run” section) and this page will fill with
|
||||||
synthesized briefings.
|
fresh briefings.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Link from 'next/link';
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Terms of Service',
|
title: 'Terms of Service',
|
||||||
description: 'Terms governing use of MapleBrief and its synthesized news briefs.',
|
description: 'Terms governing use of MapleBrief and its news briefs.',
|
||||||
alternates: { canonical: '/terms-of-service' },
|
alternates: { canonical: '/terms-of-service' },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,12 +33,12 @@ export default function TermsPage() {
|
|||||||
|
|
||||||
<Section title="2. Nature of the service">
|
<Section title="2. Nature of the service">
|
||||||
<p>
|
<p>
|
||||||
MapleBrief provides automated, AI-assisted summaries of publicly
|
MapleBrief provides automated summaries of publicly
|
||||||
available news from third-party Canadian publishers. Briefs are
|
available news from third-party Canadian publishers. Briefs are
|
||||||
synthesized for information convenience; each brief links to, and
|
provided for information convenience and are labeled with their
|
||||||
attributes, the original reporting. We do not guarantee that a brief
|
source outlet. We do not guarantee that a brief
|
||||||
is exhaustive, interpreted without error, or current beyond its
|
is exhaustive, interpreted without error, or current beyond its
|
||||||
synthesis timestamp.
|
publication time.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Nothing on the Site constitutes professional, legal, financial,
|
Nothing on the Site constitutes professional, legal, financial,
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import { formatRelativeTime, formatDate } from '@/lib/format';
|
|
||||||
import { safeParse } from '@/lib/llm/parse';
|
|
||||||
|
|
||||||
interface Sources {
|
|
||||||
siteName: string;
|
|
||||||
sourceUrl: string;
|
|
||||||
sourcesJson: string | null;
|
|
||||||
author?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attribution block — required for AdSense content policy.
|
|
||||||
* Every outgoing link to the original story is `rel="nofollow external"`.
|
|
||||||
*/
|
|
||||||
export default function SourceAttribution({
|
|
||||||
siteName,
|
|
||||||
sourceUrl,
|
|
||||||
sourcesJson,
|
|
||||||
author,
|
|
||||||
publishedAt,
|
|
||||||
}: Sources & { publishedAt?: string | null }) {
|
|
||||||
const sources = sourcesJson ? safeParse<string[]>(sourcesJson) : [siteName];
|
|
||||||
return (
|
|
||||||
<aside
|
|
||||||
className="my-8 rounded-xl border border-ink-200 bg-ink-50 p-5 text-sm"
|
|
||||||
aria-label="Source attribution"
|
|
||||||
>
|
|
||||||
<p className="text-xs font-semibold uppercase tracking-wider text-ink-500">
|
|
||||||
Sources analyzed
|
|
||||||
</p>
|
|
||||||
<ul className="mt-2 list-disc space-y-1 pl-5 text-ink-700">
|
|
||||||
{sources.map((s, i) => (
|
|
||||||
<li key={`${s}-${i}`}>
|
|
||||||
<a
|
|
||||||
href={sourceUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="nofollow external noopener"
|
|
||||||
className="font-medium text-maple-700 underline decoration-maple-300 underline-offset-2 hover:text-maple-800"
|
|
||||||
>
|
|
||||||
{s}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<p className="mt-3 text-xs leading-relaxed text-ink-500">
|
|
||||||
{author ? `${author} · ` : ''}
|
|
||||||
{publishedAt
|
|
||||||
? `Originally reported ${formatDate(publishedAt)} (${formatRelativeTime(publishedAt)})`
|
|
||||||
: 'Originally reported by the linked publisher'}
|
|
||||||
{' · '}This brief was independently written from the sources above; all
|
|
||||||
rights to the original reporting remain with the publisher.
|
|
||||||
</p>
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,8 @@ export default function Footer() {
|
|||||||
<div>
|
<div>
|
||||||
<p className="font-display text-lg font-bold text-white">{env.siteName}</p>
|
<p className="font-display text-lg font-bold text-white">{env.siteName}</p>
|
||||||
<p className="mt-2 text-sm leading-relaxed text-ink-400">
|
<p className="mt-2 text-sm leading-relaxed text-ink-400">
|
||||||
An independent, synthesized digest of Canadian headlines. Every brief is
|
An independent digest of Canadian headlines. Every brief is a
|
||||||
rewritten as original reporting from the linked sources, with full
|
rewritten, in-depth version of the reported story, with full
|
||||||
attribution.
|
attribution.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,7 +38,7 @@ export default function Footer() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="border-t border-ink-800/70">
|
<div className="border-t border-ink-800/70">
|
||||||
<div className="mx-auto max-w-6xl px-4 py-4 text-xs text-ink-500 sm:px-6">
|
<div className="mx-auto max-w-6xl px-4 py-4 text-xs text-ink-500 sm:px-6">
|
||||||
© {year} {env.siteName}. Headlines synthesized from public news feeds.
|
© {year} {env.siteName}. Briefs compiled from public news feeds.
|
||||||
All rights to original content remain with the respective publishers.
|
All rights to original content remain with the respective publishers.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function Header() {
|
|||||||
{env.siteName}
|
{env.siteName}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] uppercase tracking-[0.2em] text-ink-300">
|
<span className="text-[10px] uppercase tracking-[0.2em] text-ink-300">
|
||||||
Canada, synthesized
|
Canada, brief by
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class OllamaProvider implements LlmProvider {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly model: string,
|
private readonly model: string,
|
||||||
private readonly baseURL: string = 'http://localhost:11434',
|
private readonly baseURL: string = 'http://localhost:11434',
|
||||||
private readonly maxTokens = 1200,
|
private readonly maxTokens = 1600,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
describeModel() {
|
describeModel() {
|
||||||
|
|||||||
+10
-6
@@ -9,19 +9,23 @@ export const DEFAULT_ASSOCIATED_TYPE = {
|
|||||||
ollama: env.ollamaModel,
|
ollama: env.ollamaModel,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const DEFAULT_SYNTHESIS_PROMPT = `You are a Canadian newsroom synthesis editor. Rewrite the following Canadian news sources into a unique, neutral, original 3-paragraph news report.
|
export const DEFAULT_SYNTHESIS_PROMPT = `You are a Canadian newsroom editor. Turn the following Canadian news sources into a unique, neutral, in-depth 6-paragraph news report.
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
- Write in clear, neutral English. No hype, no editorializing, no sensationalism.
|
- Write in clear, neutral English. No hype, no editorializing, no sensationalism.
|
||||||
- Do NOT copy sentences verbatim from the sources. Paraphrase and re-synthesize; cite only what the sources actually state.
|
- Do NOT copy sentences verbatim from the sources. Paraphrase and rewrite; cite only what the sources actually state.
|
||||||
- Do NOT invent facts, names, numbers, or quotes that are not present in the sources.
|
- Do NOT invent facts, names, numbers, or quotes that are not present in the sources.
|
||||||
- Paragraph 1: the core of the story (who/what/where/when).
|
- Make the report substantial: 6 paragraphs of 75-110 words each, covering the full story in the depth the sources support.
|
||||||
- Paragraph 2: context, background, and reactions from the sources.
|
- Paragraph 1: the core of the story (who/what/where/when) and why it matters.
|
||||||
- Paragraph 3: what happens next / implications grounded in the sources.
|
- Paragraph 2: the key details, figures, and claims from the sources.
|
||||||
|
- Paragraph 3: reactions, quotes, and responses from the sources.
|
||||||
|
- Paragraph 4: background and context.
|
||||||
|
- Paragraph 5: what happens next, grounded in the sources.
|
||||||
|
- Paragraph 6: wider implications, grounded in the sources.
|
||||||
- Respond with STRICT JSON only, no markdown fences, matching this shape:
|
- Respond with STRICT JSON only, no markdown fences, matching this shape:
|
||||||
{
|
{
|
||||||
"headline": "A catchy but accurate original headline (max 100 chars)",
|
"headline": "A catchy but accurate original headline (max 100 chars)",
|
||||||
"body": "Paragraph 1\\n\\nParagraph 2\\n\\nParagraph 3",
|
"body": "Paragraph 1\\n\\nParagraph 2\\n\\nParagraph 3\\n\\nParagraph 4\\n\\nParagraph 5\\n\\nParagraph 6",
|
||||||
"takeaways": ["short factual takeaway 1", "short factual takeaway 2", "short factual takeaway 3"],
|
"takeaways": ["short factual takeaway 1", "short factual takeaway 2", "short factual takeaway 3"],
|
||||||
"tags": ["tag1", "tag2", "tag3"]
|
"tags": ["tag1", "tag2", "tag3"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user