Merge branch 'main' into feat/explore

pull/198/head
金伟强 3 years ago
commit 1d1acacb81

@ -33,3 +33,4 @@
flask run --host 0.0.0.0 --port=5001 --debug flask run --host 0.0.0.0 --port=5001 --debug
``` ```
7. Setup your application by visiting http://localhost:5001/console/api/setup or other apis... 7. Setup your application by visiting http://localhost:5001/console/api/setup or other apis...
8. If you need to debug local async processing, you can run `celery -A app.celery worker`, celery can do dataset importing and other async tasks.

@ -125,13 +125,17 @@ class Completion:
pre_prompt = PromptBuilder.process_template(pre_prompt) if pre_prompt else pre_prompt pre_prompt = PromptBuilder.process_template(pre_prompt) if pre_prompt else pre_prompt
if mode == 'completion': if mode == 'completion':
prompt_template = OutLinePromptTemplate.from_template( prompt_template = OutLinePromptTemplate.from_template(
template=("Use the following pieces of [CONTEXT] to answer the question at the end. " template=("""Use the following CONTEXT as your learned knowledge:
"If you don't know the answer, " [CONTEXT]
"just say that you don't know, don't try to make up an answer. \n" {context}
"```\n" [END CONTEXT]
"[CONTEXT]\n"
"{context}\n" When answer to user:
"```\n" if chain_output else "") - If you don't know, just say that you don't know.
- If you don't know when you are not sure, ask for clarification.
Avoid mentioning that you obtained the information from the context.
And answer according to the language of the user's question.
""" if chain_output else "")
+ (pre_prompt + "\n" if pre_prompt else "") + (pre_prompt + "\n" if pre_prompt else "")
+ "{query}\n" + "{query}\n"
) )
@ -153,38 +157,38 @@ class Completion:
else: else:
messages: List[BaseMessage] = [] messages: List[BaseMessage] = []
system_message = None human_inputs = {
if pre_prompt: "query": query
# append pre prompt as system message }
system_message = PromptBuilder.to_system_message(pre_prompt, inputs)
human_message_prompt = "{query}"
if chain_output: if chain_output:
# append context as system message, currently only use simple stuff prompt human_inputs['context'] = chain_output
context_message = PromptBuilder.to_system_message( human_message_instruction = """Use the following CONTEXT as your learned knowledge.
"""Use the following pieces of [CONTEXT] to answer the users question.
If you don't know the answer, just say that you don't know, don't try to make up an answer.
```
[CONTEXT] [CONTEXT]
{context} {context}
```""", [END CONTEXT]
{'context': chain_output}
) When answer to user:
- If you don't know, just say that you don't know.
if not system_message: - If you don't know when you are not sure, ask for clarification.
system_message = context_message Avoid mentioning that you obtained the information from the context.
else: And answer according to the language of the user's question.
system_message.content = context_message.content + "\n\n" + system_message.content """
if pre_prompt:
if system_message: human_inputs.update(inputs)
messages.append(system_message) human_message_instruction += pre_prompt + "\n"
human_inputs = { human_message_prompt = human_message_instruction + "Q:{query}\nA:"
"query": query else:
} if pre_prompt:
human_inputs.update(inputs)
human_message_prompt = pre_prompt + "\n" + human_message_prompt
# construct main prompt # construct main prompt
human_message = PromptBuilder.to_human_message( human_message = PromptBuilder.to_human_message(
prompt_content="{query}", prompt_content=human_message_prompt,
inputs=human_inputs inputs=human_inputs
) )

@ -281,6 +281,9 @@ class PubHandler:
@classmethod @classmethod
def generate_channel_name(cls, user: Union[Account | EndUser], task_id: str): def generate_channel_name(cls, user: Union[Account | EndUser], task_id: str):
if not user:
raise ValueError("user is required")
user_str = 'account-' + user.id if isinstance(user, Account) else 'end-user-' + user.id user_str = 'account-' + user.id if isinstance(user, Account) else 'end-user-' + user.id
return "generate_result:{}-{}".format(user_str, task_id) return "generate_result:{}-{}".format(user_str, task_id)

@ -29,7 +29,7 @@ class WeaviateVectorStoreClient(BaseVectorStoreClient):
return weaviate.Client( return weaviate.Client(
url=endpoint, url=endpoint,
auth_client_secret=auth_config, auth_client_secret=auth_config,
timeout_config=(5, 15), timeout_config=(5, 60),
startup_period=None startup_period=None
) )

@ -1,5 +1,5 @@
'use client' 'use client'
import type { FC } from 'react' import { FC, useRef } from 'react'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { usePathname, useRouter, useSelectedLayoutSegments } from 'next/navigation' import { usePathname, useRouter, useSelectedLayoutSegments } from 'next/navigation'
import useSWR, { SWRConfig } from 'swr' import useSWR, { SWRConfig } from 'swr'
@ -8,7 +8,7 @@ import { fetchAppList } from '@/service/apps'
import { fetchDatasets } from '@/service/datasets' import { fetchDatasets } from '@/service/datasets'
import { fetchLanggeniusVersion, fetchUserProfile, logout } from '@/service/common' import { fetchLanggeniusVersion, fetchUserProfile, logout } from '@/service/common'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import AppContext from '@/context/app-context' import { AppContextProvider } from '@/context/app-context'
import DatasetsContext from '@/context/datasets-context' import DatasetsContext from '@/context/datasets-context'
import type { LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' import type { LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
@ -23,6 +23,7 @@ const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => {
const pattern = pathname.replace(/.*\/app\//, '') const pattern = pathname.replace(/.*\/app\//, '')
const [idOrMethod] = pattern.split('/') const [idOrMethod] = pattern.split('/')
const isNotDetailPage = idOrMethod === 'list' const isNotDetailPage = idOrMethod === 'list'
const pageContainerRef = useRef<HTMLDivElement>(null)
const appId = isNotDetailPage ? '' : idOrMethod const appId = isNotDetailPage ? '' : idOrMethod
@ -71,14 +72,14 @@ const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => {
<SWRConfig value={{ <SWRConfig value={{
shouldRetryOnError: false shouldRetryOnError: false
}}> }}>
<AppContext.Provider value={{ apps: appList.data, mutateApps, userProfile, mutateUserProfile }}> <AppContextProvider value={{ apps: appList.data, mutateApps, userProfile, mutateUserProfile, pageContainerRef }}>
<DatasetsContext.Provider value={{ datasets: datasetList?.data || [], mutateDatasets, currentDataset }}> <DatasetsContext.Provider value={{ datasets: datasetList?.data || [], mutateDatasets, currentDataset }}>
<div className='relative flex flex-col h-full overflow-scroll bg-gray-100'> <div ref={pageContainerRef} className='relative flex flex-col h-full overflow-auto bg-gray-100'>
<Header isBordered={['/apps', '/datasets'].includes(pathname)} curApp={curApp as any} appItems={appList.data} userProfile={userProfile} onLogout={onLogout} langeniusVersionInfo={langeniusVersionInfo} /> <Header isBordered={['/apps', '/datasets'].includes(pathname)} curApp={curApp as any} appItems={appList.data} userProfile={userProfile} onLogout={onLogout} langeniusVersionInfo={langeniusVersionInfo} />
{children} {children}
</div> </div>
</DatasetsContext.Provider> </DatasetsContext.Provider>
</AppContext.Provider> </AppContextProvider>
</SWRConfig> </SWRConfig>
) )
} }

@ -1,23 +1,50 @@
'use client' 'use client'
import { useEffect } from 'react' import { useEffect, useRef } from 'react'
import useSWRInfinite from 'swr/infinite'
import { debounce } from 'lodash-es'
import AppCard from './AppCard' import AppCard from './AppCard'
import NewAppCard from './NewAppCard' import NewAppCard from './NewAppCard'
import { useAppContext } from '@/context/app-context' import { AppListResponse } from '@/models/app'
import { fetchAppList } from '@/service/apps'
import { useSelector } from '@/context/app-context'
const getKey = (pageIndex: number, previousPageData: AppListResponse) => {
if (!pageIndex || previousPageData.has_more)
return { url: 'apps', params: { page: pageIndex + 1, limit: 30 } }
return null
}
const Apps = () => { const Apps = () => {
const { apps, mutateApps } = useAppContext() const { data, isLoading, setSize, mutate } = useSWRInfinite(getKey, fetchAppList, { revalidateFirstPage: false })
const loadingStateRef = useRef(false)
const pageContainerRef = useSelector(state => state.pageContainerRef)
const anchorRef = useRef<HTMLAnchorElement>(null)
useEffect(() => {
loadingStateRef.current = isLoading
}, [isLoading])
useEffect(() => { useEffect(() => {
mutateApps() const onScroll = debounce(() => {
if (!loadingStateRef.current) {
const { scrollTop, clientHeight } = pageContainerRef.current!
const anchorOffset = anchorRef.current!.offsetTop
if (anchorOffset - scrollTop - clientHeight < 100) {
setSize(size => size + 1)
}
}
}, 50)
pageContainerRef.current?.addEventListener('scroll', onScroll)
return () => pageContainerRef.current?.removeEventListener('scroll', onScroll)
}, []) }, [])
return ( return (
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 lg:grid-cols-4 grow shrink-0'> <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 lg:grid-cols-4 grow shrink-0'>
{apps.map(app => (<AppCard key={app.id} app={app} />))} {data?.map(({ data: apps }) => apps.map(app => (<AppCard key={app.id} app={app} />)))}
<NewAppCard /> <NewAppCard ref={anchorRef} onSuccess={mutate} />
</nav> </nav>
) )
} }

@ -1,16 +1,20 @@
'use client' 'use client'
import { useState } from 'react' import { forwardRef, useState } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import style from '../list.module.css' import style from '../list.module.css'
import NewAppDialog from './NewAppDialog' import NewAppDialog from './NewAppDialog'
const CreateAppCard = () => { export type CreateAppCardProps = {
onSuccess?: () => void
}
const CreateAppCard = forwardRef<HTMLAnchorElement, CreateAppCardProps>(({ onSuccess }, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
const [showNewAppDialog, setShowNewAppDialog] = useState(false) const [showNewAppDialog, setShowNewAppDialog] = useState(false)
return ( return (
<a className={classNames(style.listItem, style.newItemCard)} onClick={() => setShowNewAppDialog(true)}> <a ref={ref} className={classNames(style.listItem, style.newItemCard)} onClick={() => setShowNewAppDialog(true)}>
<div className={style.listItemTitle}> <div className={style.listItemTitle}>
<span className={style.newItemIcon}> <span className={style.newItemIcon}>
<span className={classNames(style.newItemIconImage, style.newItemIconAdd)} /> <span className={classNames(style.newItemIconImage, style.newItemIconAdd)} />
@ -20,9 +24,9 @@ const CreateAppCard = () => {
</div> </div>
</div> </div>
{/* <div className='text-xs text-gray-500'>{t('app.createFromConfigFile')}</div> */} {/* <div className='text-xs text-gray-500'>{t('app.createFromConfigFile')}</div> */}
<NewAppDialog show={showNewAppDialog} onClose={() => setShowNewAppDialog(false)} /> <NewAppDialog show={showNewAppDialog} onSuccess={onSuccess} onClose={() => setShowNewAppDialog(false)} />
</a> </a>
) )
} })
export default CreateAppCard export default CreateAppCard

@ -21,10 +21,11 @@ import EmojiPicker from '@/app/components/base/emoji-picker'
type NewAppDialogProps = { type NewAppDialogProps = {
show: boolean show: boolean
onSuccess?: () => void
onClose?: () => void onClose?: () => void
} }
const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => { const NewAppDialog = ({ show, onSuccess, onClose }: NewAppDialogProps) => {
const router = useRouter() const router = useRouter()
const { notify } = useContext(ToastContext) const { notify } = useContext(ToastContext)
const { t } = useTranslation() const { t } = useTranslation()
@ -79,6 +80,8 @@ const NewAppDialog = ({ show, onClose }: NewAppDialogProps) => {
mode: isWithTemplate ? templates.data[selectedTemplateIndex].mode : newAppMode!, mode: isWithTemplate ? templates.data[selectedTemplateIndex].mode : newAppMode!,
config: isWithTemplate ? templates.data[selectedTemplateIndex].model_config : undefined, config: isWithTemplate ? templates.data[selectedTemplateIndex].model_config : undefined,
}) })
if (onSuccess)
onSuccess()
if (onClose) if (onClose)
onClose() onClose()
notify({ type: 'success', message: t('app.newApp.appCreated') }) notify({ type: 'success', message: t('app.newApp.appCreated') })

@ -1,24 +1,49 @@
'use client' 'use client'
import { useEffect } from 'react' import { useEffect, useRef } from 'react'
import useSWR from 'swr' import useSWRInfinite from 'swr/infinite'
import { DataSet } from '@/models/datasets'; import { debounce } from 'lodash-es';
import { DataSetListResponse } from '@/models/datasets';
import NewDatasetCard from './NewDatasetCard' import NewDatasetCard from './NewDatasetCard'
import DatasetCard from './DatasetCard'; import DatasetCard from './DatasetCard';
import { fetchDatasets } from '@/service/datasets'; import { fetchDatasets } from '@/service/datasets';
import { useSelector } from '@/context/app-context';
const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => {
if (!pageIndex || previousPageData.has_more)
return { url: 'datasets', params: { page: pageIndex + 1, limit: 30 } }
return null
}
const Datasets = () => { const Datasets = () => {
// const { datasets, mutateDatasets } = useAppContext() const { data, isLoading, setSize, mutate } = useSWRInfinite(getKey, fetchDatasets, { revalidateFirstPage: false })
const { data: datasetList, mutate: mutateDatasets } = useSWR({ url: '/datasets', params: { page: 1 } }, fetchDatasets) const loadingStateRef = useRef(false)
const pageContainerRef = useSelector(state => state.pageContainerRef)
const anchorRef = useRef<HTMLAnchorElement>(null)
useEffect(() => { useEffect(() => {
mutateDatasets() loadingStateRef.current = isLoading
}, [isLoading])
useEffect(() => {
const onScroll = debounce(() => {
if (!loadingStateRef.current) {
const { scrollTop, clientHeight } = pageContainerRef.current!
const anchorOffset = anchorRef.current!.offsetTop
if (anchorOffset - scrollTop - clientHeight < 100) {
setSize(size => size + 1)
}
}
}, 50)
pageContainerRef.current?.addEventListener('scroll', onScroll)
return () => pageContainerRef.current?.removeEventListener('scroll', onScroll)
}, []) }, [])
return ( return (
<nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 lg:grid-cols-4 grow shrink-0'> <nav className='grid content-start grid-cols-1 gap-4 px-12 pt-8 sm:grid-cols-2 lg:grid-cols-4 grow shrink-0'>
{datasetList?.data.map(dataset => (<DatasetCard key={dataset.id} dataset={dataset} />))} {data?.map(({ data: datasets }) => datasets.map(dataset => (<DatasetCard key={dataset.id} dataset={dataset} />)))}
<NewDatasetCard /> <NewDatasetCard ref={anchorRef} />
</nav> </nav>
) )
} }

@ -1,16 +1,16 @@
'use client' 'use client'
import { useState } from 'react' import { forwardRef, useState } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import style from '../list.module.css' import style from '../list.module.css'
const CreateAppCard = () => { const CreateAppCard = forwardRef<HTMLAnchorElement>((_, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
const [showNewAppDialog, setShowNewAppDialog] = useState(false) const [showNewAppDialog, setShowNewAppDialog] = useState(false)
return ( return (
<a className={classNames(style.listItem, style.newItemCard)} href='/datasets/create'> <a ref={ref} className={classNames(style.listItem, style.newItemCard)} href='/datasets/create'>
<div className={style.listItemTitle}> <div className={style.listItemTitle}>
<span className={style.newItemIcon}> <span className={style.newItemIcon}>
<span className={classNames(style.newItemIconImage, style.newItemIconAdd)} /> <span className={classNames(style.newItemIconImage, style.newItemIconAdd)} />
@ -23,6 +23,6 @@ const CreateAppCard = () => {
{/* <div className='text-xs text-gray-500'>{t('app.createFromConfigFile')}</div> */} {/* <div className='text-xs text-gray-500'>{t('app.createFromConfigFile')}</div> */}
</a> </a>
) )
} })
export default CreateAppCard export default CreateAppCard

@ -291,74 +291,76 @@ const Answer: FC<IAnswerProps> = ({ item, feedbackDisabled = false, isHideFeedba
</div> </div>
} }
</div> </div>
<div className={`${s.answerWrap} ${showEdit ? 'w-full' : ''}`}> <div className={s.answerWrapWrap}>
<div className={`${s.answer} relative text-sm text-gray-900`}> <div className={`${s.answerWrap} ${showEdit ? 'w-full' : ''}`}>
<div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}> <div className={`${s.answer} relative text-sm text-gray-900`}>
{item.isOpeningStatement && ( <div className={'ml-2 py-3 px-4 bg-gray-100 rounded-tr-2xl rounded-b-2xl'}>
<div className='flex items-center mb-1 gap-1'> {item.isOpeningStatement && (
<OpeningStatementIcon /> <div className='flex items-center mb-1 gap-1'>
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.title')}</div> <OpeningStatementIcon />
</div> <div className='text-xs text-gray-500'>{t('appDebug.openingStatement.title')}</div>
)}
{(isResponsing && !content) ? (
<div className='flex items-center justify-center w-6 h-5'>
<LoadingAnim type='text' />
</div>
) : (
<Markdown content={content} />
)}
{!showEdit
? (annotation?.content
&& <>
<Divider name={annotation?.account?.name || userProfile?.name} />
{annotation.content}
</>)
: <>
<Divider name={annotation?.account?.name || userProfile?.name} />
<AutoHeightTextarea
placeholder={t('appLog.detail.operation.annotationPlaceholder') as string}
value={inputValue}
onChange={e => setInputValue(e.target.value)}
minHeight={58}
className={`${cn(s.textArea)} !py-2 resize-none block w-full !px-3 bg-gray-50 border border-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm text-gray-700 tracking-[0.2px]`}
/>
<div className="mt-2 flex flex-row">
<Button
type='primary'
className='mr-2'
loading={loading}
onClick={async () => {
if (!inputValue)
return
setLoading(true)
const res = await onSubmitAnnotation?.(id, inputValue)
if (res)
setAnnotation({ ...annotation, content: inputValue } as any)
setLoading(false)
setShowEdit(false)
}}>{t('common.operation.confirm')}</Button>
<Button
onClick={() => {
setInputValue(annotation?.content ?? '')
setShowEdit(false)
}}>{t('common.operation.cancel')}</Button>
</div> </div>
</> )}
} {(isResponsing && !content) ? (
</div> <div className='flex items-center justify-center w-6 h-5'>
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'> <LoadingAnim type='text' />
<CopyBtn </div>
value={content} ) : (
className={cn(s.copyBtn, 'mr-1')} <Markdown content={content} />
/> )}
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation(displayScene !== 'console')} {!showEdit
{/* Admin feedback is displayed only in the background. */} ? (annotation?.content
{!feedbackDisabled && renderFeedbackRating(localAdminFeedback?.rating, false, false)} && <>
{/* User feedback must be displayed */} <Divider name={annotation?.account?.name || userProfile?.name} />
{!feedbackDisabled && renderFeedbackRating(feedback?.rating, !isHideFeedbackEdit, displayScene !== 'console')} {annotation.content}
</>)
: <>
<Divider name={annotation?.account?.name || userProfile?.name} />
<AutoHeightTextarea
placeholder={t('appLog.detail.operation.annotationPlaceholder') as string}
value={inputValue}
onChange={e => setInputValue(e.target.value)}
minHeight={58}
className={`${cn(s.textArea)} !py-2 resize-none block w-full !px-3 bg-gray-50 border border-gray-200 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm text-gray-700 tracking-[0.2px]`}
/>
<div className="mt-2 flex flex-row">
<Button
type='primary'
className='mr-2'
loading={loading}
onClick={async () => {
if (!inputValue)
return
setLoading(true)
const res = await onSubmitAnnotation?.(id, inputValue)
if (res)
setAnnotation({ ...annotation, content: inputValue } as any)
setLoading(false)
setShowEdit(false)
}}>{t('common.operation.confirm')}</Button>
<Button
onClick={() => {
setInputValue(annotation?.content ?? '')
setShowEdit(false)
}}>{t('common.operation.cancel')}</Button>
</div>
</>
}
</div>
<div className='absolute top-[-14px] right-[-14px] flex flex-row justify-end gap-1'>
<CopyBtn
value={content}
className={cn(s.copyBtn, 'mr-1')}
/>
{!feedbackDisabled && !item.feedbackDisabled && renderItemOperation(displayScene !== 'console')}
{/* Admin feedback is displayed only in the background. */}
{!feedbackDisabled && renderFeedbackRating(localAdminFeedback?.rating, false, false)}
{/* User feedback must be displayed */}
{!feedbackDisabled && renderFeedbackRating(feedback?.rating, !isHideFeedbackEdit, displayScene !== 'console')}
</div>
</div> </div>
{more && <MoreInfo more={more} isQuestion={false} />}
</div> </div>
{more && <MoreInfo more={more} isQuestion={false} />}
</div> </div>
</div> </div>
</div> </div>
@ -372,7 +374,7 @@ const Question: FC<IQuestionProps> = ({ id, content, more, useCurrentUserAvatar
const userName = userProfile?.name const userName = userProfile?.name
return ( return (
<div className='flex items-start justify-end' key={id}> <div className='flex items-start justify-end' key={id}>
<div> <div className={s.questionWrapWrap}>
<div className={`${s.question} relative text-sm text-gray-900`}> <div className={`${s.question} relative text-sm text-gray-900`}>
<div <div
className={'mr-2 py-3 px-4 bg-blue-500 rounded-tl-2xl rounded-b-2xl'} className={'mr-2 py-3 px-4 bg-blue-500 rounded-tl-2xl rounded-b-2xl'}

@ -42,6 +42,23 @@
display: none; display: none;
} }
.answerWrapWrap,
.questionWrapWrap {
width: 0;
flex-grow: 1;
}
.questionWrapWrap {
display: flex;
justify-content: flex-end;
}
.answerWrap,
.question {
display: inline-block;
max-width: 100%;
}
.answerWrap:hover .copyBtn { .answerWrap:hover .copyBtn {
display: block; display: block;
} }

@ -33,7 +33,7 @@ const CustomDialog = ({
const close = useCallback(() => onClose?.(), [onClose]) const close = useCallback(() => onClose?.(), [onClose])
return ( return (
<Transition appear show={show} as={Fragment}> <Transition appear show={show} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={close}> <Dialog as="div" className="relative z-40" onClose={close}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"

@ -80,6 +80,7 @@ const EmojiPicker: FC<IEmojiPickerProps> = ({
onClose={() => { }} onClose={() => { }}
isShow isShow
closable={false} closable={false}
wrapperClassName='!z-40'
className={cn(s.container, '!w-[362px] !p-0')} className={cn(s.container, '!w-[362px] !p-0')}
> >
<div className='flex flex-col items-center w-full p-3'> <div className='flex flex-col items-center w-full p-3'>

@ -26,7 +26,7 @@ export default function Modal({
}: IModal) { }: IModal) {
return ( return (
<Transition appear show={isShow} as={Fragment}> <Transition appear show={isShow} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onClose}> <Dialog as="div" className={`relative z-10 ${wrapperClassName}`} onClose={onClose}>
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"

@ -43,4 +43,7 @@
background: #f9fafb center no-repeat url(../assets/Loading.svg); background: #f9fafb center no-repeat url(../assets/Loading.svg);
background-size: contain; background-size: contain;
} }
.fileContent {
white-space: pre-line;
}

@ -190,13 +190,15 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
onChange={fileChangeHandle} onChange={fileChangeHandle}
/> />
<div className={s.title}>{t('datasetCreation.stepOne.uploader.title')}</div> <div className={s.title}>{t('datasetCreation.stepOne.uploader.title')}</div>
{!currentFile && !file && ( <div ref={dropRef}>
<div ref={dropRef} className={cn(s.uploader, dragging && s.dragging)}> {!currentFile && !file && (
<span>{t('datasetCreation.stepOne.uploader.button')}</span> <div className={cn(s.uploader, dragging && s.dragging)}>
<label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label> <span>{t('datasetCreation.stepOne.uploader.button')}</span>
{dragging && <div ref={dragRef} className={s.draggingCover}/>} <label className={s.browse} onClick={selectHandle}>{t('datasetCreation.stepOne.uploader.browse')}</label>
</div> {dragging && <div ref={dragRef} className={s.draggingCover}/>}
)} </div>
)}
</div>
{currentFile && ( {currentFile && (
<div className={cn(s.file, uploading && s.uploading)}> <div className={cn(s.file, uploading && s.uploading)}>
{uploading && ( {uploading && (

@ -41,7 +41,7 @@ const PreviewItem: FC<IPreviewItemProps> = ({
</div> </div>
</div> </div>
<div className='mt-2 max-h-[120px] line-clamp-6 overflow-hidden text-sm text-gray-800'> <div className='mt-2 max-h-[120px] line-clamp-6 overflow-hidden text-sm text-gray-800'>
{content} <div style={{ whiteSpace: 'pre-line'}}>{content}</div>
</div> </div>
</div> </div>
) )

@ -45,6 +45,7 @@
} }
.segModalContent { .segModalContent {
@apply h-96 text-gray-800 text-base overflow-y-scroll; @apply h-96 text-gray-800 text-base overflow-y-scroll;
white-space: pre-line;
} }
.footer { .footer {
@apply flex items-center justify-between box-border border-t-gray-200 border-t-[0.5px] pt-3 mt-4; @apply flex items-center justify-between box-border border-t-gray-200 border-t-[0.5px] pt-3 mt-4;

@ -54,6 +54,7 @@
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all;
user-select: text; user-select: text;
} }
@ -593,6 +594,7 @@
.markdown-body table th { .markdown-body table th {
font-weight: var(--base-text-weight-semibold, 600); font-weight: var(--base-text-weight-semibold, 600);
white-space: nowrap;
} }
.markdown-body table th, .markdown-body table th,

@ -1,27 +0,0 @@
'use client'
import { createContext, useContext } from 'use-context-selector'
import type { App } from '@/types/app'
import type { UserProfileResponse } from '@/models/common'
export type AppContextValue = {
apps: App[]
mutateApps: () => void
userProfile: UserProfileResponse
mutateUserProfile: () => void
}
const AppContext = createContext<AppContextValue>({
apps: [],
mutateApps: () => { },
userProfile: {
id: '',
name: '',
email: '',
},
mutateUserProfile: () => { },
})
export const useAppContext = () => useContext(AppContext)
export default AppContext

@ -0,0 +1,45 @@
'use client'
import { createContext, useContext, useContextSelector } from 'use-context-selector'
import type { App } from '@/types/app'
import type { UserProfileResponse } from '@/models/common'
import { createRef, FC, PropsWithChildren } from 'react'
export const useSelector = <T extends any>(selector: (value: AppContextValue) => T): T =>
useContextSelector(AppContext, selector);
export type AppContextValue = {
apps: App[]
mutateApps: () => void
userProfile: UserProfileResponse
mutateUserProfile: () => void
pageContainerRef: React.RefObject<HTMLDivElement>,
useSelector: typeof useSelector,
}
const AppContext = createContext<AppContextValue>({
apps: [],
mutateApps: () => { },
userProfile: {
id: '',
name: '',
email: '',
},
mutateUserProfile: () => { },
pageContainerRef: createRef(),
useSelector,
})
export type AppContextProviderProps = PropsWithChildren<{
value: Omit<AppContextValue, 'useSelector'>
}>
export const AppContextProvider: FC<AppContextProviderProps> = ({ value, children }) => (
<AppContext.Provider value={{ ...value, useSelector }}>
{children}
</AppContext.Provider>
)
export const useAppContext = () => useContext(AppContext)
export default AppContext

@ -23,7 +23,11 @@ export const getLocale = (request: NextRequest): Locale => {
} }
// match locale // match locale
const matchedLocale = match(languages, locales, i18n.defaultLocale) as Locale let matchedLocale:Locale = i18n.defaultLocale
try {
// If languages is ['*'], Error would happen in match function.
matchedLocale = match(languages, locales, i18n.defaultLocale) as Locale
} catch(e) {}
return matchedLocale return matchedLocale
} }

@ -61,6 +61,10 @@ export type SiteConfig = {
export type AppListResponse = { export type AppListResponse = {
data: App[] data: App[]
has_more: boolean
limit: number
page: number
total: number
} }
export type AppDetailResponse = App export type AppDetailResponse = App

@ -29,6 +29,10 @@ export type File = {
export type DataSetListResponse = { export type DataSetListResponse = {
data: DataSet[] data: DataSet[]
has_more: boolean
limit: number
page: number
total: number
} }
export type IndexingEstimateResponse = { export type IndexingEstimateResponse = {

@ -4,8 +4,8 @@ import type { ApikeysListResponse, AppDailyConversationsResponse, AppDailyEndUse
import type { CommonResponse } from '@/models/common' import type { CommonResponse } from '@/models/common'
import type { AppMode, ModelConfig } from '@/types/app' import type { AppMode, ModelConfig } from '@/types/app'
export const fetchAppList: Fetcher<AppListResponse, { params?: Record<string, any> }> = ({ params }) => { export const fetchAppList: Fetcher<AppListResponse, { url: string; params?: Record<string, any> }> = ({ url, params }) => {
return get('apps', params) as Promise<AppListResponse> return get(url, { params }) as Promise<AppListResponse>
} }
export const fetchAppDetail: Fetcher<AppDetailResponse, { url: string; id: string }> = ({ url, id }) => { export const fetchAppDetail: Fetcher<AppDetailResponse, { url: string; id: string }> = ({ url, id }) => {

Loading…
Cancel
Save