Skip to content

Commit 665c520

Browse files
authored
feat: add app and web search configuration options to settings (#1862)
* feat: add app and web search configuration options to settings * fix: type check * perf: update input fields to use password type for API key entries
1 parent 6ccfdfd commit 665c520

12 files changed

Lines changed: 241 additions & 106 deletions

File tree

apps/nestjs-backend/src/features/setting/open-api/setting-open-api.controller.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ export class SettingOpenApiController {
6161
SettingKey.ENABLE_EMAIL_VERIFICATION,
6262
SettingKey.ENABLE_WAITLIST,
6363
SettingKey.AI_CONFIG,
64+
SettingKey.APP_CONFIG,
65+
SettingKey.WEB_SEARCH_CONFIG,
6466
]);
65-
const { aiConfig, ...rest } = setting;
67+
const { aiConfig, appConfig, webSearchConfig, ...rest } = setting;
6668
return {
6769
...rest,
6870
aiConfig: {
@@ -75,6 +77,8 @@ export class SettingOpenApiController {
7577
})) ?? [],
7678
chatModel: aiConfig?.chatModel,
7779
},
80+
appGenerationEnabled: Boolean(appConfig?.apiKey),
81+
webSearchEnabled: Boolean(webSearchConfig?.apiKey),
7882
};
7983
}
8084

apps/nestjs-backend/src/features/setting/open-api/setting-open-api.service.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,6 @@ export class SettingOpenApiService {
122122
}
123123
}
124124

125-
private testWebSearch() {
126-
return Boolean(process.env.FIRECRAWL_API_KEY);
127-
}
128-
129125
private async testChatModelAbility(
130126
modelInstance: LanguageModel,
131127
ability: ITestLLMRo['ability']
@@ -162,13 +158,6 @@ export class SettingOpenApiService {
162158
}
163159
}
164160

