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
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:
@@ -0,0 +1,300 @@
|
||||
import type {
|
||||
PlazaCategoriesResponse,
|
||||
PlazaCommentsResponse,
|
||||
PlazaCreatePostResponse,
|
||||
PlazaFeedResponse,
|
||||
PlazaPostResponse,
|
||||
PlazaSitemapData,
|
||||
PlazaUserProfileResponse,
|
||||
} from '@/types/plaza';
|
||||
import { apiBase } from '@/lib/site';
|
||||
|
||||
type FetchOptions = {
|
||||
revalidate?: number | false;
|
||||
cache?: RequestCache;
|
||||
cookie?: string;
|
||||
};
|
||||
|
||||
export type PlazaAuthStatus = {
|
||||
authenticated: boolean;
|
||||
user?: { id: string; username: string; slug?: string; displayName: string } | null;
|
||||
};
|
||||
|
||||
class PlazaApiError extends Error {
|
||||
code: string;
|
||||
|
||||
constructor(message: string, code: string) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
async function plazaFetch<T>(path: string, init?: RequestInit, options?: FetchOptions): Promise<T> {
|
||||
const base = apiBase();
|
||||
const url = `${base}${path.startsWith('/') ? path : `/${path}`}`;
|
||||
const headers = new Headers(init?.headers);
|
||||
if (options?.cookie) headers.set('cookie', options.cookie);
|
||||
if (!headers.has('Accept')) headers.set('Accept', 'application/json');
|
||||
|
||||
const next =
|
||||
options?.revalidate === false
|
||||
? { revalidate: 0 }
|
||||
: options?.revalidate != null
|
||||
? { revalidate: options.revalidate }
|
||||
: undefined;
|
||||
|
||||
const response = await fetch(url, {
|
||||
...init,
|
||||
headers,
|
||||
credentials: typeof window !== 'undefined' ? 'include' : init?.credentials,
|
||||
cache: options?.cache ?? (options?.revalidate === false ? 'no-store' : undefined),
|
||||
next,
|
||||
});
|
||||
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const code = payload?.error?.code ?? 'request_failed';
|
||||
const message = payload?.error?.message ?? `请求失败 (${response.status})`;
|
||||
throw new PlazaApiError(message, code);
|
||||
}
|
||||
return payload.data as T;
|
||||
}
|
||||
|
||||
export async function fetchAuthStatus(cookie?: string): Promise<PlazaAuthStatus> {
|
||||
try {
|
||||
const base = apiBase();
|
||||
const url = `${base}/auth/status`;
|
||||
const headers = new Headers({ Accept: 'application/json' });
|
||||
if (cookie) headers.set('cookie', cookie);
|
||||
const response = await fetch(url, {
|
||||
headers,
|
||||
credentials: typeof window !== 'undefined' ? 'include' : undefined,
|
||||
cache: 'no-store',
|
||||
});
|
||||
if (!response.ok) return { authenticated: false };
|
||||
const status = (await response.json()) as PlazaAuthStatus;
|
||||
return status;
|
||||
} catch {
|
||||
return { authenticated: false };
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchCategories(options?: FetchOptions) {
|
||||
return plazaFetch<PlazaCategoriesResponse>('/api/plaza/v1/categories', undefined, {
|
||||
revalidate: 300,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchFeed(
|
||||
params: {
|
||||
sort?: 'hot' | 'new';
|
||||
category?: string;
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
} = {},
|
||||
options?: FetchOptions,
|
||||
) {
|
||||
const query = new URLSearchParams();
|
||||
if (params.sort) query.set('sort', params.sort);
|
||||
if (params.category) query.set('category', params.category);
|
||||
if (params.cursor) query.set('cursor', params.cursor);
|
||||
if (params.limit) query.set('limit', String(params.limit));
|
||||
const suffix = query.size ? `?${query.toString()}` : '';
|
||||
return plazaFetch<PlazaFeedResponse>(`/api/plaza/v1/feed${suffix}`, undefined, {
|
||||
revalidate: 60,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchPost(id: string, options?: FetchOptions) {
|
||||
return plazaFetch<PlazaPostResponse>(`/api/plaza/v1/posts/${encodeURIComponent(id)}`, undefined, {
|
||||
revalidate: false,
|
||||
cache: 'no-store',
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function createPlazaPost(
|
||||
body: {
|
||||
publication_id: string;
|
||||
category_id: string;
|
||||
tags?: string[];
|
||||
cover_url?: string;
|
||||
allow_comment?: boolean;
|
||||
},
|
||||
cookie: string,
|
||||
) {
|
||||
return plazaFetch<PlazaCreatePostResponse>(
|
||||
'/api/plaza/v1/posts',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
{ revalidate: false, cookie },
|
||||
);
|
||||
}
|
||||
|
||||
export async function togglePostReaction(
|
||||
postId: string,
|
||||
type: 'like' | 'collect' | 'share',
|
||||
active: boolean,
|
||||
) {
|
||||
if (type === 'share') {
|
||||
return plazaFetch<{ post_id: string; type: string; active: boolean }>(
|
||||
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/reactions`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'share' }),
|
||||
},
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
if (active) {
|
||||
return plazaFetch<{ post_id: string; type: string; active: boolean }>(
|
||||
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/reactions/${type}`,
|
||||
{ method: 'DELETE' },
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
return plazaFetch<{ post_id: string; type: string; active: boolean }>(
|
||||
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/reactions`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type }),
|
||||
},
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchComments(
|
||||
postId: string,
|
||||
params: { parent_id?: string; cursor?: string } = {},
|
||||
) {
|
||||
const query = new URLSearchParams();
|
||||
if (params.parent_id) query.set('parent_id', params.parent_id);
|
||||
if (params.cursor) query.set('cursor', params.cursor);
|
||||
const suffix = query.size ? `?${query.toString()}` : '';
|
||||
return plazaFetch<PlazaCommentsResponse>(
|
||||
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/comments${suffix}`,
|
||||
undefined,
|
||||
{ revalidate: false, cache: 'no-store' },
|
||||
);
|
||||
}
|
||||
|
||||
export async function createComment(postId: string, content: string, parentId?: string | null) {
|
||||
return plazaFetch<{ comment: { id: string } }>(
|
||||
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/comments`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content, parent_id: parentId ?? null }),
|
||||
},
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
|
||||
export async function toggleCommentLike(commentId: string, liked: boolean) {
|
||||
return plazaFetch<{ id: string; liked: boolean }>(
|
||||
`/api/plaza/v1/comments/${encodeURIComponent(commentId)}/reactions`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ liked }),
|
||||
},
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchUserProfile(slug: string, options?: FetchOptions) {
|
||||
return plazaFetch<PlazaUserProfileResponse>(
|
||||
`/api/plaza/v1/users/${encodeURIComponent(slug)}`,
|
||||
undefined,
|
||||
{ revalidate: 300, ...options },
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchUserPosts(
|
||||
slug: string,
|
||||
params: { cursor?: string; limit?: number } = {},
|
||||
options?: FetchOptions,
|
||||
) {
|
||||
const query = new URLSearchParams();
|
||||
if (params.cursor) query.set('cursor', params.cursor);
|
||||
if (params.limit) query.set('limit', String(params.limit));
|
||||
const suffix = query.size ? `?${query.toString()}` : '';
|
||||
return plazaFetch<Pick<PlazaFeedResponse, 'posts' | 'next_cursor' | 'has_more'>>(
|
||||
`/api/plaza/v1/users/${encodeURIComponent(slug)}/posts${suffix}`,
|
||||
undefined,
|
||||
{ revalidate: false, cache: 'no-store', ...options },
|
||||
);
|
||||
}
|
||||
|
||||
export async function toggleFollow(slug: string, following: boolean) {
|
||||
if (following) {
|
||||
return plazaFetch<{ following: boolean }>(
|
||||
`/api/plaza/v1/users/${encodeURIComponent(slug)}/follow`,
|
||||
{ method: 'DELETE' },
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
return plazaFetch<{ following: boolean }>(
|
||||
`/api/plaza/v1/users/${encodeURIComponent(slug)}/follow`,
|
||||
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' },
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchSitemapData(options?: FetchOptions) {
|
||||
return plazaFetch<PlazaSitemapData>('/api/plaza/v1/seo/sitemap', undefined, {
|
||||
revalidate: 3600,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function recordAttributionEvent(body: {
|
||||
event_type: 'landing' | 'signup';
|
||||
utm_source: string;
|
||||
utm_medium?: string;
|
||||
utm_campaign?: string;
|
||||
ref_id?: string;
|
||||
}) {
|
||||
return plazaFetch<{ id: string; event_type: string }>(
|
||||
'/api/plaza/v1/attribution/events',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
|
||||
export async function submitPostReport(postId: string, reason: string, detail?: string) {
|
||||
return plazaFetch<{ report: { id: string; status: string } }>(
|
||||
`/api/plaza/v1/posts/${encodeURIComponent(postId)}/reports`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ reason, detail: detail ?? '' }),
|
||||
},
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
|
||||
export async function submitCommentReport(commentId: string, reason: string, detail?: string) {
|
||||
return plazaFetch<{ report: { id: string; status: string } }>(
|
||||
`/api/plaza/v1/comments/${encodeURIComponent(commentId)}/reports`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ reason, detail: detail ?? '' }),
|
||||
},
|
||||
{ revalidate: false },
|
||||
);
|
||||
}
|
||||
|
||||
export { PlazaApiError };
|
||||
@@ -0,0 +1,3 @@
|
||||
export const REVALIDATE_HOME = 60;
|
||||
export const REVALIDATE_CATEGORY = 300;
|
||||
export const REVALIDATE_USER = 300;
|
||||
@@ -0,0 +1,35 @@
|
||||
export function formatCount(value: number): string {
|
||||
const n = Number(value ?? 0);
|
||||
if (n >= 100_000) return `${Math.round(n / 10_000)}万`;
|
||||
if (n >= 10_000) return `${(n / 10_000).toFixed(1).replace(/\.0$/, '')}万`;
|
||||
if (n >= 1_000) return `${(n / 1_000).toFixed(1).replace(/\.0$/, '')}k`;
|
||||
return String(n);
|
||||
}
|
||||
|
||||
export function formatRelativeTime(iso: string): string {
|
||||
const date = new Date(iso);
|
||||
if (Number.isNaN(date.getTime())) return '';
|
||||
const diffMs = Date.now() - date.getTime();
|
||||
const minutes = Math.floor(diffMs / 60_000);
|
||||
if (minutes < 1) return '刚刚';
|
||||
if (minutes < 60) return `${minutes} 分钟前`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 24) return `${hours} 小时前`;
|
||||
const days = Math.floor(hours / 24);
|
||||
if (days < 30) return `${days} 天前`;
|
||||
return date.toLocaleDateString('zh-CN');
|
||||
}
|
||||
|
||||
export function categoryAccent(slug: string): string {
|
||||
const palette = [
|
||||
'from-emerald-700 to-teal-500',
|
||||
'from-amber-700 to-orange-500',
|
||||
'from-violet-700 to-purple-500',
|
||||
'from-sky-700 to-cyan-500',
|
||||
'from-rose-700 to-pink-500',
|
||||
'from-lime-700 to-green-500',
|
||||
];
|
||||
let hash = 0;
|
||||
for (let i = 0; i < slug.length; i += 1) hash = (hash + slug.charCodeAt(i) * (i + 1)) % palette.length;
|
||||
return palette[hash] ?? palette[0];
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import type { Metadata } from 'next';
|
||||
import type { PlazaPost, PlazaUserProfile } from '@/types/plaza';
|
||||
import { plazaPath, siteOrigin } from '@/lib/site';
|
||||
|
||||
const DEFAULT_OG = `${siteOrigin()}/plaza-og.png`;
|
||||
|
||||
function postOgImage(post: PlazaPost): string {
|
||||
if (post.cover_url) return post.cover_url;
|
||||
return `${siteOrigin()}${plazaPath(`p/${post.id}/opengraph-image`)}`;
|
||||
}
|
||||
|
||||
export function homeMetadata(): Metadata {
|
||||
const url = `${siteOrigin()}${plazaPath()}`;
|
||||
return {
|
||||
title: 'Plaza - 发现 AI 创作的精彩内容',
|
||||
description: '浏览来自全球用户用 MindSpace 创作的报告、作品和页面',
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
title: 'Plaza - 发现 AI 创作的精彩内容',
|
||||
description: '浏览来自全球用户用 MindSpace 创作的报告、作品和页面',
|
||||
url,
|
||||
siteName: 'MindSpace Plaza',
|
||||
images: [{ url: DEFAULT_OG, width: 1200, height: 630 }],
|
||||
},
|
||||
alternates: { canonical: url },
|
||||
robots: { index: true, follow: true },
|
||||
};
|
||||
}
|
||||
|
||||
export function categoryMetadata(name: string, slug: string, description?: string): Metadata {
|
||||
const url = `${siteOrigin()}${plazaPath(`cat/${slug}`)}`;
|
||||
return {
|
||||
title: `${name} - Plaza | MindSpace`,
|
||||
description: description || `浏览 Plaza「${name}」分类下的 AI 创作内容`,
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
title: `${name} - Plaza`,
|
||||
description: description || `Plaza「${name}」分类`,
|
||||
url,
|
||||
siteName: 'MindSpace Plaza',
|
||||
images: [{ url: DEFAULT_OG, width: 1200, height: 630 }],
|
||||
},
|
||||
alternates: { canonical: url },
|
||||
robots: { index: true, follow: true },
|
||||
};
|
||||
}
|
||||
|
||||
export function postMetadata(post: PlazaPost): Metadata {
|
||||
const url = `${siteOrigin()}${plazaPath(`p/${post.id}`)}`;
|
||||
const image = postOgImage(post);
|
||||
const description =
|
||||
post.summary || `${post.author.display_name} 用 MindSpace 创作的作品`;
|
||||
|
||||
return {
|
||||
title: `${post.title} - Plaza | MindSpace`,
|
||||
description,
|
||||
keywords: post.tags.join(', '),
|
||||
openGraph: {
|
||||
title: post.title,
|
||||
description,
|
||||
url,
|
||||
siteName: 'MindSpace Plaza',
|
||||
images: [{ url: image, width: 1200, height: 630, alt: post.title }],
|
||||
type: 'article',
|
||||
publishedTime: post.published_at,
|
||||
authors: [`${siteOrigin()}/u/${post.author.slug}`],
|
||||
tags: post.tags,
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: post.title,
|
||||
description,
|
||||
images: [image],
|
||||
creator: `@${post.author.slug}`,
|
||||
},
|
||||
alternates: { canonical: url },
|
||||
robots: { index: true, follow: true },
|
||||
};
|
||||
}
|
||||
|
||||
export function postJsonLd(post: PlazaPost) {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Article',
|
||||
headline: post.title,
|
||||
description: post.summary,
|
||||
image: post.cover_url || postOgImage(post),
|
||||
datePublished: post.published_at,
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: post.author.display_name,
|
||||
url: `${siteOrigin()}/u/${post.author.slug}`,
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'MindSpace',
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: `${siteOrigin()}/logo.png`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function userMetadata(user: PlazaUserProfile): Metadata {
|
||||
const url = `${siteOrigin()}/u/${user.slug}`;
|
||||
const description =
|
||||
user.bio || `${user.display_name} 在 MindSpace Plaza 发布了 ${user.stats.post_count} 篇作品`;
|
||||
const image = user.avatar_url || DEFAULT_OG;
|
||||
|
||||
return {
|
||||
title: `${user.display_name} (@${user.slug}) - Plaza | MindSpace`,
|
||||
description,
|
||||
openGraph: {
|
||||
type: 'profile',
|
||||
title: user.display_name,
|
||||
description,
|
||||
url,
|
||||
siteName: 'MindSpace Plaza',
|
||||
images: user.avatar_url
|
||||
? [{ url: image, width: 400, height: 400, alt: user.display_name }]
|
||||
: [{ url: DEFAULT_OG, width: 1200, height: 630 }],
|
||||
},
|
||||
twitter: {
|
||||
card: user.avatar_url ? 'summary' : 'summary_large_image',
|
||||
title: user.display_name,
|
||||
description,
|
||||
images: [image],
|
||||
},
|
||||
alternates: { canonical: url },
|
||||
robots: { index: true, follow: true },
|
||||
};
|
||||
}
|
||||
|
||||
export function userJsonLd(user: PlazaUserProfile) {
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ProfilePage',
|
||||
name: user.display_name,
|
||||
description: user.bio || `${user.display_name} 的 MindSpace Plaza 主页`,
|
||||
url: `${siteOrigin()}/u/${user.slug}`,
|
||||
mainEntity: {
|
||||
'@type': 'Person',
|
||||
name: user.display_name,
|
||||
alternateName: user.slug,
|
||||
url: `${siteOrigin()}/u/${user.slug}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function homeJsonLd() {
|
||||
const url = `${siteOrigin()}${plazaPath()}`;
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'MindSpace Plaza',
|
||||
url,
|
||||
description: '浏览来自全球用户用 MindSpace 创作的报告、作品和页面',
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'MindSpace',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function categoryJsonLd(name: string, slug: string, description?: string) {
|
||||
const url = `${siteOrigin()}${plazaPath(`cat/${slug}`)}`;
|
||||
return {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'CollectionPage',
|
||||
name: `${name} - Plaza`,
|
||||
description: description || `Plaza「${name}」分类下的 AI 创作内容`,
|
||||
url,
|
||||
isPartOf: {
|
||||
'@type': 'WebSite',
|
||||
name: 'MindSpace Plaza',
|
||||
url: `${siteOrigin()}${plazaPath()}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export function apiBase(): string {
|
||||
if (typeof window === 'undefined') {
|
||||
return (
|
||||
process.env.PLAZA_API_BASE ??
|
||||
process.env.NEXT_PUBLIC_API_BASE ??
|
||||
'http://127.0.0.1:8080'
|
||||
);
|
||||
}
|
||||
return process.env.NEXT_PUBLIC_API_BASE ?? '';
|
||||
}
|
||||
|
||||
export function siteOrigin(): string {
|
||||
return process.env.NEXT_PUBLIC_SITE_BASE ?? 'https://go.tkmind.cn';
|
||||
}
|
||||
|
||||
export function mindSpaceOrigin(): string {
|
||||
return process.env.NEXT_PUBLIC_MINDSPACE_BASE ?? siteOrigin();
|
||||
}
|
||||
|
||||
export function plazaPath(path = ''): string {
|
||||
const base = process.env.NEXT_PUBLIC_PLAZA_BASE ?? '/plaza';
|
||||
if (!path) return base;
|
||||
return `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
export function plazaUtmQuery(params: {
|
||||
source?: string;
|
||||
medium: string;
|
||||
campaign: string;
|
||||
ref?: string;
|
||||
}) {
|
||||
const query = new URLSearchParams();
|
||||
query.set('utm_source', params.source ?? 'plaza');
|
||||
query.set('utm_medium', params.medium);
|
||||
query.set('utm_campaign', params.campaign);
|
||||
if (params.ref) query.set('ref', params.ref);
|
||||
return query.toString();
|
||||
}
|
||||
|
||||
export function mindSpaceSignupUrl(params: {
|
||||
medium: string;
|
||||
campaign: string;
|
||||
ref?: string;
|
||||
returnTo?: string;
|
||||
}) {
|
||||
const base = process.env.NEXT_PUBLIC_MINDSPACE_BASE ?? process.env.NEXT_PUBLIC_SITE_BASE ?? 'https://go.tkmind.cn';
|
||||
const query = plazaUtmQuery(params);
|
||||
const returnParam = params.returnTo ? `&return_to=${encodeURIComponent(params.returnTo)}` : '';
|
||||
return `${base.replace(/\/$/, '')}?${query}${returnParam}`;
|
||||
}
|
||||
Reference in New Issue
Block a user