chore: run page support url query

pull/17608/head
keting lu 1 year ago
parent 9529911932
commit c485765f26

@ -10,7 +10,13 @@ import cn from '@/utils/classnames'
import type { App } from '@/types/app'
import Confirm from '@/app/components/base/confirm'
import Toast, { ToastContext } from '@/app/components/base/toast'
import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps'
import {
copyApp,
deleteApp,
exportAppConfig,
fetchApiKeysList,
updateAppInfo,
} from '@/service/apps'
import DuplicateAppModal from '@/app/components/app/duplicate-modal'
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
import AppIcon from '@/app/components/base/app-icon'
@ -34,8 +40,8 @@ import { AppTypeIcon } from '@/app/components/app/type-selector'
import Button from '@/app/components/base/button'
export type AppCardProps = {
app: App
onRefresh?: () => void
app: App;
onRefresh?: () => void;
}
const AppCard = ({ app, onRefresh }: AppCardProps) => {
@ -60,54 +66,62 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
try {
await deleteApp(app.id)
notify({ type: 'success', message: t('app.appDeleted') })
if (onRefresh)
onRefresh()
if (onRefresh) onRefresh()
mutateApps()
onPlanInfoChanged()
}
catch (e: any) {
notify({
type: 'error',
message: `${t('app.appDeleteFailed')}${'message' in e ? `: ${e.message}` : ''}`,
message: `${t('app.appDeleteFailed')}${
'message' in e ? `: ${e.message}` : ''
}`,
})
}
setShowConfirmDelete(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [app.id])
const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({
const onEdit: CreateAppModalProps['onConfirm'] = useCallback(
async ({
name,
icon_type,
icon,
icon_background,
description,
use_icon_as_answer_icon,
}) => {
try {
await updateAppInfo({
appID: app.id,
name,
icon_type,
icon,
icon_background,
description,
use_icon_as_answer_icon,
})
setShowEditModal(false)
notify({
type: 'success',
message: t('app.editDone'),
})
if (onRefresh) onRefresh()
mutateApps()
}
catch {
notify({ type: 'error', message: t('app.editFailed') })
}
},
[app.id, mutateApps, notify, onRefresh, t],
)
const onCopy: DuplicateAppModalProps['onConfirm'] = async ({
name,
icon_type,
icon,
icon_background,
description,
use_icon_as_answer_icon,
}) => {
try {
await updateAppInfo({
appID: app.id,
name,
icon_type,
icon,
icon_background,
description,
use_icon_as_answer_icon,
})
setShowEditModal(false)
notify({
type: 'success',
message: t('app.editDone'),
})
if (onRefresh)
onRefresh()
mutateApps()
}
catch {
notify({ type: 'error', message: t('app.editFailed') })
}
}, [app.id, mutateApps, notify, onRefresh, t])
const onCopy: DuplicateAppModalProps['onConfirm'] = async ({ name, icon_type, icon, icon_background }) => {
try {
const newApp = await copyApp({
appID: app.id,
@ -123,8 +137,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
message: t('app.newApp.appCreated'),
})
localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
if (onRefresh)
onRefresh()
if (onRefresh) onRefresh()
mutateApps()
onPlanInfoChanged()
getRedirection(isCurrentWorkspaceEditor, newApp, push)
@ -157,8 +170,12 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
return
}
try {
const workflowDraft = await fetchWorkflowDraft(`/apps/${app.id}/workflows/draft`)
const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
const workflowDraft = await fetchWorkflowDraft(
`/apps/${app.id}/workflows/draft`,
)
const list = (workflowDraft.environment_variables || []).filter(
env => env.value_type === 'secret',
)
if (list.length === 0) {
onExport()
return
@ -171,8 +188,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
}
const onSwitch = () => {
if (onRefresh)
onRefresh()
if (onRefresh) onRefresh()
mutateApps()
setShowSwitchModal(false)
}
@ -211,16 +227,18 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
e.preventDefault()
setShowConfirmDelete(true)
}
const onClickInstalledApp = async (e: React.MouseEvent<HTMLButtonElement>) => {
const onClickInstalledApp = async (
e: React.MouseEvent<HTMLButtonElement>,
) => {
e.stopPropagation()
props.onClick?.()
e.preventDefault()
try {
const { installed_apps }: any = await fetchInstalledAppList(app.id) || {}
const { installed_apps }: any
= (await fetchInstalledAppList(app.id)) || {}
if (installed_apps?.length > 0)
window.open(`/explore/installed/${installed_apps[0].id}`, '_blank')
else
throw new Error('No app found in Explore')
else throw new Error('No app found in Explore')
}
catch (e: any) {
Toast.notify({ type: 'error', message: `${e.message || e}` })
@ -242,10 +260,12 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
<>
<Divider className="!my-1" />
<div
className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer'
className="h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer"
onClick={onClickSwitch}
>
<span className='text-gray-700 text-sm leading-5'>{t('app.switch')}</span>
<span className="text-gray-700 text-sm leading-5">
{t('app.switch')}
</span>
</div>
</>
)}
@ -270,7 +290,15 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
useEffect(() => {
setTags(app.tags)
}, [app.tags])
const runWorkflow = async (e, appId) => {
e.preventDefault()
e.stopPropagation()
const { data: apiKeysList } = await fetchApiKeysList({
url: `/apps/${appId}/api-keys`,
})
if (apiKeysList?.length > 0) push(`app/${appId}/run`)
else notify({ type: 'error', message: t('app.notPublish') })
}
return (
<>
<div
@ -278,10 +306,10 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
e.preventDefault()
getRedirection(isCurrentWorkspaceEditor, app, push)
}}
className='relative h-[160px] group col-span-1 bg-components-card-bg border-[1px] border-solid border-components-card-border rounded-xl shadow-sm inline-flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg'
className="relative h-[160px] group col-span-1 bg-components-card-bg border-[1px] border-solid border-components-card-border rounded-xl shadow-sm inline-flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg"
>
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
<div className='relative shrink-0'>
<div className="flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0">
<div className="relative shrink-0">
<AppIcon
size="large"
iconType={app.icon_type}
@ -289,53 +317,91 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
background={app.icon_background}
imageUrl={app.icon_url}
/>
<AppTypeIcon type={app.mode} wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm' className='w-3 h-3' />
<AppTypeIcon
type={app.mode}
wrapperClassName="absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm"
className="w-3 h-3"
/>
</div>
<div className='grow w-0 py-[1px]'>
<div className='flex items-center text-sm leading-5 font-semibold text-text-secondary'>
<div className='truncate' title={app.name}>{app.name}</div>
<div className="grow w-0 py-[1px]">
<div className="flex items-center text-sm leading-5 font-semibold text-text-secondary">
<div className="truncate" title={app.name}>
{app.name}
</div>
</div>
<div className='flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium'>
{app.mode === 'advanced-chat' && <div className='truncate'>{t('app.types.advanced').toUpperCase()}</div>}
{app.mode === 'chat' && <div className='truncate'>{t('app.types.chatbot').toUpperCase()}</div>}
{app.mode === 'agent-chat' && <div className='truncate'>{t('app.types.agent').toUpperCase()}</div>}
{app.mode === 'workflow' && <div className='truncate'>{t('app.types.workflow').toUpperCase()}</div>}
{app.mode === 'completion' && <div className='truncate'>{t('app.types.completion').toUpperCase()}</div>}
<div className="flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium">
{app.mode === 'advanced-chat' && (
<div className="truncate">
{t('app.types.advanced').toUpperCase()}
</div>
)}
{app.mode === 'chat' && (
<div className="truncate">
{t('app.types.chatbot').toUpperCase()}
</div>
)}
{app.mode === 'agent-chat' && (
<div className="truncate">
{t('app.types.agent').toUpperCase()}
</div>
)}
{app.mode === 'workflow' && (
<div className="truncate">
{t('app.types.workflow').toUpperCase()}
</div>
)}
{app.mode === 'completion' && (
<div className="truncate">
{t('app.types.completion').toUpperCase()}
</div>
)}
</div>
</div>
<div>
<Button onClick={(e) => {
e.preventDefault()
e.stopPropagation()
push(`app/${app.id}/run`)
}}></Button>
<Button
onClick={(e) => {
runWorkflow(e, app.id)
}}
>
{t('workflow.common.run')}
</Button>
</div>
</div>
<div className='title-wrapper h-[90px] px-[14px] text-xs leading-normal text-text-tertiary'>
<div className="title-wrapper h-[90px] px-[14px] text-xs leading-normal text-text-tertiary">
<div
className={cn(tags.length ? 'line-clamp-2' : 'line-clamp-4', 'group-hover:line-clamp-2')}
className={cn(
tags.length ? 'line-clamp-2' : 'line-clamp-4',
'group-hover:line-clamp-2',
)}
title={app.description}
>
{app.description}
</div>
</div>
<div className={cn(
'absolute bottom-1 left-0 right-0 items-center shrink-0 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
tags.length ? 'flex' : '!hidden group-hover:!flex',
)}>
<div
className={cn(
'absolute bottom-1 left-0 right-0 items-center shrink-0 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
tags.length ? 'flex' : '!hidden group-hover:!flex',
)}
>
{isCurrentWorkspaceEditor && (
<>
<div className={cn('grow flex items-center gap-1 w-0')} onClick={(e) => {
e.stopPropagation()
e.preventDefault()
}}>
<div className={cn(
'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full',
tags.length ? '!block' : '!hidden',
)}>
<div
className={cn('grow flex items-center gap-1 w-0')}
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
}}
>
<div
className={cn(
'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full',
tags.length ? '!block' : '!hidden',
)}
>
<TagSelector
position='bl'
type='app'
position="bl"
type="app"
targetID={app.id}
value={tags.map(tag => tag.id)}
selectedTags={tags}
@ -344,17 +410,15 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
/>
</div>
</div>
<div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px]' />
<div className='!hidden group-hover:!flex shrink-0'>
<div className="!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px]" />
<div className="!hidden group-hover:!flex shrink-0">
<CustomPopover
htmlContent={<Operations />}
position="br"
trigger="click"
btnElement={
<div
className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md'
>
<RiMoreFill className='w-4 h-4 text-text-tertiary' />
<div className="flex items-center justify-center w-8 h-8 cursor-pointer rounded-md">
<RiMoreFill className="w-4 h-4 text-text-tertiary" />
</div>
}
btnClassName={open =>
@ -364,7 +428,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
)
}
popupClassName={
(app.mode === 'completion' || app.mode === 'chat')
app.mode === 'completion' || app.mode === 'chat'
? '!w-[256px] translate-x-[-224px]'
: '!w-[160px] translate-x-[-128px]'
}

@ -84,7 +84,6 @@ const NodePanel: FC<Props> = ({
const isRetryNode = hasRetryNode(nodeInfo.node_type) && !!nodeInfo.retryDetail?.length
const isAgentNode = nodeInfo.node_type === BlockEnum.Agent && !!nodeInfo.agentLog?.length
const isToolNode = nodeInfo.node_type === BlockEnum.Tool && !!nodeInfo.agentLog?.length
console.log(nodeInfo)
return (
<div className={cn('px-2 py-1', className)}>
<div className='group transition-all bg-background-default border border-components-panel-border rounded-[10px] shadow-xs hover:shadow-md'>

@ -29,7 +29,7 @@ import Loading from '@/app/components/base/loading'
import { userInputsFormToPromptVariables } from '@/utils/model-config'
import Res from '@/app/components/run/text-generation/result'
import type { InstalledApp } from '@/models/explore'
import { DEFAULT_VALUE_MAX_LEN, appDefaultIconBackground } from '@/config'
import { appDefaultIconBackground } from '@/config'
import Toast from '@/app/components/base/toast'
import type { VisionFile, VisionSettings } from '@/types/app'
import { Resolution, TransferMethod } from '@/types/app'
@ -80,14 +80,8 @@ const TextGeneration: FC<IMainProps> = ({
const router = useRouter()
const pathname = usePathname()
useEffect(() => {
const params = new URLSearchParams(searchParams)
if (params.has('mode')) {
params.delete('mode')
router.replace(`${pathname}?${params.toString()}`)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const [queryInputs, setQueryInputs] = useState<any>(null)
// Notice this situation isCallBatchAPI but not in batch tab
const [isCallBatchAPI, setIsCallBatchAPI] = useState(false)
@ -143,7 +137,25 @@ const TextGeneration: FC<IMainProps> = ({
// eslint-disable-next-line ts/no-use-before-define
showResSidebar()
}
useEffect(() => {
const params = new URLSearchParams(searchParams)
if (params.has('mode')) {
params.delete('mode')
router.replace(`${pathname}?${params.toString()}`)
}
const queryInputs = Object.fromEntries(searchParams.entries())
if(queryInputs) {
setQueryInputs(queryInputs)
setInputs(queryInputs)
router.replace(pathname)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
if(queryInputs && typeof queryInputs === 'object' && Object.keys(queryInputs).length > 0)
handleSend()
}, [queryInputs])
const [controlRetry, setControlRetry] = useState(0)
const handleRetryAllFailedTask = () => {
setControlRetry(Date.now())
@ -193,137 +205,7 @@ const TextGeneration: FC<IMainProps> = ({
res[t('share.generation.completionResult')] = result
return res
})
const checkBatchInputs = (data: string[][]) => {
if (!data || data.length === 0) {
notify({ type: 'error', message: t('share.generation.errorMsg.empty') })
return false
}
const headerData = data[0]
let isMapVarName = true
promptConfig?.prompt_variables.forEach((item, index) => {
if (!isMapVarName)
return
if (item.name !== headerData[index])
isMapVarName = false
})
if (!isMapVarName) {
notify({ type: 'error', message: t('share.generation.errorMsg.fileStructNotMatch') })
return false
}
let payloadData = data.slice(1)
if (payloadData.length === 0) {
notify({ type: 'error', message: t('share.generation.errorMsg.atLeastOne') })
return false
}
// check middle empty line
const allEmptyLineIndexes = payloadData.filter(item => item.every(i => i === '')).map(item => payloadData.indexOf(item))
if (allEmptyLineIndexes.length > 0) {
let hasMiddleEmptyLine = false
let startIndex = allEmptyLineIndexes[0] - 1
allEmptyLineIndexes.forEach((index) => {
if (hasMiddleEmptyLine)
return
if (startIndex + 1 !== index) {
hasMiddleEmptyLine = true
return
}
startIndex++
})
if (hasMiddleEmptyLine) {
notify({ type: 'error', message: t('share.generation.errorMsg.emptyLine', { rowIndex: startIndex + 2 }) })
return false
}
}
// check row format
payloadData = payloadData.filter(item => !item.every(i => i === ''))
// after remove empty rows in the end, checked again
if (payloadData.length === 0) {
notify({ type: 'error', message: t('share.generation.errorMsg.atLeastOne') })
return false
}
let errorRowIndex = 0
let requiredVarName = ''
let moreThanMaxLengthVarName = ''
let maxLength = 0
payloadData.forEach((item, index) => {
if (errorRowIndex !== 0)
return
promptConfig?.prompt_variables.forEach((varItem, varIndex) => {
if (errorRowIndex !== 0)
return
if (varItem.type === 'string') {
const maxLen = varItem.max_length || DEFAULT_VALUE_MAX_LEN
if (item[varIndex].length > maxLen) {
moreThanMaxLengthVarName = varItem.name
maxLength = maxLen
errorRowIndex = index + 1
return
}
}
if (!varItem.required)
return
if (item[varIndex].trim() === '') {
requiredVarName = varItem.name
errorRowIndex = index + 1
}
})
})
if (errorRowIndex !== 0) {
if (requiredVarName)
notify({ type: 'error', message: t('share.generation.errorMsg.invalidLine', { rowIndex: errorRowIndex + 1, varName: requiredVarName }) })
if (moreThanMaxLengthVarName)
notify({ type: 'error', message: t('share.generation.errorMsg.moreThanMaxLengthLine', { rowIndex: errorRowIndex + 1, varName: moreThanMaxLengthVarName, maxLength }) })
return false
}
return true
}
const handleRunBatch = (data: string[][]) => {
if (!checkBatchInputs(data))
return
if (!allTasksFinished) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForBatchResponse') })
return
}
const payloadData = data.filter(item => !item.every(i => i === '')).slice(1)
const varLen = promptConfig?.prompt_variables.length || 0
setIsCallBatchAPI(true)
const allTaskList: Task[] = payloadData.map((item, i) => {
const inputs: Record<string, string> = {}
if (varLen > 0) {
item.slice(0, varLen).forEach((input, index) => {
inputs[promptConfig?.prompt_variables[index].key as string] = input
})
}
return {
id: i + 1,
status: i < GROUP_SIZE ? TaskStatus.running : TaskStatus.pending,
params: {
inputs,
},
}
})
setAllTaskList(allTaskList)
setCurrGroupNum(0)
setControlSend(Date.now())
// clear run once task status
setControlStopResponding(Date.now())
// eslint-disable-next-line ts/no-use-before-define
showResSidebar()
}
const handleCompleted = (completionRes: string, taskId?: number, isSuccess?: boolean) => {
const allTaskListLatest = getLatestTaskList()
const batchCompletionResLatest = getBatchCompletionRes()

@ -330,9 +330,12 @@ const Result: FC<IResultProps> = ({
}
}
const controlSendRef = useRef<number | undefined>()
const [controlClearMoreLikeThis, setControlClearMoreLikeThis] = useState(0)
useEffect(() => {
if (controlSend) {
if (controlSend && controlSendRef?.current !== controlSend) {
controlSendRef.current = controlSend
handleSend()
setControlClearMoreLikeThis(Date.now())
}

@ -110,18 +110,18 @@ const ApoToolsPreview = ({ onSelect, apoToolType, hidePopover }: ApoToolsPreview
<div className='flex'>
<div className='flex-1'>{toolDetail.label[language]}</div>
<Button variant='primary' className=' space-x-2' onClick={() => handleSelect(toolDetail)}>
<div>使</div>
<div>{t('apo.tool.use')}</div>
</Button>
</div>
<div className="text-xs text-text-tertiary">
<div className="pt-2">
<>{toolDetail.description[language]}</>
{t('apo.tool.desc')}<>{toolDetail.description[language]}</>
</div>
<div className="pt-2">
{apoToolType === 'apo_select' ? (
<>
{toolDetail?.display.type === 'metric' && <div className="pb-2">{toolDetail.display.unit}</div>}
{toolDetail?.display?.type && <div>{t(`apo.displayType.${toolDetail?.display?.type}`)}</div>}
{toolDetail?.display.type === 'metric' && <div className="pb-2">{t('apo.tool.unit')}{toolDetail.display.unit}</div>}
{toolDetail?.display?.type && <div>{t('apo.tool.output')}{t(`apo.displayType.${toolDetail?.display?.type}`)}</div>}
<div className="px-4 py-2">
<div className="h-[0.5px] divider-subtle" />
</div>
@ -132,7 +132,7 @@ const ApoToolsPreview = ({ onSelect, apoToolType, hidePopover }: ApoToolsPreview
</>
) : (
<div className="flex">
<span></span>
<span>{t('apo.tool.input')}</span>
<div>
{toolDetail?.parameters.map(parameter => (
<ParametersInfo

@ -58,8 +58,8 @@ const ToolTrialRun = ({ infoSchemas, type, title }) => {
}
return <>
<div className='py-2 text-text-primary system-sm-semibold-uppercase'>
{/* {t('tools.setBuiltInTools.parameters')} */}
{t('apo.tool.test')}
</div>
<Form inputs={formInputs} values={formValues} onChange={newValues => setFormValues(newValues) }
></Form>

@ -99,7 +99,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
return t('workflow.tabs.searchTool')
return ''
}, [activeTab, t])
const TabContent = () => <div className={`flex flex-col overflow-hidden rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${popupClassName}`}>
const TabContent = () => <div className={`flex flex-col min-h-[80vh] overflow-hidden rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${popupClassName}`}>
<div className='px-2 pt-2 ' onClick={e => e.stopPropagation()}>
{activeTab === TabsEnum.Blocks && (
<Input
@ -124,7 +124,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
)}
</div>
<div className='h-0 grow'>
<div className='h-auto grow'>
<Tabs
activeTab={activeTab}

@ -34,7 +34,7 @@ const Tabs: FC<TabsProps> = ({
const { data: workflowTools } = useAllWorkflowTools()
return (
<div onClick={e => e.stopPropagation()} className='h-full flex flex-col'>
<div onClick={e => e.stopPropagation()} className='h-full flex flex-col min-h-[80vh]'>
{
!noBlocks && (
<div className='flex items-center px-3 border-b-[0.5px] border-divider-subtle'>

@ -16,7 +16,14 @@ const translation = {
custom: 'Custom query return data',
metric: 'Metric Line Chart',
},
tool: {
test: 'Test',
use: 'Use',
desc: 'Description',
output: 'Output',
input: 'Input',
unit: 'Unit',
},
}
export default translation

@ -180,6 +180,7 @@ const translation = {
noParams: 'No parameters needed',
},
showMyCreatedAppsOnly: 'Created by me',
notPublish: 'This workflow has not been published or the API-Key has not been created',
}
export default translation

@ -16,6 +16,14 @@ const translation = {
custom: '自定义查询返回数据',
metric: '指标折线图',
},
tool: {
test: '数据测试',
use: '使用',
desc: '描述',
output: '输出',
input: '输入',
unit: '单位',
},
}

@ -181,6 +181,7 @@ const translation = {
},
openInExplore: '在“探索”中打开',
showMyCreatedAppsOnly: '我创建的',
notPublish: '此工作流还未被发布或未创建API-Key',
}
export default translation

@ -570,7 +570,6 @@ export const sseV1Post = (
const abortController = new AbortController()
const token = localStorage.getItem('console_token')
console.log(baseOptions)
const options = Object.assign({}, baseOptions, {
method: 'POST',
signal: abortController.signal,

Loading…
Cancel
Save