mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-30 04:15:45 +08:00
fix: preserve Mintlify routes and localized navigation
This commit is contained in:
+59
-12
@@ -8,7 +8,7 @@ import { update } from "fumadocs-core/source";
|
|||||||
import { uiTranslations } from "fumadocs-ui/i18n";
|
import { uiTranslations } from "fumadocs-ui/i18n";
|
||||||
import { defineDocs } from "fumadocs-mdx/macro";
|
import { defineDocs } from "fumadocs-mdx/macro";
|
||||||
import { createOpenAPI, type OpenAPIServer } from "fumadocs-openapi/server";
|
import { createOpenAPI, type OpenAPIServer } from "fumadocs-openapi/server";
|
||||||
import { readdirSync } from "node:fs";
|
import { readFileSync, readdirSync } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
const SITE_URL = "https://docs.langbot.dev";
|
const SITE_URL = "https://docs.langbot.dev";
|
||||||
@@ -16,17 +16,27 @@ const LOCALES = ["en", "zh", "ja"] as const;
|
|||||||
const MINTLIFY_LOCALE = { en: "en", zh: "cn", ja: "jp" } as const;
|
const MINTLIFY_LOCALE = { en: "en", zh: "cn", ja: "jp" } as const;
|
||||||
const HREFLANG = { en: "en", zh: "zh-CN", ja: "ja" } as const;
|
const HREFLANG = { en: "en", zh: "zh-CN", ja: "ja" } as const;
|
||||||
const mintlifyDocs = readMintlifyDocs();
|
const mintlifyDocs = readMintlifyDocs();
|
||||||
const translations = defineMintlifyI18n(mintlifyDocs, {
|
const i18n = defineMintlifyI18n(mintlifyDocs, {
|
||||||
localeMap: { en: "en", cn: "zh", jp: "ja" },
|
localeMap: { en: "en", cn: "zh", jp: "ja" },
|
||||||
}).translations()
|
});
|
||||||
|
const translations = i18n.translations()
|
||||||
.extend(uiTranslations())
|
.extend(uiTranslations())
|
||||||
.add({
|
.add({
|
||||||
en: { displayName: "English" },
|
en: { displayName: "English" },
|
||||||
zh: { displayName: "简体中文" },
|
zh: { displayName: "简体中文" },
|
||||||
ja: { displayName: "日本語" },
|
ja: { displayName: "日本語" },
|
||||||
});
|
});
|
||||||
|
// Fumapress stores translations.config as its runtime i18n config. Preserve
|
||||||
|
// the Mintlify adapter's locale bridge there so navigation.languages selects
|
||||||
|
// the matching English, Chinese, or Japanese tree instead of always using en.
|
||||||
|
Object.assign(translations.config, {
|
||||||
|
_getMintlifyLanguage: (i18n as typeof i18n & {
|
||||||
|
_getMintlifyLanguage?: (locale: string) => string | undefined;
|
||||||
|
})._getMintlifyLanguage,
|
||||||
|
});
|
||||||
// Locale-only files must not inherit the English storage and create indexed
|
// Locale-only files must not inherit the English storage and create indexed
|
||||||
// pages in locales where no canonical source translation exists.
|
// pages in locales where no canonical source translation exists.
|
||||||
|
i18n.fallbackLanguage = null;
|
||||||
translations.config.fallbackLanguage = null;
|
translations.config.fallbackLanguage = null;
|
||||||
|
|
||||||
function collectRouteLocales() {
|
function collectRouteLocales() {
|
||||||
@@ -70,6 +80,42 @@ const enOpenAPI = createOpenAPI({ input: ["openapi/service-api-en.json"] });
|
|||||||
const zhOpenAPI = createOpenAPI({ input: ["openapi/service-api-zh.json"] });
|
const zhOpenAPI = createOpenAPI({ input: ["openapi/service-api-zh.json"] });
|
||||||
const jaOpenAPI = createOpenAPI({ input: ["openapi/service-api-ja.json"] });
|
const jaOpenAPI = createOpenAPI({ input: ["openapi/service-api-ja.json"] });
|
||||||
|
|
||||||
|
function legacyOpenApiSlug(value: string) {
|
||||||
|
return value.replace(/\s+/g, "-").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyOpenApiLayout = {
|
||||||
|
groupBy: "tag" as const,
|
||||||
|
name: (entry: { info: { title: string } }) => legacyOpenApiSlug(entry.info.title),
|
||||||
|
};
|
||||||
|
|
||||||
|
function encodeRoute(route: string) {
|
||||||
|
return route.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const openApiAlternates = new Map<string, Partial<Record<(typeof LOCALES)[number], string>>>();
|
||||||
|
for (const locale of LOCALES) {
|
||||||
|
const spec = JSON.parse(readFileSync(`openapi/service-api-${locale}.json`, "utf8"));
|
||||||
|
for (const [apiPath, pathItem] of Object.entries(spec.paths ?? {}) as [string, Record<string, any>][]) {
|
||||||
|
for (const [method, operation] of Object.entries(pathItem)) {
|
||||||
|
if (!/^(?:get|post|put|patch|delete|head|options)$/i.test(method) || !operation?.summary) continue;
|
||||||
|
const tag = operation.tags?.[0] ?? "unknown";
|
||||||
|
const route = encodeRoute(`api-reference/${legacyOpenApiSlug(tag)}/${legacyOpenApiSlug(operation.summary)}`);
|
||||||
|
const key = `${method.toLowerCase()} ${apiPath}`;
|
||||||
|
const alternates = openApiAlternates.get(key) ?? {};
|
||||||
|
alternates[locale] = route;
|
||||||
|
openApiAlternates.set(key, alternates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const openApiAlternatesByRoute = new Map<string, Partial<Record<(typeof LOCALES)[number], string>>>();
|
||||||
|
for (const alternates of openApiAlternates.values()) {
|
||||||
|
for (const locale of LOCALES) {
|
||||||
|
const route = alternates[locale];
|
||||||
|
if (route) openApiAlternatesByRoute.set(`${locale}:${route}`, alternates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function localizeOpenAPISource(
|
function localizeOpenAPISource(
|
||||||
source: Awaited<ReturnType<OpenAPIServer["staticSource"]>>,
|
source: Awaited<ReturnType<OpenAPIServer["staticSource"]>>,
|
||||||
locale: string,
|
locale: string,
|
||||||
@@ -103,18 +149,19 @@ export default defineConfig({
|
|||||||
content: {
|
content: {
|
||||||
docs: docs.toFumadocsSource(),
|
docs: docs.toFumadocsSource(),
|
||||||
openapiEn: localizeOpenAPISource(
|
openapiEn: localizeOpenAPISource(
|
||||||
await enOpenAPI.staticSource({ baseDir: "en/api-reference" }),
|
await enOpenAPI.staticSource({ baseDir: "en/api-reference", ...legacyOpenApiLayout }),
|
||||||
"en",
|
"en",
|
||||||
),
|
),
|
||||||
openapiZh: localizeOpenAPISource(
|
openapiZh: localizeOpenAPISource(
|
||||||
await zhOpenAPI.staticSource({ baseDir: "zh/api-reference" }),
|
await zhOpenAPI.staticSource({ baseDir: "zh/api-reference", ...legacyOpenApiLayout }),
|
||||||
"zh",
|
"zh",
|
||||||
),
|
),
|
||||||
openapiJa: localizeOpenAPISource(
|
openapiJa: localizeOpenAPISource(
|
||||||
await jaOpenAPI.staticSource({ baseDir: "ja/api-reference" }),
|
await jaOpenAPI.staticSource({ baseDir: "ja/api-reference", ...legacyOpenApiLayout }),
|
||||||
"ja",
|
"ja",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
i18n,
|
||||||
translations,
|
translations,
|
||||||
defaultLayoutProps: ({ lang }) => ({
|
defaultLayoutProps: ({ lang }) => ({
|
||||||
links: localizedNavbar(lang),
|
links: localizedNavbar(lang),
|
||||||
@@ -123,10 +170,10 @@ export default defineConfig({
|
|||||||
page: (page) => {
|
page: (page) => {
|
||||||
const locale = page.locale as (typeof LOCALES)[number];
|
const locale = page.locale as (typeof LOCALES)[number];
|
||||||
const route = page.slugs.join("/");
|
const route = page.slugs.join("/");
|
||||||
const available = route.startsWith("api-reference/")
|
const localizedRoutes = route.startsWith("api-reference/")
|
||||||
? new Set<string>(LOCALES)
|
? openApiAlternatesByRoute.get(`${locale}:${route}`) ?? { [locale]: route }
|
||||||
: routeLocales.get(route) ?? new Set<string>([locale]);
|
: Object.fromEntries([...routeLocales.get(route) ?? new Set<string>([locale])].map((candidate) => [candidate, route]));
|
||||||
const alternates = LOCALES.filter((candidate) => available.has(candidate));
|
const alternates = LOCALES.filter((candidate) => localizedRoutes[candidate]);
|
||||||
const defaultLocale = alternates.includes("en") ? "en" : alternates[0];
|
const defaultLocale = alternates.includes("en") ? "en" : alternates[0];
|
||||||
return <>
|
return <>
|
||||||
<link rel="canonical" href={`${SITE_URL}${page.url}`} />
|
<link rel="canonical" href={`${SITE_URL}${page.url}`} />
|
||||||
@@ -135,14 +182,14 @@ export default defineConfig({
|
|||||||
key={candidate}
|
key={candidate}
|
||||||
rel="alternate"
|
rel="alternate"
|
||||||
hrefLang={HREFLANG[candidate]}
|
hrefLang={HREFLANG[candidate]}
|
||||||
href={`${SITE_URL}/${candidate}/${route}`.replace(/\/$/, "")}
|
href={`${SITE_URL}/${candidate}/${localizedRoutes[candidate]}`.replace(/\/$/, "")}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{defaultLocale && (
|
{defaultLocale && (
|
||||||
<link
|
<link
|
||||||
rel="alternate"
|
rel="alternate"
|
||||||
hrefLang="x-default"
|
hrefLang="x-default"
|
||||||
href={`${SITE_URL}/${defaultLocale}/${route}`.replace(/\/$/, "")}
|
href={`${SITE_URL}/${defaultLocale}/${localizedRoutes[defaultLocale]}`.replace(/\/$/, "")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>;
|
</>;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function decodeXml(value) {
|
|||||||
.replaceAll("'", "'");
|
.replaceAll("'", "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderAlternateSitemap(sitemap, base = BASE) {
|
export function renderAlternateSitemap(sitemap, base = BASE, explicitGroups = []) {
|
||||||
const urls = [...sitemap.matchAll(/<loc>([^<]+)<\/loc>/g)].map((match) => decodeXml(match[1]));
|
const urls = [...sitemap.matchAll(/<loc>([^<]+)<\/loc>/g)].map((match) => decodeXml(match[1]));
|
||||||
const groups = new Map();
|
const groups = new Map();
|
||||||
for (const url of urls) {
|
for (const url of urls) {
|
||||||
@@ -42,6 +42,8 @@ export function renderAlternateSitemap(sitemap, base = BASE) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const group of explicitGroups) groups.set(`explicit:${group.en}`, group);
|
||||||
|
|
||||||
const shared = [...groups.values()]
|
const shared = [...groups.values()]
|
||||||
.filter((group) => LOCALES.every((locale) => group[locale]))
|
.filter((group) => LOCALES.every((locale) => group[locale]))
|
||||||
.sort((left, right) => left.en.localeCompare(right.en));
|
.sort((left, right) => left.en.localeCompare(right.en));
|
||||||
@@ -66,6 +68,33 @@ export function renderAlternateSitemap(sitemap, base = BASE) {
|
|||||||
return { xml: `${lines.join("\n")}\n`, groups: shared.length };
|
return { xml: `${lines.join("\n")}\n`, groups: shared.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function legacyOpenApiSlug(value) {
|
||||||
|
return value.replace(/\s+/g, "-").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeRoute(route) {
|
||||||
|
return route.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectOpenApiAlternateGroups(root = ROOT, base = BASE) {
|
||||||
|
const groups = new Map();
|
||||||
|
for (const locale of LOCALES) {
|
||||||
|
const spec = JSON.parse(await readFile(path.join(root, `openapi/service-api-${locale}.json`), "utf8"));
|
||||||
|
for (const [apiPath, pathItem] of Object.entries(spec.paths ?? {})) {
|
||||||
|
for (const [method, operation] of Object.entries(pathItem)) {
|
||||||
|
if (!/^(?:get|post|put|patch|delete|head|options)$/i.test(method) || !operation?.summary) continue;
|
||||||
|
const tag = operation.tags?.[0] ?? "unknown";
|
||||||
|
const route = encodeRoute(`api-reference/${legacyOpenApiSlug(tag)}/${legacyOpenApiSlug(operation.summary)}`);
|
||||||
|
const key = `${method.toLowerCase()} ${apiPath}`;
|
||||||
|
const group = groups.get(key) ?? {};
|
||||||
|
group[locale] = `${base}/${locale}/${route}`;
|
||||||
|
groups.set(key, group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...groups.values()];
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSource(source) {
|
async function loadSource(source) {
|
||||||
if (/^https?:\/\//.test(source)) {
|
if (/^https?:\/\//.test(source)) {
|
||||||
const response = await fetch(source, { headers: { "user-agent": "LangBot docs alternate-sitemap generator" } });
|
const response = await fetch(source, { headers: { "user-agent": "LangBot docs alternate-sitemap generator" } });
|
||||||
@@ -77,7 +106,8 @@ async function loadSource(source) {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const source = process.env.SITEMAP_SOURCE ?? path.join(ROOT, "dist/public/sitemap.xml");
|
const source = process.env.SITEMAP_SOURCE ?? path.join(ROOT, "dist/public/sitemap.xml");
|
||||||
const { xml, groups } = renderAlternateSitemap(await loadSource(source));
|
const openApiGroups = await collectOpenApiAlternateGroups();
|
||||||
|
const { xml, groups } = renderAlternateSitemap(await loadSource(source), BASE, openApiGroups);
|
||||||
const outputs = process.env.SITEMAP_OUTPUT
|
const outputs = process.env.SITEMAP_OUTPUT
|
||||||
? [path.resolve(process.env.SITEMAP_OUTPUT)]
|
? [path.resolve(process.env.SITEMAP_OUTPUT)]
|
||||||
: [path.join(ROOT, "dist/public/sitemap-alternates.xml"), path.join(ROOT, "sitemap-alternates.xml")];
|
: [path.join(ROOT, "dist/public/sitemap-alternates.xml"), path.join(ROOT, "sitemap-alternates.xml")];
|
||||||
|
|||||||
@@ -115,33 +115,15 @@ function cloudflarePattern(value, destination = false) {
|
|||||||
return value.replace(/:([A-Za-z][A-Za-z0-9_]*)\*/g, destination ? ":splat" : "*");
|
return value.replace(/:([A-Za-z][A-Za-z0-9_]*)\*/g, destination ? ":splat" : "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
function slugifyOpenApiSummary(value) {
|
export function renderCloudflareRedirects(docs) {
|
||||||
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
const lines = [
|
||||||
}
|
"/ /en/insight/guide 302",
|
||||||
|
"/zh/develop/adapter/discord /zh/develop/adapter/discord/README 308",
|
||||||
export function collectOpenApiRedirectTargets(spec) {
|
"/scripts/README-blog-articles /en/articles 308",
|
||||||
const targets = new Map();
|
];
|
||||||
for (const [apiPath, pathItem] of Object.entries(spec?.paths ?? {})) {
|
|
||||||
for (const [method, operation] of Object.entries(pathItem)) {
|
|
||||||
if (!/^(?:get|post|put|patch|delete|head|options)$/i.test(method) || !operation?.summary) continue;
|
|
||||||
const summarySlug = slugifyOpenApiSummary(operation.summary);
|
|
||||||
const routePath = apiPath.replaceAll("{", "").replaceAll("}", "");
|
|
||||||
targets.set(summarySlug, `/en/api-reference${routePath}/${method.toLowerCase()}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderCloudflareRedirects(docs, englishOpenApiSpec) {
|
|
||||||
const apiTargets = collectOpenApiRedirectTargets(englishOpenApiSpec);
|
|
||||||
const lines = ["/ /en/insight/guide 302"];
|
|
||||||
for (const redirect of docs.redirects ?? []) {
|
for (const redirect of docs.redirects ?? []) {
|
||||||
const status = redirect.permanent === false ? 307 : 308;
|
const status = redirect.permanent === false ? 307 : 308;
|
||||||
const destinationSlug = redirect.destination.match(/^\/en\/api-reference\/[^/]+\/([^/:*]+)$/)?.[1];
|
lines.push(`${cloudflarePattern(redirect.source)} ${cloudflarePattern(redirect.destination, true)} ${status}`);
|
||||||
const destination = destinationSlug && apiTargets.get(destinationSlug)
|
|
||||||
? apiTargets.get(destinationSlug)
|
|
||||||
: redirect.destination;
|
|
||||||
lines.push(`${cloudflarePattern(redirect.source)} ${cloudflarePattern(destination, true)} ${status}`);
|
|
||||||
}
|
}
|
||||||
return `${lines.join("\n")}\n`;
|
return `${lines.join("\n")}\n`;
|
||||||
}
|
}
|
||||||
@@ -159,8 +141,6 @@ export async function prepareFumapress({ root = ROOT, outRoot = root } = {}) {
|
|||||||
if (openapi.length !== LOCALES.length) {
|
if (openapi.length !== LOCALES.length) {
|
||||||
throw new Error(`Expected one nested OpenAPI source per locale, found ${openapi.length}`);
|
throw new Error(`Expected one nested OpenAPI source per locale, found ${openapi.length}`);
|
||||||
}
|
}
|
||||||
const englishOpenApi = JSON.parse(await readFile(path.join(root, openapi.find((item) => item.locale === "en").source), "utf8"));
|
|
||||||
|
|
||||||
const contentRoot = path.join(outRoot, "content");
|
const contentRoot = path.join(outRoot, "content");
|
||||||
const publicRoot = path.join(outRoot, "public");
|
const publicRoot = path.join(outRoot, "public");
|
||||||
await rm(contentRoot, { recursive: true, force: true });
|
await rm(contentRoot, { recursive: true, force: true });
|
||||||
@@ -200,13 +180,13 @@ export async function prepareFumapress({ root = ROOT, outRoot = root } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await cp(path.join(root, "robots.txt"), path.join(publicRoot, "robots.txt"), { force: true });
|
await cp(path.join(root, "robots.txt"), path.join(publicRoot, "robots.txt"), { force: true });
|
||||||
await writeFile(path.join(publicRoot, "_redirects"), renderCloudflareRedirects(docs, englishOpenApi));
|
await writeFile(path.join(publicRoot, "_redirects"), renderCloudflareRedirects(docs));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
documents: documents.length,
|
documents: documents.length,
|
||||||
fallbackDefaults: 0,
|
fallbackDefaults: 0,
|
||||||
localeOnlyDocuments,
|
localeOnlyDocuments,
|
||||||
redirects: (docs.redirects?.length ?? 0) + 1,
|
redirects: (docs.redirects?.length ?? 0) + 3,
|
||||||
locales: [...LOCALES],
|
locales: [...LOCALES],
|
||||||
openapi,
|
openapi,
|
||||||
};
|
};
|
||||||
|
|||||||
+885
-885
File diff suppressed because it is too large
Load Diff
+480
@@ -0,0 +1,480 @@
|
|||||||
|
/en/api-reference/bots/create-a-bot
|
||||||
|
/en/api-reference/bots/delete-a-bot
|
||||||
|
/en/api-reference/bots/get-a-bot
|
||||||
|
/en/api-reference/bots/get-bot-event-logs
|
||||||
|
/en/api-reference/bots/list-all-bots
|
||||||
|
/en/api-reference/bots/send-a-message-through-a-bot
|
||||||
|
/en/api-reference/bots/update-a-bot
|
||||||
|
/en/api-reference/embedding-models/create-an-embedding-model
|
||||||
|
/en/api-reference/embedding-models/delete-an-embedding-model
|
||||||
|
/en/api-reference/embedding-models/get-an-embedding-model
|
||||||
|
/en/api-reference/embedding-models/list-all-embedding-models
|
||||||
|
/en/api-reference/embedding-models/test-an-embedding-model
|
||||||
|
/en/api-reference/embedding-models/update-an-embedding-model
|
||||||
|
/en/api-reference/files/upload-a-document
|
||||||
|
/en/api-reference/files/upload-an-image
|
||||||
|
/en/api-reference/knowledge-bases/add-a-file-to-a-knowledge-base
|
||||||
|
/en/api-reference/knowledge-bases/create-a-knowledge-base
|
||||||
|
/en/api-reference/knowledge-bases/delete-a-knowledge-base
|
||||||
|
/en/api-reference/knowledge-bases/get-a-knowledge-base
|
||||||
|
/en/api-reference/knowledge-bases/list-all-knowledge-bases
|
||||||
|
/en/api-reference/knowledge-bases/list-knowledge-base-files
|
||||||
|
/en/api-reference/knowledge-bases/remove-a-file-from-a-knowledge-base
|
||||||
|
/en/api-reference/knowledge-bases/retrieve-from-a-knowledge-base
|
||||||
|
/en/api-reference/knowledge-bases/update-a-knowledge-base
|
||||||
|
/en/api-reference/knowledge-engines/get-the-knowledge-engine-creation-schema
|
||||||
|
/en/api-reference/knowledge-engines/get-the-knowledge-engine-retrieval-schema
|
||||||
|
/en/api-reference/knowledge-engines/list-available-knowledge-engines
|
||||||
|
/en/api-reference/llm-models/create-an-llm-model
|
||||||
|
/en/api-reference/llm-models/delete-an-llm-model
|
||||||
|
/en/api-reference/llm-models/get-an-llm-model
|
||||||
|
/en/api-reference/llm-models/list-all-llm-models
|
||||||
|
/en/api-reference/llm-models/test-an-llm-model
|
||||||
|
/en/api-reference/llm-models/update-an-llm-model
|
||||||
|
/en/api-reference/model-providers/create-a-model-provider
|
||||||
|
/en/api-reference/model-providers/delete-a-model-provider
|
||||||
|
/en/api-reference/model-providers/get-a-model-provider
|
||||||
|
/en/api-reference/model-providers/list-all-model-providers
|
||||||
|
/en/api-reference/model-providers/update-a-model-provider
|
||||||
|
/en/api-reference/parsers/list-available-parsers
|
||||||
|
/en/api-reference/pipelines/copy-a-pipeline
|
||||||
|
/en/api-reference/pipelines/create-a-pipeline
|
||||||
|
/en/api-reference/pipelines/delete-a-pipeline
|
||||||
|
/en/api-reference/pipelines/get-a-pipeline
|
||||||
|
/en/api-reference/pipelines/get-pipeline-extension-configuration
|
||||||
|
/en/api-reference/pipelines/get-pipeline-metadata
|
||||||
|
/en/api-reference/pipelines/list-all-pipelines
|
||||||
|
/en/api-reference/pipelines/update-a-pipeline
|
||||||
|
/en/api-reference/pipelines/update-pipeline-extension-configuration
|
||||||
|
/en/api-reference/plugins/delete-a-plugin
|
||||||
|
/en/api-reference/plugins/get-plugin-configuration
|
||||||
|
/en/api-reference/plugins/get-plugin-details
|
||||||
|
/en/api-reference/plugins/install-a-plugin-from-github
|
||||||
|
/en/api-reference/plugins/install-a-plugin-from-marketplace
|
||||||
|
/en/api-reference/plugins/list-installed-plugins
|
||||||
|
/en/api-reference/plugins/update-plugin-configuration
|
||||||
|
/en/api-reference/plugins/upgrade-a-plugin
|
||||||
|
/en/api-reference/system/get-system-information
|
||||||
|
/en/api-reference/tools/get-tool-detail
|
||||||
|
/en/api-reference/tools/list-all-available-tools
|
||||||
|
/en/articles
|
||||||
|
/en/articles/connect-deepseek-to-wechat
|
||||||
|
/en/articles/deploy-ai-bot-in-5-minutes
|
||||||
|
/en/articles/dify-agent-discord-telegram-slack
|
||||||
|
/en/articles/dify-langbot-rag-knowledge-base
|
||||||
|
/en/articles/langbot-cloud-multi-user-workspaces
|
||||||
|
/en/articles/langbot-plugin-system-deep-dive
|
||||||
|
/en/articles/langbot-v4100-sandbox-skills
|
||||||
|
/en/articles/langbot-v490-rag-plugin-architecture
|
||||||
|
/en/articles/langflow-drag-and-drop-ai-bot
|
||||||
|
/en/articles/langtars-remote-pc-control-dify-n8n
|
||||||
|
/en/articles/n8n-multi-platform-ai-chatbot
|
||||||
|
/en/articles/welcome
|
||||||
|
/en/deploy/command
|
||||||
|
/en/deploy/langbot/docker
|
||||||
|
/en/deploy/langbot/kubernetes
|
||||||
|
/en/deploy/langbot/manual
|
||||||
|
/en/deploy/langbot/one-click/1panel
|
||||||
|
/en/deploy/langbot/one-click/bt
|
||||||
|
/en/deploy/langbot/package
|
||||||
|
/en/deploy/settings
|
||||||
|
/en/deploy/update
|
||||||
|
/en/develop/agent-context
|
||||||
|
/en/develop/comp-arch
|
||||||
|
/en/develop/dev-config
|
||||||
|
/en/develop/plugin-runtime
|
||||||
|
/en/insight/community
|
||||||
|
/en/insight/data-collection-policy
|
||||||
|
/en/insight/features
|
||||||
|
/en/insight/guide
|
||||||
|
/en/insight/platform-features
|
||||||
|
/en/insight/troubleshooting
|
||||||
|
/en/plugin/compatibility
|
||||||
|
/en/plugin/dev/apis/common
|
||||||
|
/en/plugin/dev/apis/messages
|
||||||
|
/en/plugin/dev/apis/pipeline-events
|
||||||
|
/en/plugin/dev/apis/tech-details
|
||||||
|
/en/plugin/dev/basic-info
|
||||||
|
/en/plugin/dev/components/add
|
||||||
|
/en/plugin/dev/components/command
|
||||||
|
/en/plugin/dev/components/event-listener
|
||||||
|
/en/plugin/dev/components/knowledge-engine
|
||||||
|
/en/plugin/dev/components/knowledge-retriever
|
||||||
|
/en/plugin/dev/components/page
|
||||||
|
/en/plugin/dev/components/parser
|
||||||
|
/en/plugin/dev/components/tool
|
||||||
|
/en/plugin/dev/directory-structure
|
||||||
|
/en/plugin/dev/migration
|
||||||
|
/en/plugin/dev/publish/github
|
||||||
|
/en/plugin/dev/publish/market
|
||||||
|
/en/plugin/dev/style
|
||||||
|
/en/plugin/dev/tutor
|
||||||
|
/en/plugin/plugin-intro
|
||||||
|
/en/tags/readme
|
||||||
|
/en/tags/webhook
|
||||||
|
/en/usage/knowledge/readme
|
||||||
|
/en/usage/mcp/readme
|
||||||
|
/en/usage/models/readme
|
||||||
|
/en/usage/pipelines/coze
|
||||||
|
/en/usage/pipelines/deerflow
|
||||||
|
/en/usage/pipelines/dify
|
||||||
|
/en/usage/pipelines/langflow
|
||||||
|
/en/usage/pipelines/n8n
|
||||||
|
/en/usage/pipelines/readme
|
||||||
|
/en/usage/pipelines/weknora
|
||||||
|
/en/usage/platforms/dingtalk
|
||||||
|
/en/usage/platforms/discord
|
||||||
|
/en/usage/platforms/http-bot
|
||||||
|
/en/usage/platforms/kook
|
||||||
|
/en/usage/platforms/lark
|
||||||
|
/en/usage/platforms/line
|
||||||
|
/en/usage/platforms/qq/aiocqhttp/lagrange
|
||||||
|
/en/usage/platforms/qq/aiocqhttp/llonebot
|
||||||
|
/en/usage/platforms/qq/aiocqhttp/napcat
|
||||||
|
/en/usage/platforms/qq/official_webhook
|
||||||
|
/en/usage/platforms/readme
|
||||||
|
/en/usage/platforms/satori/llonebot
|
||||||
|
/en/usage/platforms/slack
|
||||||
|
/en/usage/platforms/telegram
|
||||||
|
/en/usage/platforms/webpage
|
||||||
|
/en/usage/platforms/wechat/weixin
|
||||||
|
/en/usage/platforms/wecom/wecom
|
||||||
|
/en/usage/platforms/wecom/wecombot
|
||||||
|
/en/usage/platforms/wecom/wecomcs
|
||||||
|
/en/usage/platforms/wxoa
|
||||||
|
/en/usage/sandbox/config
|
||||||
|
/en/usage/sandbox/readme
|
||||||
|
/en/usage/sandbox/runtime-relationships
|
||||||
|
/en/usage/skills/readme
|
||||||
|
/en/workshop/302ai-integration
|
||||||
|
/en/workshop/impl-platform-adapter
|
||||||
|
/en/workshop/mcp-details
|
||||||
|
/en/workshop/network-details
|
||||||
|
/en/workshop/newapi-integration
|
||||||
|
/en/workshop/ppio-integration
|
||||||
|
/en/workshop/production/proxy-and-ssl
|
||||||
|
/ja/api-reference/bots/创建机器人
|
||||||
|
/ja/api-reference/bots/删除机器人
|
||||||
|
/ja/api-reference/bots/更新机器人
|
||||||
|
/ja/api-reference/bots/获取单个机器人
|
||||||
|
/ja/api-reference/bots/获取所有机器人
|
||||||
|
/ja/api-reference/bots/获取机器人事件日志
|
||||||
|
/ja/api-reference/bots/通过机器人发送消息
|
||||||
|
/ja/api-reference/embedding-models/创建向量模型
|
||||||
|
/ja/api-reference/embedding-models/删除向量模型
|
||||||
|
/ja/api-reference/embedding-models/更新向量模型
|
||||||
|
/ja/api-reference/embedding-models/测试向量模型
|
||||||
|
/ja/api-reference/embedding-models/获取单个向量模型
|
||||||
|
/ja/api-reference/embedding-models/获取所有向量模型
|
||||||
|
/ja/api-reference/files/上传图片
|
||||||
|
/ja/api-reference/files/上传文档
|
||||||
|
/ja/api-reference/knowledge-bases/从知识库删除文件
|
||||||
|
/ja/api-reference/knowledge-bases/创建知识库
|
||||||
|
/ja/api-reference/knowledge-bases/删除知识库
|
||||||
|
/ja/api-reference/knowledge-bases/将文件存入知识库
|
||||||
|
/ja/api-reference/knowledge-bases/更新知识库
|
||||||
|
/ja/api-reference/knowledge-bases/检索知识库
|
||||||
|
/ja/api-reference/knowledge-bases/获取单个知识库
|
||||||
|
/ja/api-reference/knowledge-bases/获取所有知识库
|
||||||
|
/ja/api-reference/knowledge-bases/获取知识库文件列表
|
||||||
|
/ja/api-reference/knowledge-engines/获取可用知识引擎列表
|
||||||
|
/ja/api-reference/knowledge-engines/获取知识引擎创建配置模式
|
||||||
|
/ja/api-reference/knowledge-engines/获取知识引擎检索配置模式
|
||||||
|
/ja/api-reference/llm-models/创建-llm-模型
|
||||||
|
/ja/api-reference/llm-models/删除-llm-模型
|
||||||
|
/ja/api-reference/llm-models/更新-llm-模型
|
||||||
|
/ja/api-reference/llm-models/测试-llm-模型
|
||||||
|
/ja/api-reference/llm-models/获取单个-llm-模型
|
||||||
|
/ja/api-reference/llm-models/获取所有-llm-模型
|
||||||
|
/ja/api-reference/model-providers/创建供应商
|
||||||
|
/ja/api-reference/model-providers/删除供应商
|
||||||
|
/ja/api-reference/model-providers/更新供应商
|
||||||
|
/ja/api-reference/model-providers/获取单个供应商
|
||||||
|
/ja/api-reference/model-providers/获取所有供应商
|
||||||
|
/ja/api-reference/parsers/获取可用解析器列表
|
||||||
|
/ja/api-reference/pipelines/创建流水线
|
||||||
|
/ja/api-reference/pipelines/删除流水线
|
||||||
|
/ja/api-reference/pipelines/复制流水线
|
||||||
|
/ja/api-reference/pipelines/更新流水线
|
||||||
|
/ja/api-reference/pipelines/更新流水线扩展配置
|
||||||
|
/ja/api-reference/pipelines/获取单个流水线
|
||||||
|
/ja/api-reference/pipelines/获取所有流水线
|
||||||
|
/ja/api-reference/pipelines/获取流水线元数据
|
||||||
|
/ja/api-reference/pipelines/获取流水线扩展配置
|
||||||
|
/ja/api-reference/plugins/从-github-安装插件
|
||||||
|
/ja/api-reference/plugins/从-marketplace-安装插件
|
||||||
|
/ja/api-reference/plugins/删除插件
|
||||||
|
/ja/api-reference/plugins/升级插件
|
||||||
|
/ja/api-reference/plugins/更新插件配置
|
||||||
|
/ja/api-reference/plugins/获取已安装插件列表
|
||||||
|
/ja/api-reference/plugins/获取插件详情
|
||||||
|
/ja/api-reference/plugins/获取插件配置
|
||||||
|
/ja/api-reference/system/获取系统信息
|
||||||
|
/ja/api-reference/ツール/ツール詳細を取得
|
||||||
|
/ja/api-reference/ツール/利用可能なツール一覧を取得
|
||||||
|
/ja/articles
|
||||||
|
/ja/articles/connect-deepseek-to-wechat
|
||||||
|
/ja/articles/deploy-ai-bot-in-5-minutes
|
||||||
|
/ja/articles/dify-agent-discord-telegram-slack
|
||||||
|
/ja/articles/dify-langbot-rag-knowledge-base
|
||||||
|
/ja/articles/langbot-cloud-multi-user-workspaces
|
||||||
|
/ja/articles/langbot-plugin-system-deep-dive
|
||||||
|
/ja/articles/langbot-v4100-sandbox-skills
|
||||||
|
/ja/articles/langbot-v490-rag-plugin-architecture
|
||||||
|
/ja/articles/langflow-drag-and-drop-ai-bot
|
||||||
|
/ja/articles/langtars-remote-pc-control-dify-n8n
|
||||||
|
/ja/articles/n8n-multi-platform-ai-chatbot
|
||||||
|
/ja/articles/welcome
|
||||||
|
/ja/deploy/command
|
||||||
|
/ja/deploy/langbot/docker
|
||||||
|
/ja/deploy/langbot/kubernetes
|
||||||
|
/ja/deploy/langbot/manual
|
||||||
|
/ja/deploy/langbot/one-click/1panel
|
||||||
|
/ja/deploy/langbot/one-click/bt
|
||||||
|
/ja/deploy/langbot/package
|
||||||
|
/ja/deploy/settings
|
||||||
|
/ja/deploy/update
|
||||||
|
/ja/develop/agent-context
|
||||||
|
/ja/develop/comp-arch
|
||||||
|
/ja/develop/dev-config
|
||||||
|
/ja/develop/plugin-runtime
|
||||||
|
/ja/insight/community
|
||||||
|
/ja/insight/data-collection-policy
|
||||||
|
/ja/insight/features
|
||||||
|
/ja/insight/guide
|
||||||
|
/ja/insight/platform-features
|
||||||
|
/ja/insight/troubleshooting
|
||||||
|
/ja/plugin/compatibility
|
||||||
|
/ja/plugin/dev/apis/common
|
||||||
|
/ja/plugin/dev/apis/messages
|
||||||
|
/ja/plugin/dev/apis/pipeline-events
|
||||||
|
/ja/plugin/dev/apis/tech-details
|
||||||
|
/ja/plugin/dev/basic-info
|
||||||
|
/ja/plugin/dev/components/add
|
||||||
|
/ja/plugin/dev/components/command
|
||||||
|
/ja/plugin/dev/components/event-listener
|
||||||
|
/ja/plugin/dev/components/knowledge-engine
|
||||||
|
/ja/plugin/dev/components/knowledge-retriever
|
||||||
|
/ja/plugin/dev/components/page
|
||||||
|
/ja/plugin/dev/components/parser
|
||||||
|
/ja/plugin/dev/components/tool
|
||||||
|
/ja/plugin/dev/directory-structure
|
||||||
|
/ja/plugin/dev/migration
|
||||||
|
/ja/plugin/dev/publish/github
|
||||||
|
/ja/plugin/dev/publish/market
|
||||||
|
/ja/plugin/dev/style
|
||||||
|
/ja/plugin/dev/tutor
|
||||||
|
/ja/plugin/plugin-intro
|
||||||
|
/ja/tags/readme
|
||||||
|
/ja/tags/webhook
|
||||||
|
/ja/usage/knowledge/readme
|
||||||
|
/ja/usage/mcp/readme
|
||||||
|
/ja/usage/models/readme
|
||||||
|
/ja/usage/pipelines/coze
|
||||||
|
/ja/usage/pipelines/deerflow
|
||||||
|
/ja/usage/pipelines/dify
|
||||||
|
/ja/usage/pipelines/langflow
|
||||||
|
/ja/usage/pipelines/n8n
|
||||||
|
/ja/usage/pipelines/readme
|
||||||
|
/ja/usage/pipelines/weknora
|
||||||
|
/ja/usage/platforms/dingtalk
|
||||||
|
/ja/usage/platforms/discord
|
||||||
|
/ja/usage/platforms/http-bot
|
||||||
|
/ja/usage/platforms/kook
|
||||||
|
/ja/usage/platforms/lark
|
||||||
|
/ja/usage/platforms/line
|
||||||
|
/ja/usage/platforms/qq/aiocqhttp/lagrange
|
||||||
|
/ja/usage/platforms/qq/aiocqhttp/llonebot
|
||||||
|
/ja/usage/platforms/qq/aiocqhttp/napcat
|
||||||
|
/ja/usage/platforms/qq/official_webhook
|
||||||
|
/ja/usage/platforms/readme
|
||||||
|
/ja/usage/platforms/satori/llonebot
|
||||||
|
/ja/usage/platforms/slack
|
||||||
|
/ja/usage/platforms/telegram
|
||||||
|
/ja/usage/platforms/webpage
|
||||||
|
/ja/usage/platforms/wechat/weixin
|
||||||
|
/ja/usage/platforms/wecom/wecom
|
||||||
|
/ja/usage/platforms/wecom/wecombot
|
||||||
|
/ja/usage/platforms/wecom/wecomcs
|
||||||
|
/ja/usage/platforms/wxoa
|
||||||
|
/ja/usage/sandbox/config
|
||||||
|
/ja/usage/sandbox/readme
|
||||||
|
/ja/usage/sandbox/runtime-relationships
|
||||||
|
/ja/usage/skills/readme
|
||||||
|
/ja/workshop/302ai-integration
|
||||||
|
/ja/workshop/impl-platform-adapter
|
||||||
|
/ja/workshop/mcp-details
|
||||||
|
/ja/workshop/network-details
|
||||||
|
/ja/workshop/newapi-integration
|
||||||
|
/ja/workshop/ppio-integration
|
||||||
|
/ja/workshop/production/proxy-and-ssl
|
||||||
|
/scripts/README-blog-articles
|
||||||
|
/zh/api-reference/向量模型/创建向量模型
|
||||||
|
/zh/api-reference/向量模型/删除向量模型
|
||||||
|
/zh/api-reference/向量模型/更新向量模型
|
||||||
|
/zh/api-reference/向量模型/测试向量模型
|
||||||
|
/zh/api-reference/向量模型/获取单个向量模型
|
||||||
|
/zh/api-reference/向量模型/获取所有向量模型
|
||||||
|
/zh/api-reference/大语言模型/创建-llm-模型
|
||||||
|
/zh/api-reference/大语言模型/删除-llm-模型
|
||||||
|
/zh/api-reference/大语言模型/更新-llm-模型
|
||||||
|
/zh/api-reference/大语言模型/测试-llm-模型
|
||||||
|
/zh/api-reference/大语言模型/获取单个-llm-模型
|
||||||
|
/zh/api-reference/大语言模型/获取所有-llm-模型
|
||||||
|
/zh/api-reference/工具/获取工具详情
|
||||||
|
/zh/api-reference/工具/获取所有可用工具
|
||||||
|
/zh/api-reference/插件/从-github-安装插件
|
||||||
|
/zh/api-reference/插件/从-marketplace-安装插件
|
||||||
|
/zh/api-reference/插件/删除插件
|
||||||
|
/zh/api-reference/插件/升级插件
|
||||||
|
/zh/api-reference/插件/更新插件配置
|
||||||
|
/zh/api-reference/插件/获取已安装插件列表
|
||||||
|
/zh/api-reference/插件/获取插件详情
|
||||||
|
/zh/api-reference/插件/获取插件配置
|
||||||
|
/zh/api-reference/文件/上传图片
|
||||||
|
/zh/api-reference/文件/上传文档
|
||||||
|
/zh/api-reference/机器人/创建机器人
|
||||||
|
/zh/api-reference/机器人/删除机器人
|
||||||
|
/zh/api-reference/机器人/更新机器人
|
||||||
|
/zh/api-reference/机器人/获取单个机器人
|
||||||
|
/zh/api-reference/机器人/获取所有机器人
|
||||||
|
/zh/api-reference/机器人/获取机器人事件日志
|
||||||
|
/zh/api-reference/机器人/通过机器人发送消息
|
||||||
|
/zh/api-reference/模型供应商/创建供应商
|
||||||
|
/zh/api-reference/模型供应商/删除供应商
|
||||||
|
/zh/api-reference/模型供应商/更新供应商
|
||||||
|
/zh/api-reference/模型供应商/获取单个供应商
|
||||||
|
/zh/api-reference/模型供应商/获取所有供应商
|
||||||
|
/zh/api-reference/流水线/创建流水线
|
||||||
|
/zh/api-reference/流水线/删除流水线
|
||||||
|
/zh/api-reference/流水线/复制流水线
|
||||||
|
/zh/api-reference/流水线/更新流水线
|
||||||
|
/zh/api-reference/流水线/更新流水线扩展配置
|
||||||
|
/zh/api-reference/流水线/获取单个流水线
|
||||||
|
/zh/api-reference/流水线/获取所有流水线
|
||||||
|
/zh/api-reference/流水线/获取流水线元数据
|
||||||
|
/zh/api-reference/流水线/获取流水线扩展配置
|
||||||
|
/zh/api-reference/知识库/从知识库删除文件
|
||||||
|
/zh/api-reference/知识库/创建知识库
|
||||||
|
/zh/api-reference/知识库/删除知识库
|
||||||
|
/zh/api-reference/知识库/将文件存入知识库
|
||||||
|
/zh/api-reference/知识库/更新知识库
|
||||||
|
/zh/api-reference/知识库/检索知识库
|
||||||
|
/zh/api-reference/知识库/获取单个知识库
|
||||||
|
/zh/api-reference/知识库/获取所有知识库
|
||||||
|
/zh/api-reference/知识库/获取知识库文件列表
|
||||||
|
/zh/api-reference/知识引擎/获取可用知识引擎列表
|
||||||
|
/zh/api-reference/知识引擎/获取知识引擎创建配置模式
|
||||||
|
/zh/api-reference/知识引擎/获取知识引擎检索配置模式
|
||||||
|
/zh/api-reference/系统/获取系统信息
|
||||||
|
/zh/api-reference/解析器/获取可用解析器列表
|
||||||
|
/zh/articles
|
||||||
|
/zh/articles/connect-deepseek-to-wechat
|
||||||
|
/zh/articles/deploy-ai-bot-in-5-minutes
|
||||||
|
/zh/articles/dify-agent-discord-telegram-slack
|
||||||
|
/zh/articles/dify-langbot-rag-knowledge-base
|
||||||
|
/zh/articles/langbot-cloud-multi-user-workspaces
|
||||||
|
/zh/articles/langbot-plugin-system-deep-dive
|
||||||
|
/zh/articles/langbot-v4100-sandbox-skills
|
||||||
|
/zh/articles/langbot-v490-rag-plugin-architecture
|
||||||
|
/zh/articles/langflow-drag-and-drop-ai-bot
|
||||||
|
/zh/articles/langtars-remote-pc-control-dify-n8n
|
||||||
|
/zh/articles/n8n-multi-platform-ai-chatbot
|
||||||
|
/zh/articles/welcome
|
||||||
|
/zh/deploy/command
|
||||||
|
/zh/deploy/langbot/docker
|
||||||
|
/zh/deploy/langbot/kubernetes
|
||||||
|
/zh/deploy/langbot/manual
|
||||||
|
/zh/deploy/langbot/one-click/1panel
|
||||||
|
/zh/deploy/langbot/one-click/bt
|
||||||
|
/zh/deploy/langbot/package
|
||||||
|
/zh/deploy/settings
|
||||||
|
/zh/deploy/update
|
||||||
|
/zh/develop/adapter/discord/README
|
||||||
|
/zh/develop/adapter/discord/api_reference
|
||||||
|
/zh/develop/adapter/discord/design
|
||||||
|
/zh/develop/adapter/discord/quick_start
|
||||||
|
/zh/develop/adapter/discord/troubleshooting
|
||||||
|
/zh/develop/agent-context
|
||||||
|
/zh/develop/comp-arch
|
||||||
|
/zh/develop/dev-config
|
||||||
|
/zh/develop/plugin-runtime
|
||||||
|
/zh/insight/community
|
||||||
|
/zh/insight/data-collection-policy
|
||||||
|
/zh/insight/features
|
||||||
|
/zh/insight/guide
|
||||||
|
/zh/insight/platform-features
|
||||||
|
/zh/insight/troubleshooting
|
||||||
|
/zh/plugin/compatibility
|
||||||
|
/zh/plugin/dev/apis/common
|
||||||
|
/zh/plugin/dev/apis/messages
|
||||||
|
/zh/plugin/dev/apis/pipeline-events
|
||||||
|
/zh/plugin/dev/apis/tech-details
|
||||||
|
/zh/plugin/dev/basic-info
|
||||||
|
/zh/plugin/dev/components/add
|
||||||
|
/zh/plugin/dev/components/command
|
||||||
|
/zh/plugin/dev/components/event-listener
|
||||||
|
/zh/plugin/dev/components/knowledge-engine
|
||||||
|
/zh/plugin/dev/components/knowledge-retriever
|
||||||
|
/zh/plugin/dev/components/page
|
||||||
|
/zh/plugin/dev/components/parser
|
||||||
|
/zh/plugin/dev/components/tool
|
||||||
|
/zh/plugin/dev/directory-structure
|
||||||
|
/zh/plugin/dev/migration
|
||||||
|
/zh/plugin/dev/publish/github
|
||||||
|
/zh/plugin/dev/publish/market
|
||||||
|
/zh/plugin/dev/style
|
||||||
|
/zh/plugin/dev/tutor
|
||||||
|
/zh/plugin/plugin-intro
|
||||||
|
/zh/tags/readme
|
||||||
|
/zh/tags/webhook
|
||||||
|
/zh/tob
|
||||||
|
/zh/tob/contact
|
||||||
|
/zh/tob/platform
|
||||||
|
/zh/tob/provider
|
||||||
|
/zh/usage/knowledge/readme
|
||||||
|
/zh/usage/mcp/readme
|
||||||
|
/zh/usage/models/readme
|
||||||
|
/zh/usage/pipelines/coze
|
||||||
|
/zh/usage/pipelines/deerflow
|
||||||
|
/zh/usage/pipelines/dify
|
||||||
|
/zh/usage/pipelines/langflow
|
||||||
|
/zh/usage/pipelines/n8n
|
||||||
|
/zh/usage/pipelines/readme
|
||||||
|
/zh/usage/pipelines/tbox
|
||||||
|
/zh/usage/pipelines/weknora
|
||||||
|
/zh/usage/platforms/dingtalk
|
||||||
|
/zh/usage/platforms/discord
|
||||||
|
/zh/usage/platforms/http-bot
|
||||||
|
/zh/usage/platforms/kook
|
||||||
|
/zh/usage/platforms/lark
|
||||||
|
/zh/usage/platforms/line
|
||||||
|
/zh/usage/platforms/qq/aiocqhttp/lagrange
|
||||||
|
/zh/usage/platforms/qq/aiocqhttp/llonebot
|
||||||
|
/zh/usage/platforms/qq/aiocqhttp/napcat
|
||||||
|
/zh/usage/platforms/qq/official
|
||||||
|
/zh/usage/platforms/qq/official_webhook
|
||||||
|
/zh/usage/platforms/readme
|
||||||
|
/zh/usage/platforms/satori/llonebot
|
||||||
|
/zh/usage/platforms/slack
|
||||||
|
/zh/usage/platforms/telegram
|
||||||
|
/zh/usage/platforms/webpage
|
||||||
|
/zh/usage/platforms/wechat/wechatpad
|
||||||
|
/zh/usage/platforms/wechat/weixin
|
||||||
|
/zh/usage/platforms/wecom/wecom
|
||||||
|
/zh/usage/platforms/wecom/wecombot
|
||||||
|
/zh/usage/platforms/wecom/wecomcs
|
||||||
|
/zh/usage/platforms/wxoa
|
||||||
|
/zh/usage/sandbox/config
|
||||||
|
/zh/usage/sandbox/readme
|
||||||
|
/zh/usage/sandbox/runtime-relationships
|
||||||
|
/zh/usage/skills/readme
|
||||||
|
/zh/workshop/302ai-integration
|
||||||
|
/zh/workshop/impl-platform-adapter
|
||||||
|
/zh/workshop/mcp-details
|
||||||
|
/zh/workshop/network-details
|
||||||
|
/zh/workshop/newapi-integration
|
||||||
|
/zh/workshop/ppio-integration
|
||||||
|
/zh/workshop/production/proxy-and-ssl
|
||||||
|
/zh/workshop/shengsuanyun-integration
|
||||||
|
/zh/workshop/tokenpony-integration
|
||||||
@@ -144,14 +144,15 @@ test("canonical MDX and nested OpenAPI sources are discovered", async () => {
|
|||||||
|
|
||||||
test("Cloudflare redirects preserve all Mintlify routes and add root", async () => {
|
test("Cloudflare redirects preserve all Mintlify routes and add root", async () => {
|
||||||
const docs = JSON.parse(await readFile(path.join(root, "docs.json"), "utf8"));
|
const docs = JSON.parse(await readFile(path.join(root, "docs.json"), "utf8"));
|
||||||
const spec = JSON.parse(await readFile(path.join(root, "openapi/service-api-en.json"), "utf8"));
|
const lines = renderCloudflareRedirects(docs).trim().split("\n");
|
||||||
const lines = renderCloudflareRedirects(docs, spec).trim().split("\n");
|
assert.equal(lines.length, docs.redirects.length + 3);
|
||||||
assert.equal(lines.length, docs.redirects.length + 1);
|
|
||||||
assert.equal(lines[0], "/ /en/insight/guide 302");
|
assert.equal(lines[0], "/ /en/insight/guide 302");
|
||||||
assert.ok(lines.includes("/README_EN /en/insight/guide 308"));
|
assert.ok(lines.includes("/README_EN /en/insight/guide 308"));
|
||||||
|
assert.ok(lines.includes("/zh/develop/adapter/discord /zh/develop/adapter/discord/README 308"));
|
||||||
|
assert.ok(lines.includes("/scripts/README-blog-articles /en/articles 308"));
|
||||||
assert.ok(lines.some((line) => line.startsWith("/en/deploy/platforms/* /en/usage/platforms/:splat ")));
|
assert.ok(lines.some((line) => line.startsWith("/en/deploy/platforms/* /en/usage/platforms/:splat ")));
|
||||||
assert.ok(lines.includes("/en/api-reference/system/获取系统信息 /en/api-reference/api/v1/system/info/get 308"));
|
assert.ok(lines.includes("/en/api-reference/system/获取系统信息 /en/api-reference/system/get-system-information 308"));
|
||||||
assert.ok(lines.includes("/en/api-reference/model-providers/创建供应商 /en/api-reference/api/v1/provider/providers/post 308"));
|
assert.ok(lines.includes("/en/api-reference/model-providers/创建供应商 /en/api-reference/model-providers/create-a-model-provider 308"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Fumapress config wires static localized docs, OpenAPI, and Mintlify support", async () => {
|
test("Fumapress config wires static localized docs, OpenAPI, and Mintlify support", async () => {
|
||||||
@@ -165,6 +166,8 @@ test("Fumapress config wires static localized docs, OpenAPI, and Mintlify suppor
|
|||||||
assert.match(config, new RegExp(`input:[\\s\\S]*openapi/${filename}`));
|
assert.match(config, new RegExp(`input:[\\s\\S]*openapi/${filename}`));
|
||||||
assert.match(config, new RegExp(`staticSource\\(\\{\\s*baseDir:\\s*["']${locale}/api-reference["']`));
|
assert.match(config, new RegExp(`staticSource\\(\\{\\s*baseDir:\\s*["']${locale}/api-reference["']`));
|
||||||
}
|
}
|
||||||
|
assert.match(config, /groupBy:\s*["']tag["']/);
|
||||||
|
assert.match(config, /legacyOpenApiSlug/);
|
||||||
assert.match(config, /mintlifyPlugin\(/);
|
assert.match(config, /mintlifyPlugin\(/);
|
||||||
assert.match(config, /openapiPlugin\(\{\s*server:\s*\w+OpenAPI\s*\}\)/);
|
assert.match(config, /openapiPlugin\(\{\s*server:\s*\w+OpenAPI\s*\}\)/);
|
||||||
assert.match(config, /fumadocsMdx\(/);
|
assert.match(config, /fumadocsMdx\(/);
|
||||||
@@ -179,7 +182,7 @@ test("prebuild is deterministic and preserves local assets and SEO files", async
|
|||||||
assert.equal(first.documents, 302);
|
assert.equal(first.documents, 302);
|
||||||
assert.equal(first.fallbackDefaults, 0);
|
assert.equal(first.fallbackDefaults, 0);
|
||||||
assert.equal(first.localeOnlyDocuments, 14);
|
assert.equal(first.localeOnlyDocuments, 14);
|
||||||
assert.equal(first.redirects, 102);
|
assert.equal(first.redirects, 104);
|
||||||
assert.deepEqual(first.locales, ["en", "zh", "ja"]);
|
assert.deepEqual(first.locales, ["en", "zh", "ja"]);
|
||||||
await readFile(path.join(temp, "content/docs/insight/guide.mdx"), "utf8");
|
await readFile(path.join(temp, "content/docs/insight/guide.mdx"), "utf8");
|
||||||
await readFile(path.join(temp, "content/docs/insight/guide.zh.mdx"), "utf8");
|
await readFile(path.join(temp, "content/docs/insight/guide.zh.mdx"), "utf8");
|
||||||
|
|||||||
@@ -67,24 +67,36 @@ test("representative localized guides and canonical host are emitted", async ()
|
|||||||
|
|
||||||
test("all locales emit the OpenAPI surface with endpoint semantics", async () => {
|
test("all locales emit the OpenAPI surface with endpoint semantics", async () => {
|
||||||
const representatives = {
|
const representatives = {
|
||||||
en: ["Get system information", "GET", "/api/v1/system/info"],
|
en: ["system/get-system-information", "Get system information", "GET", "/api/v1/system/info"],
|
||||||
zh: ["获取系统信息", "GET", "/api/v1/system/info"],
|
zh: ["系统/获取系统信息", "获取系统信息", "GET", "/api/v1/system/info"],
|
||||||
ja: ["获取系统信息", "GET", "/api/v1/system/info"],
|
ja: ["system/获取系统信息", "获取系统信息", "GET", "/api/v1/system/info"],
|
||||||
};
|
};
|
||||||
const sitemap = await readFile(path.join(publicRoot, "sitemap.xml"), "utf8");
|
const sitemap = await readFile(path.join(publicRoot, "sitemap.xml"), "utf8");
|
||||||
|
|
||||||
for (const [locale, semantics] of Object.entries(representatives)) {
|
for (const [locale, [representativePath, ...semantics]] of Object.entries(representatives)) {
|
||||||
const apiRoot = path.join(publicRoot, locale, "api-reference");
|
const apiRoot = path.join(publicRoot, locale, "api-reference");
|
||||||
const routes = await collectHtmlRoutes(apiRoot);
|
const routes = await collectHtmlRoutes(apiRoot);
|
||||||
assert.ok(routes.length >= 50, `${locale} emitted only ${routes.length} API routes`);
|
assert.ok(routes.length >= 50, `${locale} emitted only ${routes.length} API routes`);
|
||||||
|
|
||||||
const representative = path.join(apiRoot, "api/v1/system/info/get/index.html");
|
const encodedRepresentativePath = representativePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
||||||
|
const representative = path.join(apiRoot, encodedRepresentativePath, "index.html");
|
||||||
const html = await readFile(representative, "utf8");
|
const html = await readFile(representative, "utf8");
|
||||||
for (const semantic of semantics) assert.ok(html.includes(semantic), `${locale} page is missing ${semantic}`);
|
for (const semantic of semantics) assert.ok(html.includes(semantic), `${locale} page is missing ${semantic}`);
|
||||||
assert.ok(sitemap.includes(`https://docs.langbot.dev/${locale}/api-reference/api/v1/system/info/get`));
|
assert.ok(sitemap.includes(`https://docs.langbot.dev/${locale}/api-reference/${encodedRepresentativePath}`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("canonical sitemap routes match the legacy Mintlify directory structure", async () => {
|
||||||
|
const legacy = new Set((await readFile(path.join(root, "tests/fixtures/legacy-sitemap-routes.txt"), "utf8"))
|
||||||
|
.trim().split("\n").map((route) => route.replace(/\/$/, "")));
|
||||||
|
// Mintlify accidentally indexed this repository maintenance note; preserve
|
||||||
|
// its URL as a redirect without treating it as public documentation.
|
||||||
|
legacy.delete("/scripts/README-blog-articles");
|
||||||
|
const sitemap = await readFile(path.join(publicRoot, "sitemap.xml"), "utf8");
|
||||||
|
const actual = new Set([...sitemap.matchAll(/<loc>https:\/\/docs\.langbot\.dev(\/[^<]+)<\/loc>/g)]
|
||||||
|
.map((match) => decodeURIComponent(match[1]).replace(/\/$/, "")));
|
||||||
|
assert.deepEqual([...actual].sort(), [...legacy].sort());
|
||||||
|
});
|
||||||
|
|
||||||
const locales = ["en", "zh", "ja"];
|
const locales = ["en", "zh", "ja"];
|
||||||
const hreflangByLocale = { en: "en", zh: "zh-CN", ja: "ja" };
|
const hreflangByLocale = { en: "en", zh: "zh-CN", ja: "ja" };
|
||||||
@@ -120,11 +132,11 @@ function extractHeadLinks(html, rel) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
test("rendered navbar uses each locale's docs.json labels and hrefs", async () => {
|
test("rendered navbar and documentation tree use each locale's docs.json labels", async () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
en: ["Home", "https://langbot.app/en", "Roadmap", "https://langbot.app/en/roadmap"],
|
en: ["Home", "https://langbot.app/en", "Roadmap", "https://langbot.app/en/roadmap", "Guides", "Quick Start", "Installation", "Developers", "Articles", "API Reference"],
|
||||||
zh: ["首页", "https://langbot.app/zh", "路线图", "https://langbot.app/zh/roadmap"],
|
zh: ["首页", "https://langbot.app/zh", "路线图", "https://langbot.app/zh/roadmap", "指南", "快速开始", "安装部署", "开发者", "文章", "API 参考"],
|
||||||
ja: ["ホーム", "https://langbot.app/ja", "ロードマップ", "https://langbot.app/ja/roadmap"],
|
ja: ["ホーム", "https://langbot.app/ja", "ロードマップ", "https://langbot.app/ja/roadmap", "ガイド", "クイックスタート", "インストール", "開発者", "記事", "API リファレンス"],
|
||||||
};
|
};
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
const html = await readFile(path.join(publicRoot, locale, "insight/guide/index.html"), "utf8");
|
const html = await readFile(path.join(publicRoot, locale, "insight/guide/index.html"), "utf8");
|
||||||
@@ -179,18 +191,27 @@ test("every page emits a canonical and reciprocal available hreflangs", async ()
|
|||||||
|
|
||||||
const [, locale, ...suffixParts] = route.split("/");
|
const [, locale, ...suffixParts] = route.split("/");
|
||||||
const suffix = suffixParts.join("/");
|
const suffix = suffixParts.join("/");
|
||||||
const expected = {};
|
|
||||||
for (const candidate of locales) {
|
|
||||||
const candidateRoute = `/${candidate}/${suffix}`.replace(/\/$/, "");
|
|
||||||
if (routes.has(candidateRoute)) expected[hreflangByLocale[candidate]] = `https://docs.langbot.dev${candidateRoute}`;
|
|
||||||
}
|
|
||||||
expected["x-default"] = expected.en ?? expected["zh-CN"] ?? expected.ja;
|
|
||||||
const actual = Object.fromEntries(
|
const actual = Object.fromEntries(
|
||||||
extractHeadLinks(html, "alternate")
|
extractHeadLinks(html, "alternate")
|
||||||
.filter((item) => item.hreflang)
|
.filter((item) => item.hreflang)
|
||||||
.map((item) => [item.hreflang, item.href]),
|
.map((item) => [item.hreflang, item.href]),
|
||||||
);
|
);
|
||||||
assert.deepEqual(actual, expected, route);
|
if (suffix.startsWith("api-reference/")) {
|
||||||
|
assert.deepEqual(Object.keys(actual).sort(), ["en", "ja", "x-default", "zh-CN"]);
|
||||||
|
assert.equal(actual["x-default"], actual.en);
|
||||||
|
for (const href of Object.values(actual)) {
|
||||||
|
const target = new URL(href).pathname.replace(/\/$/, "");
|
||||||
|
assert.ok(routes.has(target), `${route} links to missing alternate ${target}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const expected = {};
|
||||||
|
for (const candidate of locales) {
|
||||||
|
const candidateRoute = `/${candidate}/${suffix}`.replace(/\/$/, "");
|
||||||
|
if (routes.has(candidateRoute)) expected[hreflangByLocale[candidate]] = `https://docs.langbot.dev${candidateRoute}`;
|
||||||
|
}
|
||||||
|
expected["x-default"] = expected.en ?? expected["zh-CN"] ?? expected.ja;
|
||||||
|
assert.deepEqual(actual, expected, route);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user