165-
if (testAbilities.includes(chatModelAbilityType.Enum.webSearch)) {
166-
const supportWebSearch = this.testWebSearch();
167-
if (supportWebSearch) {
168-
supportAbilities.push(chatModelAbilityType.Enum.webSearch);
169-
}
170-
}
171-
172161
return supportAbilities?.reduce(
173162
(acc, curr) => {
174163
acc[curr] = true;

apps/nextjs-app/src/features/app/blocks/admin/setting/SettingPage.tsx

Lines changed: 163 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import {
88
updateSetting,
99
} from '@teable/openapi';
1010
import { useIsHydrated } from '@teable/sdk/hooks';
11-
import { Label, Switch } from '@teable/ui-lib/shadcn';
12-
import { useTranslation } from 'next-i18next';
11+
import { Input, Label, Switch } from '@teable/ui-lib/shadcn';
12+
import Link from 'next/link';
13+
import { Trans, useTranslation } from 'next-i18next';
1314
import { useMemo, useRef } from 'react';
1415
import { useEnv } from '@/features/app/hooks/useEnv';
1516
import { useIsCloud } from '@/features/app/hooks/useIsCloud';
@@ -57,7 +58,8 @@ export const SettingPage = (props: ISettingPageProps) => {
5758
};
5859

5960
const llmRef = useRef<HTMLDivElement>(null);
60-
// const v0Ref = useRef<HTMLDivElement>(null);
61+
const appRef = useRef<HTMLDivElement>(null);
62+
const webSearchRef = useRef<HTMLDivElement>(null);
6163
const emailRef = useRef<HTMLDivElement>(null);
6264
const { publicOrigin, publicDatabaseProxy } = useEnv();
6365

@@ -86,11 +88,18 @@ export const SettingPage = (props: ISettingPageProps) => {
8688
anchor: llmRef,
8789
shouldShow: !setting?.aiConfig?.enable || setting?.aiConfig?.llmProviders.length === 0,
8890
},
89-
// {
90-
// title: t('admin.configuration.list.v0.title'),
91-
// key: 'v0' as const,
92-
// anchor: v0Ref,
93-
// },
91+
{
92+
title: t('admin.configuration.list.app.title'),
93+
key: 'app' as const,
94+
anchor: appRef,
95+
shouldShow: !setting?.appConfig?.apiKey,
96+
},
97+
{
98+
title: t('admin.configuration.list.webSearch.title'),
99+
key: 'webSearch' as const,
100+
anchor: webSearchRef,
101+
shouldShow: !setting?.webSearchConfig?.apiKey,
102+
},
94103
{
95104
title: t('admin.configuration.list.email.title'),
96105
key: 'email' as const,
@@ -104,6 +113,8 @@ export const SettingPage = (props: ISettingPageProps) => {
104113
publicOrigin,
105114
setting?.aiConfig?.enable,
106115
setting?.aiConfig?.llmProviders.length,
116+
setting?.appConfig?.apiKey,
117+
setting?.webSearchConfig?.apiKey,
107118
setting?.notifyMailTransportConfig,
108119
t,
109120
]
@@ -113,7 +124,7 @@ export const SettingPage = (props: ISettingPageProps) => {
113124
return todoLists.filter((item) => item.shouldShow);
114125
}, [todoLists]);
115126

116-
if (!setting) return null;
127+
if (!setting || !isHydrated) return null;
117128

118129
const {
119130
instanceId,
@@ -124,17 +135,19 @@ export const SettingPage = (props: ISettingPageProps) => {
124135
enableWaitlist,
125136
brandName,
126137
brandLogo,
138+
appConfig,
139+
webSearchConfig,
127140
} = setting;
128141

129142
return (
130-
<div className="flex h-screen flex-1 flex-col overflow-y-auto overflow-x-hidden p-8">
143+
<div className="flex h-screen flex-1 flex-col overflow-y-auto overflow-x-hidden p-4 sm:p-8">
131144
<div className="pb-6">
132145
<h1 className="text-2xl font-semibold">{t('settings.title')}</h1>
133146
<div className="mt-2 text-sm text-zinc-500">{t('admin.setting.description')}</div>
134147
</div>
135148

136-
<div className="relative flex flex-1 overflow-hidden">
137-
<div className="setting-page-left-container flex-1 overflow-y-auto overflow-x-hidden pr-10">
149+
<div className="relative flex flex-1 flex-col overflow-hidden sm:flex-row">
150+
<div className="setting-page-left-container flex-1 overflow-y-auto overflow-x-hidden sm:pr-10">
138151
{/* General Settings Section */}
139152
<div className="pb-6">
140153
<h2 className="mb-4 text-lg font-medium">{t('admin.setting.generalSettings')}</h2>
@@ -198,44 +211,6 @@ export const SettingPage = (props: ISettingPageProps) => {
198211
</div>
199212
</div>
200213

201-
{isCloud && (
202-
<div className="pb-6">
203-
<h2 className="mb-4 text-lg font-medium">{t('waitlist.title')}</h2>
204-
<div className="flex flex-col gap-4 rounded-lg border p-4 shadow-sm">
205-
<div className="flex items-center justify-between ">
206-
<div className="space-y-1">
207-
<Label htmlFor="enable-waitlist">{t('admin.setting.enableWaitlist')}</Label>
208-
<div className="text-xs text-zinc-500">
209-
{t('admin.setting.enableWaitlistDescription')}
210-
</div>
211-
</div>
212-
<Switch
213-
id="enable-waitlist"
214-
checked={Boolean(enableWaitlist)}
215-
onCheckedChange={(checked) => onValueChange('enableWaitlist', checked)}
216-
/>
217-
</div>
218-
{enableWaitlist && (
219-
<>
220-
<div className="flex items-center justify-between ">
221-
<div className="space-y-1">
222-
<Label htmlFor="enable-waitlist">{t('waitlist.title')}</Label>
223-
</div>
224-
<WaitlistManage />
225-
</div>
226-
227-
<div className="flex items-center justify-between ">
228-
<div className="space-y-1">
229-
<Label htmlFor="enable-waitlist">{t('waitlist.generateCode')}</Label>
230-
</div>
231-
<InviteCodeManage />
232-
</div>
233-
</>
234-
)}
235-
</div>
236-
</div>
237-
)}
238-
239214
{/* AI Configuration Section */}
240215
<div className="pb-6" ref={llmRef}>
241216
<h2 className="mb-4 text-lg font-medium">{t('admin.setting.aiSettings')}</h2>
@@ -245,6 +220,106 @@ export const SettingPage = (props: ISettingPageProps) => {
245220
/>
246221
</div>
247222

223+
{/* App Configuration Section */}
224+
{(isEE || isCloud) && (
225+
<div className="relative flex flex-col gap-2 pb-6" ref={appRef}>
226+
<div className="flex flex-col gap-4 overflow-hidden rounded-lg border p-4">
227+
<div className="relative flex flex-col gap-1">
228+
<div className="text-left text-lg font-semibold text-zinc-900">
229+
{t('app.title')}
230+
</div>
231+
<div className="text-left text-xs text-zinc-500">
232+
<Trans
233+
ns="common"
234+
i18nKey="app.description"
235+
components={{
236+
a: (
237+
<Link
238+
className="cursor-pointer text-blue-500"
239+
href="https://v0.app/chat/settings/keys"
240+
target="_blank"
241+
rel="noreferrer"
242+
/>
243+
),
244+
}}
245+
/>
246+
</div>
247+
</div>
248+
<div className="relative flex flex-col gap-2">
249+
<div className="self-stretch text-left text-sm font-medium text-zinc-900">
250+
{t('admin.setting.ai.apiKey')}
251+
</div>
252+
<div className="flex flex-col gap-2">
253+
<Input
254+
type="password"
255+
value={appConfig?.apiKey}
256+
placeholder={t('admin.action.enterApiKey')}
257+
onChange={(e) => {
258+
const value = e.target.value?.trim();
259+
onValueChange('appConfig', { ...appConfig, apiKey: value });
260+
}}
261+
/>
262+
</div>
263+
</div>
264+
</div>
265+
{!appConfig?.apiKey && (
266+
<div className="h-4 shrink-0 grow-0 text-left text-xs text-red-500">
267+
{t('admin.configuration.list.app.errorTips')}
268+
</div>
269+
)}
270+
</div>
271+
)}
272+
273+
{/* Web Search Configuration Section */}
274+
{(isEE || isCloud) && (
275+
<div className="relative flex flex-col gap-2 pb-6" ref={webSearchRef}>
276+
<div className="flex flex-col gap-4 overflow-hidden rounded-lg border p-4">
277+
<div className="relative flex flex-col gap-1">
278+
<div className="text-left text-lg font-semibold text-zinc-900">
279+
{t('admin.configuration.list.webSearch.title')}
280+
</div>
281+
<div className="text-left text-xs text-zinc-500">
282+
<Trans
283+
ns="common"
284+
i18nKey="admin.setting.webSearch.description"
285+
components={{
286+
a: (
287+
<Link
288+
className="cursor-pointer text-blue-500"
289+
href="https://www.firecrawl.dev/app/api-keys"
290+
target="_blank"
291+
rel="noreferrer"
292+
/>
293+
),
294+
}}
295+
/>
296+
</div>
297+
</div>
298+
<div className="relative flex flex-col gap-2">
299+
<div className="self-stretch text-left text-sm font-medium text-zinc-900">
300+
{t('admin.setting.ai.apiKey')}
301+
</div>
302+
<div className="flex flex-col gap-2">
303+
<Input
304+
type="password"
305+
value={webSearchConfig?.apiKey}
306+
placeholder={t('admin.action.enterApiKey')}
307+
onChange={(e) => {
308+
const value = e.target.value?.trim();
309+
onValueChange('webSearchConfig', { apiKey: value });
310+
}}
311+
/>
312+
</div>
313+
</div>
314+
</div>
315+
{!webSearchConfig?.apiKey && (
316+
<div className="h-4 shrink-0 grow-0 text-left text-xs text-red-500">
317+
{t('admin.configuration.list.webSearch.errorTips')}
318+
</div>
319+
)}
320+
</div>
321+
)}
322+
248323
<div className="pb-6" ref={emailRef}>
249324
<h2 className="mb-4 text-lg font-medium">{t('email.config')}</h2>
250325
<div className="flex w-full flex-col space-y-4">
@@ -286,7 +361,6 @@ export const SettingPage = (props: ISettingPageProps) => {
286361
</div>
287362
)}
288363
</div>
289-
290364
{/* Branding Settings Section */}
291365
{instanceUsage?.level === BillingProductLevel.Enterprise && (
292366
<Branding
@@ -296,6 +370,44 @@ export const SettingPage = (props: ISettingPageProps) => {
296370
/>
297371
)}
298372

373+
{isCloud && (
374+
<div className="pb-6">
375+
<h2 className="mb-4 text-lg font-medium">{t('waitlist.title')}</h2>
376+
<div className="flex flex-col gap-4 rounded-lg border p-4 shadow-sm">
377+
<div className="flex items-center justify-between ">
378+
<div className="space-y-1">
379+
<Label htmlFor="enable-waitlist">{t('admin.setting.enableWaitlist')}</Label>
380+
<div className="text-xs text-zinc-500">
381+
{t('admin.setting.enableWaitlistDescription')}
382+
</div>
383+
</div>
384+
<Switch
385+
id="enable-waitlist"
386+
checked={Boolean(enableWaitlist)}
387+
onCheckedChange={(checked) => onValueChange('enableWaitlist', checked)}
388+
/>
389+
</div>
390+
{enableWaitlist && (
391+
<>
392+
<div className="flex items-center justify-between ">
393+
<div className="space-y-1">
394+
<Label htmlFor="enable-waitlist">{t('waitlist.title')}</Label>
395+
</div>
396+
<WaitlistManage />
397+
</div>
398+
399+
<div className="flex items-center justify-between ">
400+
<div className="space-y-1">
401+
<Label htmlFor="enable-waitlist">{t('waitlist.generateCode')}</Label>
402+
</div>
403+
<InviteCodeManage />
404+
</div>
405+
</>
406+
)}
407+
</div>
408+
</div>
409+
)}
410+
299411
<CopyInstance instanceId={instanceId} />
300412
</div>
301413
{finalList.length > 0 && <ConfigurationList list={finalList} />}

apps/nextjs-app/src/features/app/blocks/admin/setting/components/ConfigurationList.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { RefObject } from 'react';
44

55
interface IList {
66
title: string;
7-
key: 'publicOrigin' | 'https' | 'databaseProxy' | 'llmApi' | 'v0' | 'email';
7+
key: 'publicOrigin' | 'https' | 'databaseProxy' | 'llmApi' | 'app' | 'webSearch' | 'email';
88
anchor?: RefObject<HTMLDivElement>;
99
}
1010

@@ -33,9 +33,10 @@ export const ConfigurationList = (props: IConfigurationListProps) => {
3333
targetElement?.scrollIntoView({ behavior: 'smooth' });
3434
}
3535
};
36+
3637
return (
3738
<div>
38-
<div className="sticky top-0 flex h-auto w-[360px] min-w-[360px] flex-col space-y-4 rounded-lg border bg-secondary p-4">
39+
<div className="sticky top-0 mt-4 flex h-44 w-full min-w-full flex-col space-y-4 overflow-y-auto rounded-lg border bg-secondary p-4 sm:h-auto sm:w-[360px] sm:min-w-[360px] sm:overflow-hidden">
3940
<div className="flex flex-col">
4041
<span className="justify-start self-stretch text-sm font-semibold text-foreground">
4142
{t('admin.configuration.title')}

0 commit comments

Comments
 (0)