Add TKMind platform extensions, H5/MindSpace stack, and deployment tooling.
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled

Fork goose with custom MCP widgets, platform extensions (aider, git, web, search),
MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-06-14 21:30:20 +08:00
parent e5fd568e01
commit 4e21ca937a
359 changed files with 70658 additions and 56 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+19
View File
@@ -0,0 +1,19 @@
@import "tailwindcss";
:root {
--background: #f5f0e5;
--foreground: #17221d;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
}
+41
View File
@@ -0,0 +1,41 @@
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import { Suspense } from 'react';
import { Footer } from '@/components/layout/Footer';
import { Header } from '@/components/layout/Header';
import { MobileNav } from '@/components/layout/MobileNav';
import { AttributionTracker } from '@/components/seo/AttributionTracker';
import { homeMetadata } from '@/lib/metadata';
import './globals.css';
const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
});
const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
});
export const metadata: Metadata = homeMetadata();
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="zh-CN" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
<body className="min-h-full flex flex-col bg-[#f5f0e5] pb-16 text-[#17221d] md:pb-0">
<Suspense fallback={null}>
<AttributionTracker />
</Suspense>
<Header />
<main className="mx-auto w-full max-w-6xl flex-1 px-4 py-6">{children}</main>
<Footer />
<MobileNav />
</body>
</html>
);
}
+6
View File
@@ -0,0 +1,6 @@
import { redirect } from 'next/navigation';
import { plazaPath } from '@/lib/site';
export default function Home() {
redirect(plazaPath());
}
+78
View File
@@ -0,0 +1,78 @@
import { notFound } from 'next/navigation';
import { CategoryNav } from '@/components/feed/CategoryNav';
import { FeedLoadMore } from '@/components/feed/FeedLoadMore';
import { FeedTabs } from '@/components/feed/FeedTabs';
import { fetchCategories, fetchFeed } from '@/lib/api';
import { categoryJsonLd, categoryMetadata } from '@/lib/metadata';
export const dynamic = 'force-dynamic';
export async function generateStaticParams() {
try {
const { categories } = await fetchCategories();
return categories.map((cat) => ({ slug: cat.slug }));
} catch {
return [];
}
}
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
try {
const { categories } = await fetchCategories();
const category = categories.find((item) => item.slug === slug);
if (!category) return { title: '分类未找到' };
return categoryMetadata(category.name, category.slug, category.description);
} catch {
return { title: 'Plaza 分类' };
}
}
export default async function PlazaCategoryPage({
params,
searchParams,
}: {
params: Promise<{ slug: string }>;
searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
const { slug } = await params;
const query = await searchParams;
const sort = query.sort === 'new' ? 'new' : 'hot';
const { categories } = await fetchCategories();
const category = categories.find((item) => item.slug === slug);
if (!category) notFound();
const feed = await fetchFeed({ sort, category: slug, limit: 20 });
return (
<div className="space-y-6">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(categoryJsonLd(category.name, category.slug, category.description)),
}}
/>
<div className="space-y-2">
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-[#68716c]"></p>
<h1 className="text-3xl font-bold text-[#17221d]">
{category.icon} {category.name}
</h1>
{category.description ? (
<p className="text-[#4a5751]">{category.description}</p>
) : (
<p className="text-[#4a5751]">{category.post_count ?? 0} </p>
)}
</div>
<CategoryNav categories={categories} activeSlug={slug} />
<FeedTabs sort={sort} categorySlug={slug} />
<FeedLoadMore
initialPosts={feed.posts}
initialCursor={feed.next_cursor}
initialHasMore={feed.has_more}
sort={sort}
categorySlug={slug}
/>
</div>
);
}
+14
View File
@@ -0,0 +1,14 @@
import { PostGridSkeleton } from '@/components/feed/PostGridSkeleton';
export default function PlazaLoading() {
return (
<div className="space-y-6">
<div className="space-y-2">
<div className="h-4 w-16 animate-pulse rounded bg-[#ebe4d6]" />
<div className="h-9 w-2/3 animate-pulse rounded bg-[#ebe4d6]" />
<div className="h-4 w-full max-w-xl animate-pulse rounded bg-[#f0ebe0]" />
</div>
<PostGridSkeleton />
</div>
);
}
@@ -0,0 +1,43 @@
import { ImageResponse } from 'next/og';
import { fetchPost, PlazaApiError } from '@/lib/api';
export const runtime = 'edge';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function PostOpenGraphImage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
let title = 'MindSpace Plaza';
let author = '创作者';
try {
const { post } = await fetchPost(id, { revalidate: false, cache: 'no-store' });
title = post.title;
author = post.author.display_name;
} catch (error) {
if (!(error instanceof PlazaApiError)) throw error;
}
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '64px',
background: 'linear-gradient(135deg, #2f6f57 0%, #1f4f3f 55%, #17221d 100%)',
color: '#fffdf7',
}}
>
<div style={{ fontSize: 28, opacity: 0.85 }}>MindSpace Plaza</div>
<div style={{ fontSize: 56, fontWeight: 700, lineHeight: 1.15, maxWidth: 980 }}>{title}</div>
<div style={{ fontSize: 28, opacity: 0.9 }}>{author}</div>
</div>
),
{ ...size },
);
}
+79
View File
@@ -0,0 +1,79 @@
import { notFound } from 'next/navigation';
import Link from 'next/link';
import { cookies } from 'next/headers';
import { CommentSection } from '@/components/comment/CommentSection';
import { PostReportButton } from '@/components/post/PostReportButton';
import { PostActions } from '@/components/post/PostActions';
import { PostEmbed } from '@/components/post/PostEmbed';
import { PostMeta } from '@/components/post/PostMeta';
import { mindSpaceSignupUrl } from '@/lib/utm';
import { fetchPost } from '@/lib/api';
import { PlazaApiError } from '@/lib/api';
import { postJsonLd, postMetadata } from '@/lib/metadata';
export const dynamic = 'force-dynamic';
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
try {
const { post } = await fetchPost(id);
return postMetadata(post);
} catch {
return { title: '帖子未找到' };
}
}
export default async function PlazaPostPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const cookieStore = await cookies();
const cookieHeader = cookieStore.toString();
let post;
try {
({ post } = await fetchPost(id, { cookie: cookieHeader }));
} catch (error) {
if (error instanceof PlazaApiError && error.code === 'POST_NOT_FOUND') notFound();
throw error;
}
const embedUrl = post.publication_url;
const jsonLd = postJsonLd(post);
return (
<article className="space-y-6">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<PostMeta post={post} />
{embedUrl ? (
<PostEmbed src={embedUrl} title={post.title} />
) : (
<div className="rounded-2xl border border-dashed border-[#d6d0c3] bg-[#fffdf7] px-6 py-12 text-center text-[#68716c]">
</div>
)}
<PostActions
postId={post.id}
stats={post.stats}
viewerReacted={post.viewer_reacted}
/>
<div className="flex justify-end">
<PostReportButton postId={post.id} />
</div>
<CommentSection postId={post.id} allowComment={post.allow_comment !== false} />
<div className="rounded-2xl border border-[#d6d0c3] bg-[#f0ebe0] px-5 py-4 text-center">
<Link
href={mindSpaceSignupUrl({
medium: 'footer',
campaign: 'post_watermark',
ref: post.id,
})}
className="inline-flex items-center gap-2 text-sm font-semibold text-[#2f6f57]"
>
MindSpace
</Link>
</div>
</article>
);
}
+63
View File
@@ -0,0 +1,63 @@
import { CategoryNav } from '@/components/feed/CategoryNav';
import { FeaturedBanner } from '@/components/feed/FeaturedBanner';
import { FeedLoadMore } from '@/components/feed/FeedLoadMore';
import { FeedTabs } from '@/components/feed/FeedTabs';
import { fetchCategories, fetchFeed } from '@/lib/api';
import { REVALIDATE_HOME } from '@/lib/cache';
import { homeJsonLd, homeMetadata } from '@/lib/metadata';
export const metadata = homeMetadata();
export const dynamic = 'force-dynamic';
export default async function PlazaHomePage({
searchParams,
}: {
searchParams: Promise<Record<string, string | string[] | undefined>>;
}) {
const params = await searchParams;
const sort = params.sort === 'new' ? 'new' : 'hot';
let categories: Awaited<ReturnType<typeof fetchCategories>>['categories'] = [];
let feed: Awaited<ReturnType<typeof fetchFeed>> = {
posts: [],
featured: { homepage_banner: [], trending: [] },
next_cursor: null,
has_more: false,
};
try {
const [categoryData, feedData] = await Promise.all([
fetchCategories({ revalidate: REVALIDATE_HOME }),
fetchFeed({ sort, limit: 20 }, { revalidate: REVALIDATE_HOME }),
]);
categories = categoryData.categories;
feed = feedData;
} catch {
categories = [];
}
return (
<div className="space-y-6">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(homeJsonLd()) }}
/>
<div className="space-y-2">
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-[#68716c]">Plaza</p>
<h1 className="text-3xl font-bold text-[#17221d]"> AI </h1>
<p className="max-w-2xl text-[#4a5751]">
MindSpace
</p>
</div>
<CategoryNav categories={categories} />
<FeedTabs sort={sort} />
<FeaturedBanner posts={feed.featured.homepage_banner} />
<FeedLoadMore
initialPosts={feed.posts}
initialCursor={feed.next_cursor}
initialHasMore={feed.has_more}
sort={sort}
/>
</div>
);
}
+15
View File
@@ -0,0 +1,15 @@
import type { MetadataRoute } from 'next';
import { siteOrigin } from '@/lib/site';
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: ['/plaza', '/u/'],
disallow: ['/ops', '/api', '/_next'],
},
],
sitemap: `${siteOrigin()}/sitemap.xml`,
};
}
+42
View File
@@ -0,0 +1,42 @@
import type { MetadataRoute } from 'next';
import { fetchSitemapData } from '@/lib/api';
import { plazaPath, siteOrigin } from '@/lib/site';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const origin = siteOrigin();
const now = new Date();
const staticPages: MetadataRoute.Sitemap = [
{
url: `${origin}${plazaPath()}`,
lastModified: now,
changeFrequency: 'hourly',
priority: 1,
},
];
try {
const data = await fetchSitemapData({ revalidate: 3600 });
const categoryPages = data.categories.map((category) => ({
url: `${origin}${plazaPath(`cat/${category.slug}`)}`,
lastModified: now,
changeFrequency: 'hourly' as const,
priority: 0.8,
}));
const postPages = data.posts.map((post) => ({
url: `${origin}${plazaPath(`p/${post.id}`)}`,
lastModified: new Date(post.updated_at),
changeFrequency: 'weekly' as const,
priority: 0.6,
}));
const userPages = data.users.map((user) => ({
url: `${origin}/u/${user.slug}`,
lastModified: new Date(user.last_post_at),
changeFrequency: 'weekly' as const,
priority: 0.5,
}));
return [...staticPages, ...categoryPages, ...postPages, ...userPages];
} catch {
return staticPages;
}
}
+52
View File
@@ -0,0 +1,52 @@
import { notFound } from 'next/navigation';
import { cookies } from 'next/headers';
import { UserCard } from '@/components/user/UserCard';
import { UserPostGrid } from '@/components/user/UserPostGrid';
import { fetchUserProfile, fetchUserPosts, PlazaApiError } from '@/lib/api';
import { userJsonLd, userMetadata } from '@/lib/metadata';
export const revalidate = 300;
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
try {
const { user } = await fetchUserProfile(slug, { revalidate: 300 });
return userMetadata(user);
} catch {
return { title: '用户未找到' };
}
}
export default async function UserProfilePage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const cookieStore = await cookies();
const cookieHeader = cookieStore.toString();
let profile;
let postsData;
try {
[profile, postsData] = await Promise.all([
fetchUserProfile(slug, { revalidate: 300, cookie: cookieHeader }),
fetchUserPosts(slug, { limit: 20 }, { revalidate: 300 }),
]);
} catch (error) {
if (error instanceof PlazaApiError && error.code === 'user_not_found') notFound();
throw error;
}
return (
<div className="space-y-6">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(userJsonLd(profile.user)) }}
/>
<UserCard user={profile.user} />
<UserPostGrid
slug={slug}
initialPosts={postsData.posts}
initialCursor={postsData.next_cursor}
initialHasMore={postsData.has_more}
/>
</div>
);
}