diff --git a/src/routes/api/slugify/+server.ts b/src/routes/api/slugify/+server.ts new file mode 100644 index 0000000..444caf6 --- /dev/null +++ b/src/routes/api/slugify/+server.ts @@ -0,0 +1,81 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; + +// 将英文文本转换为规范的 kebab-case Slug +function toSlug(str: string): string { + return str + .toLowerCase() + .replace(/[^\w\s-]/g, '') + .trim() + .replace(/[\s_]+/g, '-') + .replace(/-+/g, '-'); +} + +export const POST: RequestHandler = async ({ request, platform }) => { + try { + const body = (await request.json().catch(() => ({}))) as Record; + const text = typeof body?.text === 'string' ? body.text.trim() : ''; + + if (!text) { + return json({ slug: '' }); + } + + // 若输入的文本已经是纯英文/数字(只包含 ASCII 字符、空格或连字符) + if (/^[a-zA-Z0-9\s-_]+$/.test(text)) { + return json({ slug: toSlug(text) }); + } + + let translatedText = ''; + + // 优先调用 Cloudflare Workers AI (@cf/meta/m2m100_418m 翻译模型) + if (platform?.env?.AI) { + try { + const res = (await platform.env.AI.run('@cf/meta/m2m100_418m', { + text, + target_lang: 'english' + })) as { translated_text?: string }; + + if (res?.translated_text) { + translatedText = res.translated_text; + } + } catch (err) { + console.warn('Workers AI 翻译异常,启动安全降级:', err); + } + } + + // 降级策略:如果 Workers AI 未绑定或在线环境缺失,尝试轻量免费 API 作为备用 + if (!translatedText) { + try { + const fallbackUrl = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=zh-CN|en`; + const res = await fetch(fallbackUrl); + if (res.ok) { + const data = (await res.json()) as { responseData?: { translatedText?: string } }; + if (data?.responseData?.translatedText) { + translatedText = data.responseData.translatedText; + } + } + } catch (e) { + console.warn('降级翻译服务调用失败:', e); + } + } + + // 如果得到了英文翻译,格式化为 kebab-case Slug + if (translatedText) { + const formattedSlug = toSlug(translatedText); + if (formattedSlug) { + return json({ slug: formattedSlug }); + } + } + + // 最底层的安全兜底:若没有任何英文结果返回,生成基于拼音/基本替换的 slug + const fallbackSlug = text + .toLowerCase() + .replace(/\s+/g, '-') + .replace(/[^\w-]/g, ''); + + return json({ slug: fallbackSlug || 'post' }); + } catch (error) { + console.error('Slug生成接口未知错误:', error); + return json({ slug: '' }, { status: 500 }); + } +}; diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 78b5592..c8e97bf 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,11 +1,12 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: 8e316e5cc1c036c1cc1ec926e6667c6d) +// Generated by Wrangler by running `wrangler types` (hash: 9194822214de3b6a9d6a85fa866a429b) // Runtime types generated with workerd@1.20260801.1 2026-08-07 nodejs_compat interface __BaseEnv_Env { R2: R2Bucket; DB: D1Database; - CF_ACCESS_TEAM_DOMAIN: 'https://hisland.cloudflareaccess.com'; - CF_ACCESS_AUD: '35b84a1c6ea81e1c036272ee68ecc4197041aadc1bed0815accac283a30e01ff'; + AI: Ai; + CF_ACCESS_TEAM_DOMAIN: "https://hisland.cloudflareaccess.com"; + CF_ACCESS_AUD: "35b84a1c6ea81e1c036272ee68ecc4197041aadc1bed0815accac283a30e01ff"; } declare namespace Cloudflare { interface Env extends __BaseEnv_Env {} @@ -15,9 +16,7 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues< - Pick - > {} + interface ProcessEnv extends StringifyValues> {} } // Begin runtime types diff --git a/wrangler.jsonc b/wrangler.jsonc index 48d645f..c74209d 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -21,5 +21,8 @@ "binding": "R2", "bucket_name": "blogflare-assets" } - ] + ], + "ai": { + "binding": "AI" + } }