mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
Merge branch 'main' into feat/plugins
This commit is contained in:
@ -2,7 +2,8 @@
|
||||
"root": true,
|
||||
"extends": [
|
||||
"next",
|
||||
"@antfu"
|
||||
"@antfu",
|
||||
"plugin:storybook/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-definitions": [
|
||||
|
||||
3
web/.gitignore
vendored
3
web/.gitignore
vendored
@ -49,4 +49,5 @@ package-lock.json
|
||||
# pmpm
|
||||
pnpm-lock.yaml
|
||||
|
||||
.favorites.json
|
||||
.favorites.json
|
||||
*storybook.log
|
||||
19
web/.storybook/main.ts
Normal file
19
web/.storybook/main.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { StorybookConfig } from '@storybook/nextjs'
|
||||
|
||||
const config: StorybookConfig = {
|
||||
// stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
stories: ['../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-onboarding',
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@chromatic-com/storybook',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/nextjs',
|
||||
options: {},
|
||||
},
|
||||
staticDirs: ['../public'],
|
||||
}
|
||||
export default config
|
||||
37
web/.storybook/preview.tsx
Normal file
37
web/.storybook/preview.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import type { Preview } from '@storybook/react'
|
||||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import I18nServer from '../app/components/i18n-server'
|
||||
|
||||
import '../app/styles/globals.css'
|
||||
import '../app/styles/markdown.scss'
|
||||
import './storybook.css'
|
||||
|
||||
export const decorators = [
|
||||
withThemeByDataAttribute({
|
||||
themes: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
},
|
||||
defaultTheme: 'light',
|
||||
attributeName: 'data-theme',
|
||||
}),
|
||||
Story => {
|
||||
return <I18nServer>
|
||||
<Story />
|
||||
</I18nServer>
|
||||
}
|
||||
];
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
6
web/.storybook/storybook.css
Normal file
6
web/.storybook/storybook.css
Normal file
@ -0,0 +1,6 @@
|
||||
html,
|
||||
body {
|
||||
max-width: unset;
|
||||
overflow: auto;
|
||||
user-select: text;
|
||||
}
|
||||
@ -74,6 +74,18 @@ If you want to customize the host and port:
|
||||
npm run start --port=3001 --host=0.0.0.0
|
||||
```
|
||||
|
||||
## Storybook
|
||||
|
||||
This project uses [Storybook](https://storybook.js.org/) for UI component development.
|
||||
|
||||
To start the storybook server, run:
|
||||
|
||||
```bash
|
||||
yarn storybook
|
||||
```
|
||||
|
||||
Open [http://localhost:6006](http://localhost:6006) with your browser to see the result.
|
||||
|
||||
## Lint Code
|
||||
|
||||
If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting.
|
||||
|
||||
@ -87,15 +87,15 @@ const Apps = () => {
|
||||
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
||||
mutate()
|
||||
}
|
||||
}, [])
|
||||
}, [mutate, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCurrentWorkspaceDatasetOperator)
|
||||
return router.replace('/datasets')
|
||||
}, [isCurrentWorkspaceDatasetOperator])
|
||||
}, [router, isCurrentWorkspaceDatasetOperator])
|
||||
|
||||
const hasMore = data?.at(-1)?.has_more ?? true
|
||||
useEffect(() => {
|
||||
const hasMore = data?.at(-1)?.has_more ?? true
|
||||
let observer: IntersectionObserver | undefined
|
||||
if (anchorRef.current) {
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
@ -105,7 +105,7 @@ const Apps = () => {
|
||||
observer.observe(anchorRef.current)
|
||||
}
|
||||
return () => observer?.disconnect()
|
||||
}, [isLoading, setSize, anchorRef, mutate, hasMore])
|
||||
}, [isLoading, setSize, anchorRef, mutate, data])
|
||||
|
||||
const { run: handleSearch } = useDebounceFn(() => {
|
||||
setSearchKeywords(keywords)
|
||||
|
||||
@ -1050,6 +1050,151 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/hit_testing'
|
||||
method='POST'
|
||||
title='Dataset hit testing'
|
||||
name='#dataset_hit_testing'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
Dataset ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='query' type='string' key='query'>
|
||||
retrieval keywordc
|
||||
</Property>
|
||||
<Property name='retrieval_model' type='object' key='retrieval_model'>
|
||||
retrieval keyword(Optional, if not filled, it will be recalled according to the default method)
|
||||
- <code>search_method</code> (text) Search method: One of the following four keywords is required
|
||||
- <code>keyword_search</code> Keyword search
|
||||
- <code>semantic_search</code> Semantic search
|
||||
- <code>full_text_search</code> Full-text search
|
||||
- <code>hybrid_search</code> Hybrid search
|
||||
- <code>reranking_enable</code> (bool) Whether to enable reranking, optional, required if the search mode is semantic_search or hybrid_search
|
||||
- <code>reranking_mode</code> (object) Rerank model configuration, optional, required if reranking is enabled
|
||||
- <code>reranking_provider_name</code> (string) Rerank model provider
|
||||
- <code>reranking_model_name</code> (string) Rerank model name
|
||||
- <code>weights</code> (double) Semantic search weight setting in hybrid search mode
|
||||
- <code>top_k</code> (integer) Number of results to return, optional
|
||||
- <code>score_threshold_enabled</code> (bool) Whether to enable score threshold
|
||||
- <code>score_threshold</code> (double) Score threshold
|
||||
</Property>
|
||||
<Property name='external_retrieval_model' type='object' key='external_retrieval_model'>
|
||||
Unused field
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/hit_testing"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/hit_testing' \\\n--header 'Authorization: Bearer {api_key}'\\\n--header 'Content-Type: application/json'\\\n--data-raw '{
|
||||
"query": "test",
|
||||
"retrieval_model": {
|
||||
"search_method": "keyword_search",
|
||||
"reranking_enable": false,
|
||||
"reranking_mode": null,
|
||||
"reranking_model": {
|
||||
"reranking_provider_name": "",
|
||||
"reranking_model_name": ""
|
||||
},
|
||||
"weights": null,
|
||||
"top_k": 1,
|
||||
"score_threshold_enabled": false,
|
||||
"score_threshold": null
|
||||
}
|
||||
}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/hit_testing' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"query": "test",
|
||||
"retrieval_model": {
|
||||
"search_method": "keyword_search",
|
||||
"reranking_enable": false,
|
||||
"reranking_mode": null,
|
||||
"reranking_model": {
|
||||
"reranking_provider_name": "",
|
||||
"reranking_model_name": ""
|
||||
},
|
||||
"weights": null,
|
||||
"top_k": 2,
|
||||
"score_threshold_enabled": false,
|
||||
"score_threshold": null
|
||||
}
|
||||
}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"query": {
|
||||
"content": "test"
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"segment": {
|
||||
"id": "7fa6f24f-8679-48b3-bc9d-bdf28d73f218",
|
||||
"position": 1,
|
||||
"document_id": "a8c6c36f-9f5d-4d7a-8472-f5d7b75d71d2",
|
||||
"content": "Operation guide",
|
||||
"answer": null,
|
||||
"word_count": 847,
|
||||
"tokens": 280,
|
||||
"keywords": [
|
||||
"install",
|
||||
"java",
|
||||
"base",
|
||||
"scripts",
|
||||
"jdk",
|
||||
"manual",
|
||||
"internal",
|
||||
"opens",
|
||||
"add",
|
||||
"vmoptions"
|
||||
],
|
||||
"index_node_id": "39dd8443-d960-45a8-bb46-7275ad7fbc8e",
|
||||
"index_node_hash": "0189157697b3c6a418ccf8264a09699f25858975578f3467c76d6bfc94df1d73",
|
||||
"hit_count": 0,
|
||||
"enabled": true,
|
||||
"disabled_at": null,
|
||||
"disabled_by": null,
|
||||
"status": "completed",
|
||||
"created_by": "dbcb1ab5-90c8-41a7-8b78-73b235eb6f6f",
|
||||
"created_at": 1728734540,
|
||||
"indexing_at": 1728734552,
|
||||
"completed_at": 1728734584,
|
||||
"error": null,
|
||||
"stopped_at": null,
|
||||
"document": {
|
||||
"id": "a8c6c36f-9f5d-4d7a-8472-f5d7b75d71d2",
|
||||
"data_source_type": "upload_file",
|
||||
"name": "readme.txt",
|
||||
"doc_type": null
|
||||
}
|
||||
},
|
||||
"score": 3.730463140527718e-05,
|
||||
"tsne_position": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
<Row>
|
||||
<Col>
|
||||
### Error message
|
||||
|
||||
@ -1049,6 +1049,152 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
---
|
||||
|
||||
<Heading
|
||||
url='/datasets/{dataset_id}/hit_testing'
|
||||
method='POST'
|
||||
title='知识库召回测试'
|
||||
name='#dataset_hit_testing'
|
||||
/>
|
||||
<Row>
|
||||
<Col>
|
||||
### Path
|
||||
<Properties>
|
||||
<Property name='dataset_id' type='string' key='dataset_id'>
|
||||
知识库 ID
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Request Body
|
||||
<Properties>
|
||||
<Property name='query' type='string' key='query'>
|
||||
召回关键词
|
||||
</Property>
|
||||
<Property name='retrieval_model' type='object' key='retrieval_model'>
|
||||
召回参数(选填,如不填,按照默认方式召回)
|
||||
- <code>search_method</code> (text) 检索方法:以下三个关键字之一,必填
|
||||
- <code>keyword_search</code> 关键字检索
|
||||
- <code>semantic_search</code> 语义检索
|
||||
- <code>full_text_search</code> 全文检索
|
||||
- <code>hybrid_search</code> 混合检索
|
||||
- <code>reranking_enable</code> (bool) 是否启用 Reranking,非必填,如果检索模式为semantic_search模式或者hybrid_search则传值
|
||||
- <code>reranking_mode</code> (object) Rerank模型配置,非必填,如果启用了 reranking 则传值
|
||||
- <code>reranking_provider_name</code> (string) Rerank 模型提供商
|
||||
- <code>reranking_model_name</code> (string) Rerank 模型名称
|
||||
- <code>weights</code> (double) 混合检索模式下语意检索的权重设置
|
||||
- <code>top_k</code> (integer) 返回结果数量,非必填
|
||||
- <code>score_threshold_enabled</code> (bool) 是否开启Score阈值
|
||||
- <code>score_threshold</code> (double) Score阈值
|
||||
</Property>
|
||||
<Property name='external_retrieval_model' type='object' key='external_retrieval_model'>
|
||||
未启用字段
|
||||
</Property>
|
||||
</Properties>
|
||||
</Col>
|
||||
<Col sticky>
|
||||
<CodeGroup
|
||||
title="Request"
|
||||
tag="POST"
|
||||
label="/datasets/{dataset_id}/hit_testing"
|
||||
targetCode={`curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}/hit_testing' \\\n--header 'Authorization: Bearer {api_key}'\\\n--header 'Content-Type: application/json'\\\n--data-raw '{
|
||||
"query": "test",
|
||||
"retrieval_model": {
|
||||
"search_method": "keyword_search",
|
||||
"reranking_enable": false,
|
||||
"reranking_mode": null,
|
||||
"reranking_model": {
|
||||
"reranking_provider_name": "",
|
||||
"reranking_model_name": ""
|
||||
},
|
||||
"weights": null,
|
||||
"top_k": 1,
|
||||
"score_threshold_enabled": false,
|
||||
"score_threshold": null
|
||||
}
|
||||
}'`}
|
||||
>
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}/hit_testing' \
|
||||
--header 'Authorization: Bearer {api_key}' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"query": "test",
|
||||
"retrieval_model": {
|
||||
"search_method": "keyword_search",
|
||||
"reranking_enable": false,
|
||||
"reranking_mode": null,
|
||||
"reranking_model": {
|
||||
"reranking_provider_name": "",
|
||||
"reranking_model_name": ""
|
||||
},
|
||||
"weights": null,
|
||||
"top_k": 2,
|
||||
"score_threshold_enabled": false,
|
||||
"score_threshold": null
|
||||
}
|
||||
}'
|
||||
```
|
||||
</CodeGroup>
|
||||
<CodeGroup title="Response">
|
||||
```json {{ title: 'Response' }}
|
||||
{
|
||||
"query": {
|
||||
"content": "test"
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"segment": {
|
||||
"id": "7fa6f24f-8679-48b3-bc9d-bdf28d73f218",
|
||||
"position": 1,
|
||||
"document_id": "a8c6c36f-9f5d-4d7a-8472-f5d7b75d71d2",
|
||||
"content": "Operation guide",
|
||||
"answer": null,
|
||||
"word_count": 847,
|
||||
"tokens": 280,
|
||||
"keywords": [
|
||||
"install",
|
||||
"java",
|
||||
"base",
|
||||
"scripts",
|
||||
"jdk",
|
||||
"manual",
|
||||
"internal",
|
||||
"opens",
|
||||
"add",
|
||||
"vmoptions"
|
||||
],
|
||||
"index_node_id": "39dd8443-d960-45a8-bb46-7275ad7fbc8e",
|
||||
"index_node_hash": "0189157697b3c6a418ccf8264a09699f25858975578f3467c76d6bfc94df1d73",
|
||||
"hit_count": 0,
|
||||
"enabled": true,
|
||||
"disabled_at": null,
|
||||
"disabled_by": null,
|
||||
"status": "completed",
|
||||
"created_by": "dbcb1ab5-90c8-41a7-8b78-73b235eb6f6f",
|
||||
"created_at": 1728734540,
|
||||
"indexing_at": 1728734552,
|
||||
"completed_at": 1728734584,
|
||||
"error": null,
|
||||
"stopped_at": null,
|
||||
"document": {
|
||||
"id": "a8c6c36f-9f5d-4d7a-8472-f5d7b75d71d2",
|
||||
"data_source_type": "upload_file",
|
||||
"name": "readme.txt",
|
||||
"doc_type": null
|
||||
}
|
||||
},
|
||||
"score": 3.730463140527718e-05,
|
||||
"tsne_position": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
---
|
||||
|
||||
<Row>
|
||||
|
||||
@ -63,7 +63,7 @@ const ConfigContent: FC<Props> = ({
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
|
||||
|
||||
const {
|
||||
currentModel,
|
||||
currentModel: currentRerankModel,
|
||||
} = useCurrentProviderAndModel(
|
||||
rerankModelList,
|
||||
rerankDefaultModel
|
||||
@ -74,11 +74,6 @@ const ConfigContent: FC<Props> = ({
|
||||
: undefined,
|
||||
)
|
||||
|
||||
const handleDisabledSwitchClick = useCallback(() => {
|
||||
if (!currentModel)
|
||||
Toast.notify({ type: 'error', message: t('workflow.errorMsg.rerankModelRequired') })
|
||||
}, [currentModel, rerankDefaultModel, t])
|
||||
|
||||
const rerankModel = (() => {
|
||||
if (datasetConfigs.reranking_model?.reranking_provider_name) {
|
||||
return {
|
||||
@ -164,12 +159,33 @@ const ConfigContent: FC<Props> = ({
|
||||
const showWeightedScorePanel = showWeightedScore && datasetConfigs.reranking_mode === RerankingModeEnum.WeightedScore && datasetConfigs.weights
|
||||
const selectedRerankMode = datasetConfigs.reranking_mode || RerankingModeEnum.RerankingModel
|
||||
|
||||
const canManuallyToggleRerank = useMemo(() => {
|
||||
return !(
|
||||
(selectedDatasetsMode.allInternal && selectedDatasetsMode.allEconomic)
|
||||
|| selectedDatasetsMode.allExternal
|
||||
)
|
||||
}, [selectedDatasetsMode.allEconomic, selectedDatasetsMode.allExternal, selectedDatasetsMode.allInternal])
|
||||
|
||||
const showRerankModel = useMemo(() => {
|
||||
if (datasetConfigs.reranking_enable === false && selectedDatasetsMode.allEconomic)
|
||||
if (!canManuallyToggleRerank)
|
||||
return false
|
||||
|
||||
return true
|
||||
}, [datasetConfigs.reranking_enable, selectedDatasetsMode.allEconomic])
|
||||
return datasetConfigs.reranking_enable
|
||||
}, [canManuallyToggleRerank, datasetConfigs.reranking_enable])
|
||||
|
||||
const handleDisabledSwitchClick = useCallback(() => {
|
||||
if (!currentRerankModel && !showRerankModel)
|
||||
Toast.notify({ type: 'error', message: t('workflow.errorMsg.rerankModelRequired') })
|
||||
}, [currentRerankModel, showRerankModel, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (!canManuallyToggleRerank && showRerankModel !== datasetConfigs.reranking_enable) {
|
||||
onChange({
|
||||
...datasetConfigs,
|
||||
reranking_enable: showRerankModel,
|
||||
})
|
||||
}
|
||||
}, [canManuallyToggleRerank, showRerankModel, datasetConfigs, onChange])
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -256,13 +272,15 @@ const ConfigContent: FC<Props> = ({
|
||||
>
|
||||
<Switch
|
||||
size='md'
|
||||
defaultValue={currentModel ? showRerankModel : false}
|
||||
disabled={!currentModel}
|
||||
defaultValue={showRerankModel}
|
||||
disabled={!currentRerankModel || !canManuallyToggleRerank}
|
||||
onChange={(v) => {
|
||||
onChange({
|
||||
...datasetConfigs,
|
||||
reranking_enable: v,
|
||||
})
|
||||
if (canManuallyToggleRerank) {
|
||||
onChange({
|
||||
...datasetConfigs,
|
||||
reranking_enable: v,
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -42,6 +42,7 @@ const ParamsConfig = ({
|
||||
allHighQuality,
|
||||
allHighQualityFullTextSearch,
|
||||
allHighQualityVectorSearch,
|
||||
allInternal,
|
||||
allExternal,
|
||||
mixtureHighQualityAndEconomic,
|
||||
inconsistentEmbeddingModel,
|
||||
@ -50,7 +51,7 @@ const ParamsConfig = ({
|
||||
const { datasets, retrieval_model, score_threshold_enabled, ...restConfigs } = datasetConfigs
|
||||
let rerankEnable = restConfigs.reranking_enable
|
||||
|
||||
if ((allEconomic && !restConfigs.reranking_model?.reranking_provider_name && rerankEnable === undefined) || allExternal)
|
||||
if (((allInternal && allEconomic) || allExternal) && !restConfigs.reranking_model?.reranking_provider_name && rerankEnable === undefined)
|
||||
rerankEnable = false
|
||||
|
||||
if (allEconomic || allHighQuality || allHighQualityFullTextSearch || allHighQualityVectorSearch || (allExternal && selectedDatasets.length === 1))
|
||||
|
||||
107
web/app/components/base/button/index.stories.tsx
Normal file
107
web/app/components/base/button/index.stories.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { fn } from '@storybook/test'
|
||||
|
||||
import { RocketLaunchIcon } from '@heroicons/react/20/solid'
|
||||
import { Button } from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
loading: { control: 'boolean' },
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['primary', 'warning', 'secondary', 'secondary-accent', 'ghost', 'ghost-accent', 'tertiary'],
|
||||
},
|
||||
},
|
||||
args: {
|
||||
variant: 'ghost',
|
||||
onClick: fn(),
|
||||
children: 'adsf',
|
||||
},
|
||||
} satisfies Meta<typeof Button>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
loading: false,
|
||||
children: 'Primary Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
variant: 'secondary',
|
||||
children: 'Secondary Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const SecondaryAccent: Story = {
|
||||
args: {
|
||||
variant: 'secondary-accent',
|
||||
children: 'Secondary Accent Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Ghost: Story = {
|
||||
args: {
|
||||
variant: 'ghost',
|
||||
children: 'Ghost Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const GhostAccent: Story = {
|
||||
args: {
|
||||
variant: 'ghost-accent',
|
||||
children: 'Ghost Accent Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Tertiary: Story = {
|
||||
args: {
|
||||
variant: 'tertiary',
|
||||
children: 'Tertiary Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Warning: Story = {
|
||||
args: {
|
||||
variant: 'warning',
|
||||
children: 'Warning Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
disabled: true,
|
||||
children: 'Disabled Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
loading: true,
|
||||
children: 'Loading Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
children: (
|
||||
<>
|
||||
<RocketLaunchIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" />
|
||||
Launch
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
@ -28,6 +28,8 @@ const HeaderInMobile = () => {
|
||||
className='mr-2'
|
||||
size='tiny'
|
||||
icon={appData?.site.icon}
|
||||
iconType={appData?.site.icon_type}
|
||||
imageUrl={appData?.site.icon_url}
|
||||
background={appData?.site.icon_background}
|
||||
/>
|
||||
<div className='py-1 text-base font-semibold text-gray-800 truncate'>
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
export const markdownContent = `
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
|
||||
##### Heading 5
|
||||
|
||||
###### Heading 6
|
||||
|
||||
# Basic markdown content.
|
||||
|
||||
Should support **bold**, *italic*, and ~~strikethrough~~.
|
||||
Should support [links](https://www.google.com).
|
||||
Should support inline \`code\` blocks.
|
||||
|
||||
# Number list
|
||||
|
||||
1. First item
|
||||
2. Second item
|
||||
3. Third item
|
||||
|
||||
# Bullet list
|
||||
|
||||
- First item
|
||||
- Second item
|
||||
- Third item
|
||||
|
||||
# Link
|
||||
|
||||
[Google](https://www.google.com)
|
||||
|
||||
# Image
|
||||
|
||||

|
||||
|
||||
# Table
|
||||
|
||||
| Column 1 | Column 2 | Column 3 |
|
||||
| -------- | -------- | -------- |
|
||||
| Cell 1 | Cell 2 | Cell 3 |
|
||||
| Cell 4 | Cell 5 | Cell 6 |
|
||||
| Cell 7 | Cell 8 | Cell 9 |
|
||||
|
||||
# Code
|
||||
|
||||
\`\`\`JavaScript
|
||||
const code = "code"
|
||||
\`\`\`
|
||||
|
||||
# Blockquote
|
||||
|
||||
> This is a blockquote.
|
||||
|
||||
# Horizontal rule
|
||||
|
||||
---
|
||||
`
|
||||
@ -0,0 +1,27 @@
|
||||
export const markdownContentSVG = `
|
||||
\`\`\`svg
|
||||
<svg width="400" height="600" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100%" height="100%" fill="#F0F8FF"/>
|
||||
|
||||
<text x="50%" y="60" font-family="楷体" font-size="32" fill="#4682B4" text-anchor="middle">创意Logo设计</text>
|
||||
|
||||
<line x1="50" y1="80" x2="350" y2="80" stroke="#B0C4DE" stroke-width="2"/>
|
||||
|
||||
<text x="50%" y="120" font-family="Arial" font-size="24" fill="#708090" text-anchor="middle">科研</text>
|
||||
<text x="50%" y="150" font-family="MS Mincho" font-size="20" fill="#778899" text-anchor="middle">科学研究</text>
|
||||
|
||||
<text x="50%" y="200" font-family="汇文明朝体" font-size="18" fill="#696969" text-anchor="middle">
|
||||
<tspan x="50%" dy="25">探索未知的灯塔,</tspan>
|
||||
<tspan x="50%" dy="25">照亮人类前进的道路。</tspan>
|
||||
<tspan x="50%" dy="25">科研,是永不熄灭的好奇心,</tspan>
|
||||
<tspan x="50%" dy="25">也是推动世界进步的引擎。</tspan>
|
||||
</text>
|
||||
|
||||
<circle cx="200" cy="400" r="80" fill="none" stroke="#4169E1" stroke-width="3"/>
|
||||
<line x1="200" y1="320" x2="200" y2="480" stroke="#4169E1" stroke-width="3"/>
|
||||
<line x1="120" y1="400" x2="280" y2="400" stroke="#4169E1" stroke-width="3"/>
|
||||
|
||||
<text x="50%" y="550" font-family="微软雅黑" font-size="16" fill="#1E90FF" text-anchor="middle">探索 • 创新 • 进步</text>
|
||||
</svg>
|
||||
\`\`\`
|
||||
`
|
||||
@ -0,0 +1,136 @@
|
||||
import type { WorkflowProcess } from '@/app/components/base/chat/types'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
|
||||
export const mockedWorkflowProcess = {
|
||||
status: WorkflowRunningStatus.Succeeded,
|
||||
resultText: 'Hello, how can I assist you today?',
|
||||
tracing: [
|
||||
{
|
||||
extras: {},
|
||||
id: 'f6337dc9-e280-4915-965f-10b0552dd917',
|
||||
node_id: '1724232060789',
|
||||
node_type: 'start',
|
||||
title: 'Start',
|
||||
index: 1,
|
||||
predecessor_node_id: null,
|
||||
inputs: {
|
||||
'sys.query': 'hi',
|
||||
'sys.files': [],
|
||||
'sys.conversation_id': '92ce0a3e-8f15-43d1-b31d-32716c4b10a7',
|
||||
'sys.user_id': 'fbff43f9-d5a4-4e85-b63b-d3a91d806c6f',
|
||||
'sys.dialogue_count': 1,
|
||||
'sys.app_id': 'b2e8906a-aad3-43a0-9ace-0e44cc7315e1',
|
||||
'sys.workflow_id': '70004abe-561f-418b-b9e8-8c957ce55140',
|
||||
'sys.workflow_run_id': '69db9267-aaee-42e1-9581-dbfb67e8eeb5',
|
||||
},
|
||||
process_data: null,
|
||||
outputs: {
|
||||
'sys.query': 'hi',
|
||||
'sys.files': [],
|
||||
'sys.conversation_id': '92ce0a3e-8f15-43d1-b31d-32716c4b10a7',
|
||||
'sys.user_id': 'fbff43f9-d5a4-4e85-b63b-d3a91d806c6f',
|
||||
'sys.dialogue_count': 1,
|
||||
'sys.app_id': 'b2e8906a-aad3-43a0-9ace-0e44cc7315e1',
|
||||
'sys.workflow_id': '70004abe-561f-418b-b9e8-8c957ce55140',
|
||||
'sys.workflow_run_id': '69db9267-aaee-42e1-9581-dbfb67e8eeb5',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.035744,
|
||||
execution_metadata: null,
|
||||
created_at: 1728980002,
|
||||
finished_at: 1728980002,
|
||||
files: [],
|
||||
parallel_id: null,
|
||||
parallel_start_node_id: null,
|
||||
parent_parallel_id: null,
|
||||
parent_parallel_start_node_id: null,
|
||||
iteration_id: null,
|
||||
},
|
||||
{
|
||||
extras: {},
|
||||
id: '92204d8d-4198-4c46-aa02-c2754b11dec9',
|
||||
node_id: 'llm',
|
||||
node_type: 'llm',
|
||||
title: 'LLM',
|
||||
index: 2,
|
||||
predecessor_node_id: '1724232060789',
|
||||
inputs: null,
|
||||
process_data: {
|
||||
model_mode: 'chat',
|
||||
prompts: [
|
||||
{
|
||||
role: 'system',
|
||||
text: 'hi',
|
||||
files: [],
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
text: 'hi',
|
||||
files: [],
|
||||
},
|
||||
],
|
||||
model_provider: 'openai',
|
||||
model_name: 'gpt-4o-mini',
|
||||
},
|
||||
outputs: {
|
||||
text: 'Hello! How can I assist you today?',
|
||||
usage: {
|
||||
prompt_tokens: 13,
|
||||
prompt_unit_price: '0.15',
|
||||
prompt_price_unit: '0.000001',
|
||||
prompt_price: '0.0000020',
|
||||
completion_tokens: 9,
|
||||
completion_unit_price: '0.60',
|
||||
completion_price_unit: '0.000001',
|
||||
completion_price: '0.0000054',
|
||||
total_tokens: 22,
|
||||
total_price: '0.0000074',
|
||||
currency: 'USD',
|
||||
latency: 1.8902503330027685,
|
||||
},
|
||||
finish_reason: 'stop',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 5.089409,
|
||||
execution_metadata: {
|
||||
total_tokens: 22,
|
||||
total_price: '0.0000074',
|
||||
currency: 'USD',
|
||||
},
|
||||
created_at: 1728980002,
|
||||
finished_at: 1728980007,
|
||||
files: [],
|
||||
parallel_id: null,
|
||||
parallel_start_node_id: null,
|
||||
parent_parallel_id: null,
|
||||
parent_parallel_start_node_id: null,
|
||||
iteration_id: null,
|
||||
},
|
||||
{
|
||||
extras: {},
|
||||
id: '7149bac6-60f9-4e06-a5ed-1d9d3764c06b',
|
||||
node_id: 'answer',
|
||||
node_type: 'answer',
|
||||
title: 'Answer',
|
||||
index: 3,
|
||||
predecessor_node_id: 'llm',
|
||||
inputs: null,
|
||||
process_data: null,
|
||||
outputs: {
|
||||
answer: 'Hello! How can I assist you today?',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.015339,
|
||||
execution_metadata: null,
|
||||
created_at: 1728980007,
|
||||
finished_at: 1728980007,
|
||||
parallel_id: null,
|
||||
parallel_start_node_id: null,
|
||||
parent_parallel_id: null,
|
||||
parent_parallel_start_node_id: null,
|
||||
},
|
||||
],
|
||||
} as unknown as WorkflowProcess
|
||||
96
web/app/components/base/chat/chat/answer/index.stories.tsx
Normal file
96
web/app/components/base/chat/chat/answer/index.stories.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import type { ChatItem } from '../../types'
|
||||
import { mockedWorkflowProcess } from './__mocks__/workflowProcess'
|
||||
import { markdownContent } from './__mocks__/markdownContent'
|
||||
import { markdownContentSVG } from './__mocks__/markdownContentSVG'
|
||||
import Answer from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Chat Answer',
|
||||
component: Answer,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
noChatInput: { control: 'boolean', description: 'If set to true, some buttons that are supposed to be shown on hover will not be displayed.' },
|
||||
responding: { control: 'boolean', description: 'Indicates if the answer is being generated.' },
|
||||
showPromptLog: { control: 'boolean', description: 'If set to true, the prompt log button will be shown on hover.' },
|
||||
},
|
||||
args: {
|
||||
noChatInput: false,
|
||||
responding: false,
|
||||
showPromptLog: false,
|
||||
},
|
||||
} satisfies Meta<typeof Answer>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const mockedBaseChatItem = {
|
||||
id: '1',
|
||||
isAnswer: true,
|
||||
content: 'Hello, how can I assist you today?',
|
||||
} satisfies ChatItem
|
||||
|
||||
export const Basic: Story = {
|
||||
args: {
|
||||
item: mockedBaseChatItem,
|
||||
question: mockedBaseChatItem.content,
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
},
|
||||
}
|
||||
|
||||
export const WithWorkflowProcess: Story = {
|
||||
args: {
|
||||
item: {
|
||||
...mockedBaseChatItem,
|
||||
workflowProcess: mockedWorkflowProcess,
|
||||
},
|
||||
question: mockedBaseChatItem.content,
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
},
|
||||
}
|
||||
|
||||
export const WithMarkdownContent: Story = {
|
||||
args: {
|
||||
item: {
|
||||
...mockedBaseChatItem,
|
||||
content: markdownContent,
|
||||
},
|
||||
question: mockedBaseChatItem.content,
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
},
|
||||
}
|
||||
|
||||
export const WithMarkdownSVG: Story = {
|
||||
args: {
|
||||
item: {
|
||||
...mockedBaseChatItem,
|
||||
content: markdownContentSVG,
|
||||
},
|
||||
question: mockedBaseChatItem.content,
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
},
|
||||
}
|
||||
33
web/app/components/base/chat/chat/question.stories.tsx
Normal file
33
web/app/components/base/chat/chat/question.stories.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import type { ChatItem } from '../types'
|
||||
import Question from './question'
|
||||
import { User } from '@/app/components/base/icons/src/public/avatar'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Chat Question',
|
||||
component: Question,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {},
|
||||
} satisfies Meta<typeof Question>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
item: {
|
||||
id: '1',
|
||||
isAnswer: false,
|
||||
content: 'You are a helpful assistant.',
|
||||
} satisfies ChatItem,
|
||||
theme: undefined,
|
||||
questionIcon: <div className='w-full h-full rounded-full border-[0.5px] border-black/5'>
|
||||
<User className='w-full h-full' />
|
||||
</div>,
|
||||
},
|
||||
}
|
||||
@ -13,16 +13,19 @@ import TextToSpeech from './text-to-speech'
|
||||
import SpeechToText from './speech-to-text'
|
||||
import Citation from './citation'
|
||||
import Moderation from './moderation'
|
||||
import type { InputVar } from '@/app/components/workflow/types'
|
||||
|
||||
export type FeaturePanelProps = {
|
||||
onChange?: OnFeaturesChange
|
||||
openingStatementProps: OpeningStatementProps
|
||||
disabled?: boolean
|
||||
workflowVariables: InputVar[]
|
||||
}
|
||||
const FeaturePanel = ({
|
||||
onChange,
|
||||
openingStatementProps,
|
||||
disabled,
|
||||
workflowVariables,
|
||||
}: FeaturePanelProps) => {
|
||||
const { t } = useTranslation()
|
||||
const features = useFeatures(s => s.features)
|
||||
@ -60,6 +63,7 @@ const FeaturePanel = ({
|
||||
{...openingStatementProps}
|
||||
onChange={onChange}
|
||||
readonly={disabled}
|
||||
workflowVariables={workflowVariables}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/conf
|
||||
import { getNewVar } from '@/utils/var'
|
||||
import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
|
||||
import type { PromptVariable } from '@/models/debug'
|
||||
import type { InputVar } from '@/app/components/workflow/types'
|
||||
|
||||
const MAX_QUESTION_NUM = 5
|
||||
|
||||
@ -32,6 +33,7 @@ export type OpeningStatementProps = {
|
||||
readonly?: boolean
|
||||
promptVariables?: PromptVariable[]
|
||||
onAutoAddPromptVariable: (variable: PromptVariable[]) => void
|
||||
workflowVariables?: InputVar[]
|
||||
}
|
||||
|
||||
// regex to match the {{}} and replace it with a span
|
||||
@ -42,6 +44,7 @@ const OpeningStatement: FC<OpeningStatementProps> = ({
|
||||
readonly,
|
||||
promptVariables = [],
|
||||
onAutoAddPromptVariable,
|
||||
workflowVariables = [],
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const featureStore = useFeaturesStore()
|
||||
@ -96,14 +99,18 @@ const OpeningStatement: FC<OpeningStatementProps> = ({
|
||||
const handleConfirm = () => {
|
||||
const keys = getInputKeys(tempValue)
|
||||
const promptKeys = promptVariables.map(item => item.key)
|
||||
const workflowVariableKeys = workflowVariables.map(item => item.variable)
|
||||
let notIncludeKeys: string[] = []
|
||||
|
||||
if (promptKeys.length === 0) {
|
||||
if (promptKeys.length === 0 && workflowVariables.length === 0) {
|
||||
if (keys.length > 0)
|
||||
notIncludeKeys = keys
|
||||
}
|
||||
else {
|
||||
notIncludeKeys = keys.filter(key => !promptKeys.includes(key))
|
||||
if (workflowVariables.length > 0)
|
||||
notIncludeKeys = keys.filter(key => !workflowVariableKeys.includes(key))
|
||||
|
||||
else notIncludeKeys = keys.filter(key => !promptKeys.includes(key))
|
||||
}
|
||||
|
||||
if (notIncludeKeys.length > 0) {
|
||||
|
||||
@ -17,6 +17,7 @@ type IPopover = {
|
||||
btnElement?: string | React.ReactNode
|
||||
btnClassName?: string | ((open: boolean) => string)
|
||||
manualClose?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const timeoutDuration = 100
|
||||
@ -30,6 +31,7 @@ export default function CustomPopover({
|
||||
className,
|
||||
btnClassName,
|
||||
manualClose,
|
||||
disabled = false,
|
||||
}: IPopover) {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const timeOutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
@ -60,6 +62,7 @@ export default function CustomPopover({
|
||||
>
|
||||
<Popover.Button
|
||||
ref={buttonRef}
|
||||
disabled={disabled}
|
||||
className={`group ${s.popupBtn} ${open ? '' : 'bg-gray-100'} ${!btnClassName
|
||||
? ''
|
||||
: typeof btnClassName === 'string'
|
||||
|
||||
@ -132,6 +132,7 @@ const StepTwo = ({
|
||||
? IndexingType.QUALIFIED
|
||||
: IndexingType.ECONOMICAL,
|
||||
)
|
||||
const [isLanguageSelectDisabled, setIsLanguageSelectDisabled] = useState(false)
|
||||
const [docForm, setDocForm] = useState<DocForm | string>(
|
||||
(datasetId && documentDetail) ? documentDetail.doc_form : DocForm.TEXT,
|
||||
)
|
||||
@ -200,9 +201,9 @@ const StepTwo = ({
|
||||
}
|
||||
}
|
||||
|
||||
const fetchFileIndexingEstimate = async (docForm = DocForm.TEXT) => {
|
||||
const fetchFileIndexingEstimate = async (docForm = DocForm.TEXT, language?: string) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams(docForm)!)
|
||||
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams(docForm, language)!)
|
||||
if (segmentationType === SegmentType.CUSTOM)
|
||||
setCustomFileIndexingEstimate(res)
|
||||
else
|
||||
@ -270,7 +271,7 @@ const StepTwo = ({
|
||||
}
|
||||
}
|
||||
|
||||
const getFileIndexingEstimateParams = (docForm: DocForm): IndexingEstimateParams | undefined => {
|
||||
const getFileIndexingEstimateParams = (docForm: DocForm, language?: string): IndexingEstimateParams | undefined => {
|
||||
if (dataSourceType === DataSourceType.FILE) {
|
||||
return {
|
||||
info_list: {
|
||||
@ -282,7 +283,7 @@ const StepTwo = ({
|
||||
indexing_technique: getIndexing_technique() as string,
|
||||
process_rule: getProcessRule(),
|
||||
doc_form: docForm,
|
||||
doc_language: docLanguage,
|
||||
doc_language: language || docLanguage,
|
||||
dataset_id: datasetId as string,
|
||||
}
|
||||
}
|
||||
@ -295,7 +296,7 @@ const StepTwo = ({
|
||||
indexing_technique: getIndexing_technique() as string,
|
||||
process_rule: getProcessRule(),
|
||||
doc_form: docForm,
|
||||
doc_language: docLanguage,
|
||||
doc_language: language || docLanguage,
|
||||
dataset_id: datasetId as string,
|
||||
}
|
||||
}
|
||||
@ -308,7 +309,7 @@ const StepTwo = ({
|
||||
indexing_technique: getIndexing_technique() as string,
|
||||
process_rule: getProcessRule(),
|
||||
doc_form: docForm,
|
||||
doc_language: docLanguage,
|
||||
doc_language: language || docLanguage,
|
||||
dataset_id: datasetId as string,
|
||||
}
|
||||
}
|
||||
@ -483,8 +484,26 @@ const StepTwo = ({
|
||||
setDocForm(DocForm.TEXT)
|
||||
}
|
||||
|
||||
const previewSwitch = async (language?: string) => {
|
||||
setPreviewSwitched(true)
|
||||
setIsLanguageSelectDisabled(true)
|
||||
if (segmentationType === SegmentType.AUTO)
|
||||
setAutomaticFileIndexingEstimate(null)
|
||||
else
|
||||
setCustomFileIndexingEstimate(null)
|
||||
try {
|
||||
await fetchFileIndexingEstimate(DocForm.QA, language)
|
||||
}
|
||||
finally {
|
||||
setIsLanguageSelectDisabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelect = (language: string) => {
|
||||
setDocLanguage(language)
|
||||
// Switch language, re-cutter
|
||||
if (docForm === DocForm.QA && previewSwitched)
|
||||
previewSwitch(language)
|
||||
}
|
||||
|
||||
const changeToEconomicalType = () => {
|
||||
@ -494,15 +513,6 @@ const StepTwo = ({
|
||||
}
|
||||
}
|
||||
|
||||
const previewSwitch = async () => {
|
||||
setPreviewSwitched(true)
|
||||
if (segmentationType === SegmentType.AUTO)
|
||||
setAutomaticFileIndexingEstimate(null)
|
||||
else
|
||||
setCustomFileIndexingEstimate(null)
|
||||
await fetchFileIndexingEstimate(DocForm.QA)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// fetch rules
|
||||
if (!isSetting) {
|
||||
@ -575,7 +585,7 @@ const StepTwo = ({
|
||||
<div ref={scrollRef} className='relative h-full w-full overflow-y-scroll'>
|
||||
<div className={cn(s.pageHeader, scrolled && s.fixed, isMobile && '!px-6')}>
|
||||
<span>{t('datasetCreation.steps.two')}</span>
|
||||
{isMobile && (
|
||||
{(isMobile || !showPreview) && (
|
||||
<Button
|
||||
className='border-[0.5px] !h-8 hover:outline hover:outline-[0.5px] hover:outline-gray-300 text-gray-700 font-medium bg-white shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)]'
|
||||
onClick={setShowPreview}
|
||||
@ -777,7 +787,7 @@ const StepTwo = ({
|
||||
<div className='mb-[2px] text-md font-medium text-gray-900'>{t('datasetCreation.stepTwo.QATitle')}</div>
|
||||
<div className='inline-flex items-center text-[13px] leading-[18px] text-gray-500'>
|
||||
<span className='pr-1'>{t('datasetCreation.stepTwo.QALanguage')}</span>
|
||||
<LanguageSelect currentLanguage={docLanguage} onSelect={handleSelect} />
|
||||
<LanguageSelect currentLanguage={docLanguage} onSelect={handleSelect} disabled={isLanguageSelectDisabled} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='shrink-0'>
|
||||
@ -948,7 +958,7 @@ const StepTwo = ({
|
||||
<div className='grow flex items-center'>
|
||||
<div>{t('datasetCreation.stepTwo.previewTitle')}</div>
|
||||
{docForm === DocForm.QA && !previewSwitched && (
|
||||
<Button className='ml-2' variant='secondary-accent' onClick={previewSwitch}>{t('datasetCreation.stepTwo.previewButton')}</Button>
|
||||
<Button className='ml-2' variant='secondary-accent' onClick={() => previewSwitch()}>{t('datasetCreation.stepTwo.previewButton')}</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
|
||||
|
||||
@ -9,16 +9,19 @@ import { languages } from '@/i18n/language'
|
||||
export type ILanguageSelectProps = {
|
||||
currentLanguage: string
|
||||
onSelect: (language: string) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const LanguageSelect: FC<ILanguageSelectProps> = ({
|
||||
currentLanguage,
|
||||
onSelect,
|
||||
disabled,
|
||||
}) => {
|
||||
return (
|
||||
<Popover
|
||||
manualClose
|
||||
trigger='click'
|
||||
disabled={disabled}
|
||||
htmlContent={
|
||||
<div className='w-full py-1'>
|
||||
{languages.filter(language => language.supported).map(({ prompt_name, name }) => (
|
||||
|
||||
@ -81,7 +81,10 @@ const ApiBasedExtensionSelector: FC<ApiBasedExtensionSelectorProps> = ({
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center text-xs text-primary-600 cursor-pointer'
|
||||
onClick={() => setShowAccountSettingModal({ payload: 'api-based-extension' })}
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
setShowAccountSettingModal({ payload: 'api-based-extension' })
|
||||
}}
|
||||
>
|
||||
{t('common.apiBasedExtension.selector.manage')}
|
||||
<ArrowUpRight className='ml-0.5 w-3 h-3' />
|
||||
@ -106,7 +109,10 @@ const ApiBasedExtensionSelector: FC<ApiBasedExtensionSelectorProps> = ({
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className='flex items-center px-3 h-8 text-sm text-primary-600 cursor-pointer'
|
||||
onClick={() => setShowApiBasedExtensionModal({ payload: {}, onSaveCallback: () => mutate() })}
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
setShowApiBasedExtensionModal({ payload: {}, onSaveCallback: () => mutate() })
|
||||
}}
|
||||
>
|
||||
<RiAddLine className='mr-2 w-4 h-4' />
|
||||
{t('common.operation.add')}
|
||||
|
||||
@ -14,12 +14,12 @@ const SwrInitor = ({
|
||||
}: SwrInitorProps) => {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const { getNewAccessToken } = useRefreshToken()
|
||||
const consoleToken = searchParams.get('access_token')
|
||||
const refreshToken = searchParams.get('refresh_token')
|
||||
const consoleTokenFromLocalStorage = localStorage?.getItem('console_token')
|
||||
const refreshTokenFromLocalStorage = localStorage?.getItem('refresh_token')
|
||||
const [init, setInit] = useState(false)
|
||||
const { getNewAccessToken } = useRefreshToken()
|
||||
|
||||
useEffect(() => {
|
||||
if (!(consoleToken || refreshToken || consoleTokenFromLocalStorage || refreshTokenFromLocalStorage)) {
|
||||
@ -27,12 +27,12 @@ const SwrInitor = ({
|
||||
return
|
||||
}
|
||||
if (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage)
|
||||
getNewAccessToken(consoleTokenFromLocalStorage, refreshTokenFromLocalStorage)
|
||||
getNewAccessToken()
|
||||
|
||||
if (consoleToken && refreshToken) {
|
||||
localStorage.setItem('console_token', consoleToken)
|
||||
localStorage.setItem('refresh_token', refreshToken)
|
||||
getNewAccessToken(consoleToken, refreshToken).then(() => {
|
||||
getNewAccessToken().then(() => {
|
||||
router.replace('/apps', { forceOptimisticNavigation: false } as any)
|
||||
}).catch(() => {
|
||||
router.replace('/signin')
|
||||
|
||||
@ -4,16 +4,21 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { useStore } from './store'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
} from './hooks'
|
||||
import { type CommonNodeType, type InputVar, InputVarType, type Node } from './types'
|
||||
import useConfig from './nodes/start/use-config'
|
||||
import type { StartNodeType } from './nodes/start/types'
|
||||
import {
|
||||
FeaturesChoose,
|
||||
FeaturesPanel,
|
||||
} from '@/app/components/base/features'
|
||||
import type { PromptVariable } from '@/models/debug'
|
||||
|
||||
const Features = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -21,6 +26,24 @@ const Features = () => {
|
||||
const setShowFeaturesPanel = useStore(s => s.setShowFeaturesPanel)
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
|
||||
const startNode = nodes.find(node => node.data.type === 'start')
|
||||
const { id, data } = startNode as Node<StartNodeType>
|
||||
const { handleAddVariable } = useConfig(id, data)
|
||||
|
||||
const handleAddOpeningStatementVariable = (variables: PromptVariable[]) => {
|
||||
const newVariable = variables[0]
|
||||
const startNodeVariable: InputVar = {
|
||||
variable: newVariable.key,
|
||||
label: newVariable.name,
|
||||
type: InputVarType.textInput,
|
||||
max_length: newVariable.max_length,
|
||||
required: newVariable.required || false,
|
||||
options: [],
|
||||
}
|
||||
handleAddVariable(startNodeVariable)
|
||||
}
|
||||
|
||||
const handleFeaturesChange = useCallback(() => {
|
||||
handleSyncWorkflowDraft()
|
||||
@ -55,8 +78,9 @@ const Features = () => {
|
||||
disabled={nodesReadOnly}
|
||||
onChange={handleFeaturesChange}
|
||||
openingStatementProps={{
|
||||
onAutoAddPromptVariable: () => {},
|
||||
onAutoAddPromptVariable: handleAddOpeningStatementVariable,
|
||||
}}
|
||||
workflowVariables={data.variables}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,25 +1,17 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useWorkflowStore } from '../store'
|
||||
import {
|
||||
BlockEnum,
|
||||
WorkflowRunningStatus,
|
||||
} from '../types'
|
||||
import type { KnowledgeRetrievalNodeType } from '../nodes/knowledge-retrieval/types'
|
||||
import type { Node } from '../types'
|
||||
import { useWorkflow } from './use-workflow'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesSyncDraft,
|
||||
useWorkflowInteractions,
|
||||
useWorkflowRun,
|
||||
} from './index'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
export const useWorkflowStartRun = () => {
|
||||
const store = useStoreApi()
|
||||
@ -28,26 +20,7 @@ export const useWorkflowStartRun = () => {
|
||||
const isChatMode = useIsChatMode()
|
||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||
const { handleRun } = useWorkflowRun()
|
||||
const { isFromStartNode } = useWorkflow()
|
||||
const { doSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { checkValid: checkKnowledgeRetrievalValid } = KnowledgeRetrievalDefault
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
modelList: rerankModelList,
|
||||
defaultModel: rerankDefaultModel,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
|
||||
|
||||
const {
|
||||
currentModel,
|
||||
} = useCurrentProviderAndModel(
|
||||
rerankModelList,
|
||||
rerankDefaultModel
|
||||
? {
|
||||
...rerankDefaultModel,
|
||||
provider: rerankDefaultModel.provider.provider,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
|
||||
const handleWorkflowStartRunInWorkflow = useCallback(async () => {
|
||||
const {
|
||||
@ -60,9 +33,6 @@ export const useWorkflowStartRun = () => {
|
||||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
const startNode = nodes.find(node => node.data.type === BlockEnum.Start)
|
||||
const knowledgeRetrievalNodes = nodes.filter((node: Node<KnowledgeRetrievalNodeType>) =>
|
||||
node.data.type === BlockEnum.KnowledgeRetrieval,
|
||||
)
|
||||
const startVariables = startNode?.data.variables || []
|
||||
const fileSettings = featuresStore!.getState().features.file
|
||||
const {
|
||||
@ -72,31 +42,6 @@ export const useWorkflowStartRun = () => {
|
||||
setShowEnvPanel,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (knowledgeRetrievalNodes.length > 0) {
|
||||
for (const node of knowledgeRetrievalNodes) {
|
||||
if (isFromStartNode(node.id)) {
|
||||
const res = checkKnowledgeRetrievalValid(node.data, t)
|
||||
if (!res.isValid || !currentModel || !rerankDefaultModel) {
|
||||
const errorMessage = res.errorMessage
|
||||
if (errorMessage) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: errorMessage,
|
||||
})
|
||||
return false
|
||||
}
|
||||
else {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('appDebug.datasetConfig.rerankModelRequired'),
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setShowEnvPanel(false)
|
||||
|
||||
if (showDebugAndPreviewPanel) {
|
||||
|
||||
@ -36,6 +36,7 @@ export const TitleInput = memo(({
|
||||
grow mr-2 px-1 h-6 text-base text-gray-900 font-semibold rounded-lg border border-transparent appearance-none outline-none
|
||||
hover:bg-gray-50
|
||||
focus:border-gray-300 focus:shadow-xs focus:bg-white caret-[#295EFF]
|
||||
min-w-0
|
||||
`}
|
||||
placeholder={t('workflow.common.addTitle') || ''}
|
||||
onBlur={handleBlur}
|
||||
|
||||
@ -23,7 +23,7 @@ import type { DataSet } from '@/models/datasets'
|
||||
import { fetchDatasets } from '@/service/datasets'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
@ -34,6 +34,8 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
const startNodeId = startNode?.id
|
||||
const { inputs, setInputs: doSetInputs } = useNodeCrud<KnowledgeRetrievalNodeType>(id, payload)
|
||||
|
||||
const inputRef = useRef(inputs)
|
||||
|
||||
const setInputs = useCallback((s: KnowledgeRetrievalNodeType) => {
|
||||
const newInputs = produce(s, (draft) => {
|
||||
if (s.retrieval_mode === RETRIEVE_TYPE.multiWay)
|
||||
@ -43,13 +45,9 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
})
|
||||
// not work in pass to draft...
|
||||
doSetInputs(newInputs)
|
||||
inputRef.current = newInputs
|
||||
}, [doSetInputs])
|
||||
|
||||
const inputRef = useRef(inputs)
|
||||
useEffect(() => {
|
||||
inputRef.current = inputs
|
||||
}, [inputs])
|
||||
|
||||
const handleQueryVarChange = useCallback((newVar: ValueSelector | string) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.query_variable_selector = newVar as ValueSelector
|
||||
@ -63,9 +61,22 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration)
|
||||
|
||||
const {
|
||||
modelList: rerankModelList,
|
||||
defaultModel: rerankDefaultModel,
|
||||
} = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
|
||||
|
||||
const {
|
||||
currentModel: currentRerankModel,
|
||||
} = useCurrentProviderAndModel(
|
||||
rerankModelList,
|
||||
rerankDefaultModel
|
||||
? {
|
||||
...rerankDefaultModel,
|
||||
provider: rerankDefaultModel.provider.provider,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
|
||||
const handleModelChanged = useCallback((model: { provider: string; modelId: string; mode?: string }) => {
|
||||
const newInputs = produce(inputRef.current, (draft) => {
|
||||
if (!draft.single_retrieval_config) {
|
||||
@ -110,7 +121,7 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
// set defaults models
|
||||
useEffect(() => {
|
||||
const inputs = inputRef.current
|
||||
if (inputs.retrieval_mode === RETRIEVE_TYPE.multiWay && inputs.multiple_retrieval_config?.reranking_model?.provider)
|
||||
if (inputs.retrieval_mode === RETRIEVE_TYPE.multiWay && inputs.multiple_retrieval_config?.reranking_model?.provider && currentRerankModel && rerankDefaultModel)
|
||||
return
|
||||
|
||||
if (inputs.retrieval_mode === RETRIEVE_TYPE.oneWay && inputs.single_retrieval_config?.model?.provider)
|
||||
@ -130,7 +141,6 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const multipleRetrievalConfig = draft.multiple_retrieval_config
|
||||
draft.multiple_retrieval_config = {
|
||||
top_k: multipleRetrievalConfig?.top_k || DATASET_DEFAULT.top_k,
|
||||
@ -138,6 +148,9 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
reranking_model: multipleRetrievalConfig?.reranking_model,
|
||||
reranking_mode: multipleRetrievalConfig?.reranking_mode,
|
||||
weights: multipleRetrievalConfig?.weights,
|
||||
reranking_enable: multipleRetrievalConfig?.reranking_enable !== undefined
|
||||
? multipleRetrievalConfig.reranking_enable
|
||||
: Boolean(currentRerankModel && rerankDefaultModel),
|
||||
}
|
||||
})
|
||||
setInputs(newInput)
|
||||
@ -194,14 +207,14 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const inputs = inputRef.current
|
||||
let query_variable_selector: ValueSelector = inputs.query_variable_selector
|
||||
if (isChatMode && inputs.query_variable_selector.length === 0 && startNodeId)
|
||||
query_variable_selector = [startNodeId, 'sys.query']
|
||||
|
||||
setInputs({
|
||||
...inputs,
|
||||
query_variable_selector,
|
||||
})
|
||||
setInputs(produce(inputs, (draft) => {
|
||||
draft.query_variable_selector = query_variable_selector
|
||||
}))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
|
||||
@ -113,7 +113,7 @@ export const getMultipleRetrievalConfig = (multipleRetrievalConfig: MultipleRetr
|
||||
reranking_mode,
|
||||
reranking_model,
|
||||
weights,
|
||||
reranking_enable: allEconomic ? reranking_enable : true,
|
||||
reranking_enable: ((allInternal && allEconomic) || allExternal) ? reranking_enable : true,
|
||||
}
|
||||
|
||||
if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel || allExternal || mixtureInternalAndExternal)
|
||||
|
||||
@ -99,7 +99,7 @@ const NormalForm = () => {
|
||||
if (res.result === 'success') {
|
||||
localStorage.setItem('console_token', res.data.access_token)
|
||||
localStorage.setItem('refresh_token', res.data.refresh_token)
|
||||
getNewAccessToken(res.data.access_token, res.data.refresh_token)
|
||||
getNewAccessToken()
|
||||
router.replace('/apps')
|
||||
}
|
||||
else {
|
||||
|
||||
@ -31,7 +31,7 @@ const UserSSOForm: FC<UserSSOFormProps> = ({
|
||||
if (refreshToken && consoleToken) {
|
||||
localStorage.setItem('console_token', consoleToken)
|
||||
localStorage.setItem('refresh_token', refreshToken)
|
||||
getNewAccessToken(consoleToken, refreshToken)
|
||||
getNewAccessToken()
|
||||
router.replace('/apps')
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,6 @@ const useRefreshToken = () => {
|
||||
const router = useRouter()
|
||||
const timer = useRef<NodeJS.Timeout>()
|
||||
const advanceTime = useRef<number>(5 * 60 * 1000)
|
||||
const interval = useRef<number>(55 * 60 * 1000)
|
||||
|
||||
const getExpireTime = useCallback((token: string) => {
|
||||
if (!token)
|
||||
@ -31,18 +30,24 @@ const useRefreshToken = () => {
|
||||
localStorage?.removeItem('is_refreshing')
|
||||
localStorage?.removeItem('console_token')
|
||||
localStorage?.removeItem('refresh_token')
|
||||
localStorage?.removeItem('last_refresh_time')
|
||||
router.replace('/signin')
|
||||
}, [])
|
||||
|
||||
const getNewAccessToken = useCallback(async (currentAccessToken: string, currentRefreshToken: string) => {
|
||||
if (localStorage?.getItem('is_refreshing') === '1')
|
||||
const getNewAccessToken = useCallback(async () => {
|
||||
const currentAccessToken = localStorage?.getItem('console_token')
|
||||
const currentRefreshToken = localStorage?.getItem('refresh_token')
|
||||
if (!currentAccessToken || !currentRefreshToken) {
|
||||
handleError()
|
||||
return new Error('No access token or refresh token found')
|
||||
}
|
||||
if (localStorage?.getItem('is_refreshing') === '1') {
|
||||
timer.current = setTimeout(() => {
|
||||
getNewAccessToken()
|
||||
}, 1000)
|
||||
return null
|
||||
}
|
||||
const currentTokenExpireTime = getExpireTime(currentAccessToken)
|
||||
let lastRefreshTime = parseInt(localStorage?.getItem('last_refresh_time') || '0')
|
||||
lastRefreshTime = isNaN(lastRefreshTime) ? 0 : lastRefreshTime
|
||||
if (getCurrentTimeStamp() + advanceTime.current > currentTokenExpireTime
|
||||
&& lastRefreshTime + interval.current < getCurrentTimeStamp()) {
|
||||
if (getCurrentTimeStamp() + advanceTime.current > currentTokenExpireTime) {
|
||||
localStorage?.setItem('is_refreshing', '1')
|
||||
const [e, res] = await fetchWithRetry(fetchNewToken({
|
||||
body: { refresh_token: currentRefreshToken },
|
||||
@ -53,24 +58,17 @@ const useRefreshToken = () => {
|
||||
}
|
||||
const { access_token, refresh_token } = res.data
|
||||
localStorage?.setItem('is_refreshing', '0')
|
||||
localStorage?.setItem('last_refresh_time', getCurrentTimeStamp().toString())
|
||||
localStorage?.setItem('console_token', access_token)
|
||||
localStorage?.setItem('refresh_token', refresh_token)
|
||||
const newTokenExpireTime = getExpireTime(access_token)
|
||||
timer.current = setTimeout(() => {
|
||||
const consoleTokenFromLocalStorage = localStorage?.getItem('console_token')
|
||||
const refreshTokenFromLocalStorage = localStorage?.getItem('refresh_token')
|
||||
if (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage)
|
||||
getNewAccessToken(consoleTokenFromLocalStorage, refreshTokenFromLocalStorage)
|
||||
getNewAccessToken()
|
||||
}, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp())
|
||||
}
|
||||
else {
|
||||
const newTokenExpireTime = getExpireTime(currentAccessToken)
|
||||
timer.current = setTimeout(() => {
|
||||
const consoleTokenFromLocalStorage = localStorage?.getItem('console_token')
|
||||
const refreshTokenFromLocalStorage = localStorage?.getItem('refresh_token')
|
||||
if (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage)
|
||||
getNewAccessToken(consoleTokenFromLocalStorage, refreshTokenFromLocalStorage)
|
||||
getNewAccessToken()
|
||||
}, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp())
|
||||
}
|
||||
return null
|
||||
@ -80,7 +78,6 @@ const useRefreshToken = () => {
|
||||
return () => {
|
||||
clearTimeout(timer.current)
|
||||
localStorage?.removeItem('is_refreshing')
|
||||
localStorage?.removeItem('last_refresh_time')
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dify-web",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
@ -18,7 +18,9 @@
|
||||
"check-i18n": "node ./i18n/check-i18n.js",
|
||||
"auto-gen-i18n": "node ./i18n/auto-gen-i18n.js",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch"
|
||||
"test:watch": "jest --watch",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.22.3",
|
||||
@ -106,8 +108,18 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.36.0",
|
||||
"@chromatic-com/storybook": "^1.9.0",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@rgrove/parse-xml": "^4.1.0",
|
||||
"@storybook/addon-essentials": "^8.3.5",
|
||||
"@storybook/addon-interactions": "^8.3.5",
|
||||
"@storybook/addon-links": "^8.3.5",
|
||||
"@storybook/addon-onboarding": "^8.3.5",
|
||||
"@storybook/addon-themes": "^8.3.5",
|
||||
"@storybook/blocks": "^8.3.5",
|
||||
"@storybook/nextjs": "^8.3.5",
|
||||
"@storybook/react": "^8.3.5",
|
||||
"@storybook/test": "^8.3.5",
|
||||
"@testing-library/dom": "^10.3.2",
|
||||
"@testing-library/jest-dom": "^6.4.6",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
@ -134,6 +146,7 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-next": "^14.0.4",
|
||||
"eslint-plugin-storybook": "^0.9.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
@ -141,6 +154,7 @@
|
||||
"magicast": "^0.3.4",
|
||||
"postcss": "^8.4.31",
|
||||
"sass": "^1.61.0",
|
||||
"storybook": "^8.3.5",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "4.9.5",
|
||||
|
||||
4519
web/yarn.lock
4519
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user