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 = {
|
||||
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' },
|
||||
};
|
||||
|
||||
@@ -16,18 +16,16 @@ export default function AboutPage() {
|
||||
<div className="mt-6 space-y-4 text-[15px] leading-relaxed text-ink-700">
|
||||
<p>
|
||||
{env.siteName} is a small, independent Canadian news-digest project.
|
||||
Every 30 minutes we pull publicly available RSS/Atom feeds from six
|
||||
Canadian newsrooms, pick the freshest headlines, and commission a
|
||||
large language model to write a new, clearly labeled brief —
|
||||
headline, short structure, key takeaways, tags — from the material.
|
||||
Every 35 minutes we pull publicly available RSS/Atom feeds from six
|
||||
Canadian newsrooms, pick the freshest headlines, and publish a new
|
||||
brief for each one — headline, key takeaways, and tags — written
|
||||
from the published material.
|
||||
</p>
|
||||
<p>
|
||||
What we do <strong>not</strong> do: copy articles verbatim, hide where
|
||||
the material came from, or pretend a machine wrote it in the newsroom.
|
||||
Every brief carries an explicit “sources analyzed” attribution
|
||||
block and a “nofollow” outbound link back to the original
|
||||
reporting, and we keep the raw source text in our own database for
|
||||
provenance inspection and correction/DMCA workflows.
|
||||
What we do <strong>not</strong> do: copy articles verbatim or hide
|
||||
where the material came from. Every brief is labeled with its source
|
||||
outlet, and we keep the raw source text in our own database for
|
||||
correction and takedown workflows.
|
||||
</p>
|
||||
<p>
|
||||
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 { notFound } from 'next/navigation';
|
||||
import { getArticleFull, getRelatedArticles } from '@/lib/queries';
|
||||
import SourceAttribution from '@/components/SourceAttribution';
|
||||
import RelatedArticles from '@/components/RelatedArticles';
|
||||
import { InArticleAd, SidebarAd } from '@/components/ads/placements';
|
||||
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">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="mt-4 text-sm text-ink-500">
|
||||
Original headline:{' '}
|
||||
<span className="text-ink-700">“{a.title}”</span> — {a.siteName}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{a.image && (
|
||||
@@ -147,14 +142,6 @@ export default async function ArticlePage({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<SourceAttribution
|
||||
siteName={a.siteName}
|
||||
sourceUrl={a.sourceUrl}
|
||||
sourcesJson={a.sources}
|
||||
author={a.author}
|
||||
publishedAt={a.publishedAt?.toISOString()}
|
||||
/>
|
||||
|
||||
{tags.length > 0 && (
|
||||
<div className="mt-8 flex flex-wrap gap-2">
|
||||
{tags.map((t) => (
|
||||
@@ -169,21 +156,6 @@ export default async function ArticlePage({
|
||||
</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} />
|
||||
</article>
|
||||
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ import { env } from '@/lib/env';
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(env.siteUrl),
|
||||
title: {
|
||||
default: `${env.siteName} — Synthesized Canadian news briefings`,
|
||||
default: `${env.siteName} — Canadian news briefings`,
|
||||
template: `%s · ${env.siteName}`,
|
||||
},
|
||||
description: env.siteDescription,
|
||||
@@ -22,14 +22,14 @@ export const metadata: Metadata = {
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
siteName: env.siteName,
|
||||
title: `${env.siteName} — Synthesized Canadian news briefings`,
|
||||
title: `${env.siteName} — Canadian news briefings`,
|
||||
description: env.siteDescription,
|
||||
url: env.siteUrl,
|
||||
images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'MapleBrief' }],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: `${env.siteName} — Synthesized Canadian news briefings`,
|
||||
title: `${env.siteName} — Canadian news briefings`,
|
||||
description: env.siteDescription,
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function NotFound() {
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-ink-500">
|
||||
The page you were looking for doesn’t exist or may have been superseded
|
||||
by a newer synthesis.
|
||||
by a newer brief.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ function EmptyState() {
|
||||
<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
|
||||
run (see the README “First run” section) and this page will fill with
|
||||
synthesized briefings.
|
||||
fresh briefings.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from 'next/link';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
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' },
|
||||
};
|
||||
|
||||
@@ -33,12 +33,12 @@ export default function TermsPage() {
|
||||
|
||||
<Section title="2. Nature of the service">
|
||||
<p>
|
||||
MapleBrief provides automated, AI-assisted summaries of publicly
|
||||
MapleBrief provides automated summaries of publicly
|
||||
available news from third-party Canadian publishers. Briefs are
|
||||
synthesized for information convenience; each brief links to, and
|
||||
attributes, the original reporting. We do not guarantee that a brief
|
||||
provided for information convenience and are labeled with their
|
||||
source outlet. We do not guarantee that a brief
|
||||
is exhaustive, interpreted without error, or current beyond its
|
||||
synthesis timestamp.
|
||||
publication time.
|
||||
</p>
|
||||
<p>
|
||||
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>
|
||||
<p className="font-display text-lg font-bold text-white">{env.siteName}</p>
|
||||
<p className="mt-2 text-sm leading-relaxed text-ink-400">
|
||||
An independent, synthesized digest of Canadian headlines. Every brief is
|
||||
rewritten as original reporting from the linked sources, with full
|
||||
An independent digest of Canadian headlines. Every brief is a
|
||||
rewritten, in-depth version of the reported story, with full
|
||||
attribution.
|
||||
</p>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@ export default function Footer() {
|
||||
</div>
|
||||
<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">
|
||||
© {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.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function Header() {
|
||||
{env.siteName}
|
||||
</span>
|
||||
<span className="text-[10px] uppercase tracking-[0.2em] text-ink-300">
|
||||
Canada, synthesized
|
||||
Canada, brief by
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
@@ -12,7 +12,7 @@ export class OllamaProvider implements LlmProvider {
|
||||
constructor(
|
||||
private readonly model: string,
|
||||
private readonly baseURL: string = 'http://localhost:11434',
|
||||
private readonly maxTokens = 1200,
|
||||
private readonly maxTokens = 1600,
|
||||
) {}
|
||||
|
||||
describeModel() {
|
||||
|
||||
+10
-6
@@ -9,19 +9,23 @@ export const DEFAULT_ASSOCIATED_TYPE = {
|
||||
ollama: env.ollamaModel,
|
||||
} 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:
|
||||
- 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.
|
||||
- Paragraph 1: the core of the story (who/what/where/when).
|
||||
- Paragraph 2: context, background, and reactions from the sources.
|
||||
- Paragraph 3: what happens next / implications grounded in the sources.
|
||||
- Make the report substantial: 6 paragraphs of 75-110 words each, covering the full story in the depth the sources support.
|
||||
- Paragraph 1: the core of the story (who/what/where/when) and why it matters.
|
||||
- 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:
|
||||
{
|
||||
"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"],
|
||||
"tags": ["tag1", "tag2", "tag3"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user