88 updateSetting ,
99} from '@teable/openapi' ;
1010import { 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' ;
1314import { useMemo , useRef } from 'react' ;
1415import { useEnv } from '@/features/app/hooks/useEnv' ;
1516import { 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 } /> }
0 commit comments