Skip to content

Commit

Permalink
✨ feat: support white list for discover assistant (#5216)
Browse files Browse the repository at this point in the history
* ✨ feat: support white list for discover assistant

* improve error status
  • Loading branch information
arvinxx authored Dec 28, 2024
1 parent 5b0bd68 commit 90bb20d
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 30 deletions.
13 changes: 2 additions & 11 deletions src/app/(backend)/webapi/assistant/store/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { NextResponse } from 'next/server';

import { DEFAULT_LANG } from '@/const/locale';
import { AssistantStore } from '@/server/modules/AssistantStore';

export const runtime = 'edge';
Expand All @@ -11,18 +10,10 @@ export const GET = async (req: Request) => {

const market = new AssistantStore();

let res: Response;
const data = await market.getAgentIndex(locale as any);

res = await fetch(market.getAgentIndexUrl(locale as any));

if (res.status === 404) {
res = await fetch(market.getAgentIndexUrl(DEFAULT_LANG));
}

const data = await res.json();
return NextResponse.json(data);
} catch (e) {
console.error(e);
} catch {
return new Response(`failed to fetch agent market index`, {
headers: {
'Access-Control-Allow-Origin': '*',
Expand Down
4 changes: 4 additions & 0 deletions src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const getAppConfig = () => {
PLUGIN_SETTINGS: z.string().optional(),

APP_URL: z.string().optional(),
VERCEL_EDGE_CONFIG: z.string().optional(),

CDN_USE_GLOBAL: z.boolean().optional(),
CUSTOM_FONT_FAMILY: z.string().optional(),
CUSTOM_FONT_URL: z.string().optional(),
Expand Down Expand Up @@ -75,6 +77,8 @@ export const getAppConfig = () => {

PLUGIN_SETTINGS: process.env.PLUGIN_SETTINGS,

VERCEL_EDGE_CONFIG: process.env.VERCEL_EDGE_CONFIG,

APP_URL,
CUSTOM_FONT_FAMILY: process.env.CUSTOM_FONT_FAMILY,
CUSTOM_FONT_URL: process.env.CUSTOM_FONT_URL,
Expand Down
10 changes: 5 additions & 5 deletions src/server/modules/AssistantStore/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,35 @@ const baseURL = 'https://registry.npmmirror.com/@lobehub/agents-index/v1/files/p
describe('AssistantStore', () => {
it('should return the default index URL when no language is provided', () => {
const agentMarket = new AssistantStore();
const url = agentMarket.getAgentIndexUrl();
const url = agentMarket['getAgentIndexUrl']();
expect(url).toBe(`${baseURL}/index.en-US.json`);
});

it('should return the index URL for a not supported language', () => {
const agentMarket = new AssistantStore();
const url = agentMarket.getAgentIndexUrl('xxx' as any);
const url = agentMarket['getAgentIndexUrl']('xxx' as any);
expect(url).toBe('https://registry.npmmirror.com/@lobehub/agents-index/v1/files/public');
});

it('should return the zh-CN URL for zh locale', () => {
const agentMarket = new AssistantStore();
const url = agentMarket.getAgentIndexUrl('zh' as any);
const url = agentMarket['getAgentIndexUrl']('zh' as any);
expect(url).toBe(
'https://registry.npmmirror.com/@lobehub/agents-index/v1/files/public/index.zh-CN.json',
);
});

it('should return the default URL for en locale', () => {
const agentMarket = new AssistantStore();
const url = agentMarket.getAgentIndexUrl('en' as any);
const url = agentMarket['getAgentIndexUrl']('en' as any);
expect(url).toBe(
'https://registry.npmmirror.com/@lobehub/agents-index/v1/files/public/index.en-US.json',
);
});

it('should return the base URL if the provided language is not supported', () => {
const agentMarket = new AssistantStore();
const url = agentMarket.getAgentIndexUrl('fr' as any);
const url = agentMarket['getAgentIndexUrl']('fr' as any);
expect(url).toBe(baseURL);
});

Expand Down
40 changes: 39 additions & 1 deletion src/server/modules/AssistantStore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import urlJoin from 'url-join';
import { appEnv } from '@/config/app';
import { DEFAULT_LANG, isLocaleNotSupport } from '@/const/locale';
import { Locales, normalizeLocale } from '@/locales/resources';
import { EdgeConfig } from '@/server/modules/EdgeConfig';
import { AgentStoreIndex } from '@/types/discover';

export class AssistantStore {
private readonly baseUrl: string;
Expand All @@ -11,7 +13,7 @@ export class AssistantStore {
this.baseUrl = baseUrl || appEnv.AGENTS_INDEX_URL;
}

getAgentIndexUrl = (lang: Locales = DEFAULT_LANG) => {
private getAgentIndexUrl = (lang: Locales = DEFAULT_LANG) => {
if (isLocaleNotSupport(lang)) return this.baseUrl;

return urlJoin(this.baseUrl, `index.${normalizeLocale(lang)}.json`);
Expand All @@ -22,4 +24,40 @@ export class AssistantStore {

return urlJoin(this.baseUrl, `${identifier}.${normalizeLocale(lang)}.json`);
};

getAgentIndex = async (locale: Locales = DEFAULT_LANG, revalidate?: number) => {
try {
let res: Response;

res = await fetch(this.getAgentIndexUrl(locale as any), { next: { revalidate } });

if (res.status === 404) {
res = await fetch(this.getAgentIndexUrl(DEFAULT_LANG), { next: { revalidate } });
}

if (!res.ok) {
console.error('fetch agent index error:', await res.text());
return [];
}

const data: AgentStoreIndex = await res.json();

// Get the assistant whitelist from Edge Config
const edgeConfig = new EdgeConfig();

if (!!appEnv.VERCEL_EDGE_CONFIG) {
const assistantWhitelist = await edgeConfig.getAgentWhitelist();

if (assistantWhitelist && assistantWhitelist?.length > 0) {
data.agents = data.agents.filter((item) => assistantWhitelist.includes(item.identifier));
}
}

return data;
} catch (e) {
console.error('fetch agent index error:', e);

throw e;
}
};
}
23 changes: 23 additions & 0 deletions src/server/modules/EdgeConfig/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { EdgeConfigClient, createClient } from '@vercel/edge-config';

import { appEnv } from '@/config/app';

enum EdgeConfigKeys {
/**
* Assistant whitelist
*/
AssistantWhitelist = 'assistant_whitelist',
}

export class EdgeConfig {
get client(): EdgeConfigClient {
if (!appEnv.VERCEL_EDGE_CONFIG) {
throw new Error('VERCEL_EDGE_CONFIG is not set');
}
return createClient(appEnv.VERCEL_EDGE_CONFIG);
}

getAgentWhitelist = async (): Promise<string[] | undefined> => {
return this.client.get<string[]>(EdgeConfigKeys.AssistantWhitelist);
};
}
15 changes: 2 additions & 13 deletions src/server/services/discover/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,9 @@ export class DiscoverService {
};

getAssistantList = async (locale: Locales): Promise<DiscoverAssistantItem[]> => {
let res = await fetch(this.assistantStore.getAgentIndexUrl(locale), {
next: { revalidate },
});

if (!res.ok) {
res = await fetch(this.assistantStore.getAgentIndexUrl(DEFAULT_LANG), {
next: { revalidate },
});
}

if (!res.ok) return [];

const json = await res.json();
const json = await this.assistantStore.getAgentIndex(locale, revalidate);

// @ts-expect-error 目前类型不一致,未来要统一
return json.agents;
};

Expand Down
20 changes: 20 additions & 0 deletions src/types/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,23 @@ export interface FilterBy {
token?: number;
vision?: boolean;
}

interface AgentIndexItem {
author: string;
createAt: string;
createdAt: string;
homepage: string;
identifier: string;
meta: {
avatar: string;
category: string;
description: string;
tags: string[];
title: string;
};
}

export interface AgentStoreIndex {
agents: AgentIndexItem[];
schemaVersion: number;
}

0 comments on commit 90bb20d

Please sign in to comment.