Commit 761febc7 authored by JzoNg's avatar JzoNg

Merge branch 'main' into tp

parents 4814d6ea d5ab3b50
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '0 3 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "Close due to it's no longer active, if you have any questions, you can reopen it."
stale-pr-message: "Close due to it's no longer active, if you have any questions, you can reopen it."
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'
...@@ -130,6 +130,7 @@ dmypy.json ...@@ -130,6 +130,7 @@ dmypy.json
.idea/' .idea/'
.DS_Store .DS_Store
web/.vscode/settings.json
# Intellij IDEA Files # Intellij IDEA Files
.idea/ .idea/
......
...@@ -42,7 +42,7 @@ The easiest way to start the Dify server is to run our [docker-compose.yml](dock ...@@ -42,7 +42,7 @@ The easiest way to start the Dify server is to run our [docker-compose.yml](dock
```bash ```bash
cd docker cd docker
docker-compose up -d docker compose up -d
``` ```
After running, you can access the Dify dashboard in your browser at [http://localhost/install](http://localhost/install) and start the initialization installation process. After running, you can access the Dify dashboard in your browser at [http://localhost/install](http://localhost/install) and start the initialization installation process.
......
...@@ -44,7 +44,7 @@ Dify 兼容 Langchain,这意味着我们将逐步支持多种 LLMs ,目前 ...@@ -44,7 +44,7 @@ Dify 兼容 Langchain,这意味着我们将逐步支持多种 LLMs ,目前
```bash ```bash
cd docker cd docker
docker-compose up -d docker compose up -d
``` ```
运行后,可以在浏览器上访问 [http://localhost/install](http://localhost/install) 进入 Dify 控制台并开始初始化安装操作。 运行后,可以在浏览器上访问 [http://localhost/install](http://localhost/install) 进入 Dify 控制台并开始初始化安装操作。
......
...@@ -43,7 +43,7 @@ Dify サーバーを起動する最も簡単な方法は、[docker-compose.yml]( ...@@ -43,7 +43,7 @@ Dify サーバーを起動する最も簡単な方法は、[docker-compose.yml](
```bash ```bash
cd docker cd docker
docker-compose up -d docker compose up -d
``` ```
実行後、ブラウザで [http://localhost/install](http://localhost/install) にアクセスし、初期化インストール作業を開始することができます。 実行後、ブラウザで [http://localhost/install](http://localhost/install) にアクセスし、初期化インストール作業を開始することができます。
......
...@@ -50,8 +50,8 @@ def _validate_name(name): ...@@ -50,8 +50,8 @@ def _validate_name(name):
def _validate_description_length(description): def _validate_description_length(description):
if len(description) > 200: if len(description) > 400:
raise ValueError('Description cannot exceed 200 characters.') raise ValueError('Description cannot exceed 400 characters.')
return description return description
......
...@@ -78,12 +78,14 @@ class DatasetDocumentSegmentListApi(Resource): ...@@ -78,12 +78,14 @@ class DatasetDocumentSegmentListApi(Resource):
parser.add_argument('hit_count_gte', type=int, parser.add_argument('hit_count_gte', type=int,
default=None, location='args') default=None, location='args')
parser.add_argument('enabled', type=str, default='all', location='args') parser.add_argument('enabled', type=str, default='all', location='args')
parser.add_argument('keyword', type=str, default=None, location='args')
args = parser.parse_args() args = parser.parse_args()
last_id = args['last_id'] last_id = args['last_id']
limit = min(args['limit'], 100) limit = min(args['limit'], 100)
status_list = args['status'] status_list = args['status']
hit_count_gte = args['hit_count_gte'] hit_count_gte = args['hit_count_gte']
keyword = args['keyword']
query = DocumentSegment.query.filter( query = DocumentSegment.query.filter(
DocumentSegment.document_id == str(document_id), DocumentSegment.document_id == str(document_id),
...@@ -104,6 +106,9 @@ class DatasetDocumentSegmentListApi(Resource): ...@@ -104,6 +106,9 @@ class DatasetDocumentSegmentListApi(Resource):
if hit_count_gte is not None: if hit_count_gte is not None:
query = query.filter(DocumentSegment.hit_count >= hit_count_gte) query = query.filter(DocumentSegment.hit_count >= hit_count_gte)
if keyword:
query = query.where(DocumentSegment.content.ilike(f'%{keyword}%'))
if args['enabled'].lower() != 'all': if args['enabled'].lower() != 'all':
if args['enabled'].lower() == 'true': if args['enabled'].lower() == 'true':
query = query.filter(DocumentSegment.enabled == True) query = query.filter(DocumentSegment.enabled == True)
......
import logging import logging
from langchain.chat_models.base import BaseChatModel from langchain.chat_models.base import BaseChatModel
from langchain.schema import HumanMessage from langchain.schema import HumanMessage, OutputParserException
from core.constant import llm_constant from core.constant import llm_constant
from core.llm.llm_builder import LLMBuilder from core.llm.llm_builder import LLMBuilder
...@@ -153,6 +153,8 @@ class LLMGenerator: ...@@ -153,6 +153,8 @@ class LLMGenerator:
try: try:
output = llm(query) output = llm(query)
rule_config = output_parser.parse(output) rule_config = output_parser.parse(output)
except OutputParserException:
raise ValueError('Please give a valid input for intended audience or hoping to solve problems.')
except Exception: except Exception:
logging.exception("Error generating prompt") logging.exception("Error generating prompt")
rule_config = { rule_config = {
......
"""Markdown parser.
Contains parser for md files.
"""
import re
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union, cast
from llama_index.readers.file.base_parser import BaseParser
class MarkdownParser(BaseParser):
"""Markdown parser.
Extract text from markdown files.
Returns dictionary with keys as headers and values as the text between headers.
"""
def __init__(
self,
*args: Any,
remove_hyperlinks: bool = True,
remove_images: bool = True,
**kwargs: Any,
) -> None:
"""Init params."""
super().__init__(*args, **kwargs)
self._remove_hyperlinks = remove_hyperlinks
self._remove_images = remove_images
def markdown_to_tups(self, markdown_text: str) -> List[Tuple[Optional[str], str]]:
"""Convert a markdown file to a dictionary.
The keys are the headers and the values are the text under each header.
"""
markdown_tups: List[Tuple[Optional[str], str]] = []
lines = markdown_text.split("\n")
current_header = None
current_text = ""
for line in lines:
header_match = re.match(r"^#+\s", line)
if header_match:
if current_header is not None:
markdown_tups.append((current_header, current_text))
current_header = line
current_text = ""
else:
current_text += line + "\n"
markdown_tups.append((current_header, current_text))
if current_header is not None:
# pass linting, assert keys are defined
markdown_tups = [
(re.sub(r"#", "", cast(str, key)).strip(), re.sub(r"<.*?>", "", value))
for key, value in markdown_tups
]
else:
markdown_tups = [
(key, re.sub("\n", "", value)) for key, value in markdown_tups
]
return markdown_tups
def remove_images(self, content: str) -> str:
"""Get a dictionary of a markdown file from its path."""
pattern = r"!{1}\[\[(.*)\]\]"
content = re.sub(pattern, "", content)
return content
def remove_hyperlinks(self, content: str) -> str:
"""Get a dictionary of a markdown file from its path."""
pattern = r"\[(.*?)\]\((.*?)\)"
content = re.sub(pattern, r"\1", content)
return content
def _init_parser(self) -> Dict:
"""Initialize the parser with the config."""
return {}
def parse_tups(
self, filepath: Path, errors: str = "ignore"
) -> List[Tuple[Optional[str], str]]:
"""Parse file into tuples."""
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
if self._remove_hyperlinks:
content = self.remove_hyperlinks(content)
if self._remove_images:
content = self.remove_images(content)
markdown_tups = self.markdown_to_tups(content)
return markdown_tups
def parse_file(
self, filepath: Path, errors: str = "ignore"
) -> Union[str, List[str]]:
"""Parse file into string."""
tups = self.parse_tups(filepath, errors=errors)
results = []
# TODO: don't include headers right now
for header, value in tups:
if header is None:
results.append(value)
else:
results.append(f"\n\n{header}\n{value}")
return results
...@@ -20,6 +20,7 @@ from core.data_source.notion import NotionPageReader ...@@ -20,6 +20,7 @@ from core.data_source.notion import NotionPageReader
from core.docstore.dataset_docstore import DatesetDocumentStore from core.docstore.dataset_docstore import DatesetDocumentStore
from core.index.keyword_table_index import KeywordTableIndex from core.index.keyword_table_index import KeywordTableIndex
from core.index.readers.html_parser import HTMLParser from core.index.readers.html_parser import HTMLParser
from core.index.readers.markdown_parser import MarkdownParser
from core.index.readers.pdf_parser import PDFParser from core.index.readers.pdf_parser import PDFParser
from core.index.spiltter.fixed_text_splitter import FixedRecursiveCharacterTextSplitter from core.index.spiltter.fixed_text_splitter import FixedRecursiveCharacterTextSplitter
from core.index.vector_index import VectorIndex from core.index.vector_index import VectorIndex
...@@ -327,6 +328,7 @@ class IndexingRunner: ...@@ -327,6 +328,7 @@ class IndexingRunner:
file_extractor = DEFAULT_FILE_EXTRACTOR.copy() file_extractor = DEFAULT_FILE_EXTRACTOR.copy()
file_extractor[".markdown"] = MarkdownParser() file_extractor[".markdown"] = MarkdownParser()
file_extractor[".md"] = MarkdownParser()
file_extractor[".html"] = HTMLParser() file_extractor[".html"] = HTMLParser()
file_extractor[".htm"] = HTMLParser() file_extractor[".htm"] = HTMLParser()
file_extractor[".pdf"] = PDFParser({'upload_file': upload_file}) file_extractor[".pdf"] = PDFParser({'upload_file': upload_file})
......
...@@ -13,6 +13,12 @@ def parse_json_markdown(json_string: str) -> dict: ...@@ -13,6 +13,12 @@ def parse_json_markdown(json_string: str) -> dict:
if start_index != -1 and end_index != -1: if start_index != -1 and end_index != -1:
extracted_content = json_string[start_index + len("```json"):end_index].strip() extracted_content = json_string[start_index + len("```json"):end_index].strip()
# Parse the JSON string into a Python dictionary
parsed = json.loads(extracted_content)
elif start_index != -1 and end_index == -1 and json_string.endswith("``"):
end_index = json_string.find("``", start_index + len("```json"))
extracted_content = json_string[start_index + len("```json"):end_index].strip()
# Parse the JSON string into a Python dictionary # Parse the JSON string into a Python dictionary
parsed = json.loads(extracted_content) parsed = json.loads(extracted_content)
elif json_string.startswith("{"): elif json_string.startswith("{"):
......
...@@ -43,7 +43,7 @@ def clean_document_task(document_id: str, dataset_id: str): ...@@ -43,7 +43,7 @@ def clean_document_task(document_id: str, dataset_id: str):
for segment in segments: for segment in segments:
db.session.delete(segment) db.session.delete(segment)
db.session.commit()
end_at = time.perf_counter() end_at = time.perf_counter()
logging.info( logging.info(
click.style('Cleaned document when document deleted: {} latency: {}'.format(document_id, end_at - start_at), fg='green')) click.style('Cleaned document when document deleted: {} latency: {}'.format(document_id, end_at - start_at), fg='green'))
......
...@@ -59,7 +59,7 @@ def document_indexing_update_task(dataset_id: str, document_id: str): ...@@ -59,7 +59,7 @@ def document_indexing_update_task(dataset_id: str, document_id: str):
for segment in segments: for segment in segments:
db.session.delete(segment) db.session.delete(segment)
db.session.commit()
end_at = time.perf_counter() end_at = time.perf_counter()
logging.info( logging.info(
click.style('Cleaned document when document update data source or process rule: {} latency: {}'.format(document_id, end_at - start_at), fg='green')) click.style('Cleaned document when document update data source or process rule: {} latency: {}'.format(document_id, end_at - start_at), fg='green'))
......
...@@ -10,3 +10,8 @@ NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api ...@@ -10,3 +10,8 @@ NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api
# console or api domain. # console or api domain.
# example: http://udify.app/api # example: http://udify.app/api
NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
# SENTRY
NEXT_PUBLIC_SENTRY_DSN=
NEXT_PUBLIC_SENTRY_ORG=
NEXT_PUBLIC_SENTRY_PROJECT=
\ No newline at end of file
...@@ -13,7 +13,7 @@ WORKDIR /app/web ...@@ -13,7 +13,7 @@ WORKDIR /app/web
COPY package.json /app/web/package.json COPY package.json /app/web/package.json
RUN npm install RUN npm install --only=prod
COPY . /app/web/ COPY . /app/web/
......
...@@ -23,6 +23,9 @@ The `pages/api` directory is mapped to `/api/*`. Files in this directory are tre ...@@ -23,6 +23,9 @@ The `pages/api` directory is mapped to `/api/*`. Files in this directory are tre
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Lint Code
If your ide is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting.
## Learn More ## Learn More
To learn more about Next.js, take a look at the following resources: To learn more about Next.js, take a look at the following resources:
......
'use client' 'use client'
import { FC, useRef } from 'react' import type { FC } from 'react'
import React, { useEffect, useState } from 'react' import React, { useEffect, useRef, 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'
import Header from '../components/header' import Header from '../components/header'
...@@ -50,7 +50,7 @@ const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => { ...@@ -50,7 +50,7 @@ const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => {
if (!appList || !userProfile || !langeniusVersionInfo) if (!appList || !userProfile || !langeniusVersionInfo)
return <Loading type='app' /> return <Loading type='app' />
const curApp = appList?.data.find(opt => opt.id === appId) const curAppId = segments[0] === 'app' && segments[2]
const currentDatasetId = segments[0] === 'datasets' && segments[2] const currentDatasetId = segments[0] === 'datasets' && segments[2]
const currentDataset = datasetList?.data?.find(opt => opt.id === currentDatasetId) const currentDataset = datasetList?.data?.find(opt => opt.id === currentDatasetId)
...@@ -70,12 +70,18 @@ const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => { ...@@ -70,12 +70,18 @@ const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => {
return ( return (
<SWRConfig value={{ <SWRConfig value={{
shouldRetryOnError: false shouldRetryOnError: false,
}}> }}>
<AppContextProvider value={{ apps: appList.data, mutateApps, userProfile, mutateUserProfile, pageContainerRef }}> <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 ref={pageContainerRef} className='relative flex flex-col h-full overflow-auto 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)}
curAppId={curAppId || ''}
userProfile={userProfile}
onLogout={onLogout}
langeniusVersionInfo={langeniusVersionInfo}
/>
{children} {children}
</div> </div>
</DatasetsContext.Provider> </DatasetsContext.Provider>
......
...@@ -29,7 +29,7 @@ export default function ChartView({ appId }: IChartViewProps) { ...@@ -29,7 +29,7 @@ export default function ChartView({ appId }: IChartViewProps) {
const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } }) const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } })
const onSelect = (item: Item) => { const onSelect = (item: Item) => {
setPeriod({ name: item.name, query: { start: today.subtract(item.value as number, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } }) setPeriod({ name: item.name, query: item.value === 'all' ? undefined : { start: today.subtract(item.value as number, 'day').format(queryDateFormat), end: today.format(queryDateFormat) } })
} }
if (!response) if (!response)
......
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import cn from 'classnames'
import ConfirmAddVar from './confirm-add-var'
import s from './style.module.css'
import BlockInput from '@/app/components/base/block-input' import BlockInput from '@/app/components/base/block-input'
import type { PromptVariable } from '@/models/debug' import type { PromptVariable } from '@/models/debug'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import { AppType } from '@/types/app' import { AppType } from '@/types/app'
import { getNewVar } from '@/utils/var' import { getNewVar } from '@/utils/var'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import ConfirmAddVar from './confirm-add-var'
export type IPromptProps = { export type IPromptProps = {
mode: AppType mode: AppType
promptTemplate: string promptTemplate: string
promptVariables: PromptVariable[] promptVariables: PromptVariable[]
onChange: (promp: string, promptVariables: PromptVariable[]) => void readonly?: boolean
onChange?: (promp: string, promptVariables: PromptVariable[]) => void
} }
const Prompt: FC<IPromptProps> = ({ const Prompt: FC<IPromptProps> = ({
mode, mode,
promptTemplate, promptTemplate,
promptVariables, promptVariables,
readonly = false,
onChange, onChange,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -45,23 +49,24 @@ const Prompt: FC<IPromptProps> = ({ ...@@ -45,23 +49,24 @@ const Prompt: FC<IPromptProps> = ({
showConfirmAddVar() showConfirmAddVar()
return return
} }
onChange(newTemplates, []) onChange?.(newTemplates, [])
} }
const handleAutoAdd = (isAdd: boolean) => { const handleAutoAdd = (isAdd: boolean) => {
return () => { return () => {
onChange(newTemplates, isAdd ? newPromptVariables : []) onChange?.(newTemplates, isAdd ? newPromptVariables : [])
hideConfirmAddVar() hideConfirmAddVar()
} }
} }
return ( return (
<div className='relative rounded-xl border border-[#2D0DEE] bg-gray-25'> <div className={cn(!readonly ? `${s.gradientBorder}` : 'bg-gray-50', 'relative rounded-xl')}>
<div className="flex items-center h-11 pl-3 gap-1"> <div className="flex items-center h-11 pl-3 gap-1">
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M3.00001 0.100098C3.21218 0.100098 3.41566 0.184383 3.56569 0.334412C3.71572 0.484441 3.80001 0.687924 3.80001 0.900098V1.7001H4.60001C4.81218 1.7001 5.01566 1.78438 5.16569 1.93441C5.31572 2.08444 5.40001 2.28792 5.40001 2.5001C5.40001 2.71227 5.31572 2.91575 5.16569 3.06578C5.01566 3.21581 4.81218 3.3001 4.60001 3.3001H3.80001V4.1001C3.80001 4.31227 3.71572 4.51575 3.56569 4.66578C3.41566 4.81581 3.21218 4.9001 3.00001 4.9001C2.78783 4.9001 2.58435 4.81581 2.43432 4.66578C2.28429 4.51575 2.20001 4.31227 2.20001 4.1001V3.3001H1.40001C1.18783 3.3001 0.98435 3.21581 0.834321 3.06578C0.684292 2.91575 0.600006 2.71227 0.600006 2.5001C0.600006 2.28792 0.684292 2.08444 0.834321 1.93441C0.98435 1.78438 1.18783 1.7001 1.40001 1.7001H2.20001V0.900098C2.20001 0.687924 2.28429 0.484441 2.43432 0.334412C2.58435 0.184383 2.78783 0.100098 3.00001 0.100098ZM3.00001 8.1001C3.21218 8.1001 3.41566 8.18438 3.56569 8.33441C3.71572 8.48444 3.80001 8.68792 3.80001 8.9001V9.7001H4.60001C4.81218 9.7001 5.01566 9.78438 5.16569 9.93441C5.31572 10.0844 5.40001 10.2879 5.40001 10.5001C5.40001 10.7123 5.31572 10.9158 5.16569 11.0658C5.01566 11.2158 4.81218 11.3001 4.60001 11.3001H3.80001V12.1001C3.80001 12.3123 3.71572 12.5158 3.56569 12.6658C3.41566 12.8158 3.21218 12.9001 3.00001 12.9001C2.78783 12.9001 2.58435 12.8158 2.43432 12.6658C2.28429 12.5158 2.20001 12.3123 2.20001 12.1001V11.3001H1.40001C1.18783 11.3001 0.98435 11.2158 0.834321 11.0658C0.684292 10.9158 0.600006 10.7123 0.600006 10.5001C0.600006 10.2879 0.684292 10.0844 0.834321 9.93441C0.98435 9.78438 1.18783 9.7001 1.40001 9.7001H2.20001V8.9001C2.20001 8.68792 2.28429 8.48444 2.43432 8.33441C2.58435 8.18438 2.78783 8.1001 3.00001 8.1001ZM8.60001 0.100098C8.77656 0.100041 8.94817 0.158388 9.0881 0.266047C9.22802 0.373706 9.32841 0.52463 9.37361 0.695298L10.3168 4.2601L13 5.8073C13.1216 5.87751 13.2226 5.9785 13.2928 6.10011C13.363 6.22173 13.4 6.35967 13.4 6.5001C13.4 6.64052 13.363 6.77847 13.2928 6.90008C13.2226 7.02169 13.1216 7.12268 13 7.1929L10.3168 8.7409L9.37281 12.3049C9.32753 12.4754 9.22716 12.6262 9.08732 12.7337C8.94748 12.8413 8.77602 12.8996 8.59961 12.8996C8.42319 12.8996 8.25173 12.8413 8.11189 12.7337C7.97205 12.6262 7.87169 12.4754 7.82641 12.3049L6.88321 8.7401L4.20001 7.1929C4.0784 7.12268 3.97742 7.02169 3.90721 6.90008C3.837 6.77847 3.80004 6.64052 3.80004 6.5001C3.80004 6.35967 3.837 6.22173 3.90721 6.10011C3.97742 5.9785 4.0784 5.87751 4.20001 5.8073L6.88321 4.2593L7.82721 0.695298C7.87237 0.524762 7.97263 0.373937 8.1124 0.266291C8.25216 0.158646 8.42359 0.100217 8.60001 0.100098Z" fill="#5850EC" /> <path fillRule="evenodd" clipRule="evenodd" d="M3.00001 0.100098C3.21218 0.100098 3.41566 0.184383 3.56569 0.334412C3.71572 0.484441 3.80001 0.687924 3.80001 0.900098V1.7001H4.60001C4.81218 1.7001 5.01566 1.78438 5.16569 1.93441C5.31572 2.08444 5.40001 2.28792 5.40001 2.5001C5.40001 2.71227 5.31572 2.91575 5.16569 3.06578C5.01566 3.21581 4.81218 3.3001 4.60001 3.3001H3.80001V4.1001C3.80001 4.31227 3.71572 4.51575 3.56569 4.66578C3.41566 4.81581 3.21218 4.9001 3.00001 4.9001C2.78783 4.9001 2.58435 4.81581 2.43432 4.66578C2.28429 4.51575 2.20001 4.31227 2.20001 4.1001V3.3001H1.40001C1.18783 3.3001 0.98435 3.21581 0.834321 3.06578C0.684292 2.91575 0.600006 2.71227 0.600006 2.5001C0.600006 2.28792 0.684292 2.08444 0.834321 1.93441C0.98435 1.78438 1.18783 1.7001 1.40001 1.7001H2.20001V0.900098C2.20001 0.687924 2.28429 0.484441 2.43432 0.334412C2.58435 0.184383 2.78783 0.100098 3.00001 0.100098ZM3.00001 8.1001C3.21218 8.1001 3.41566 8.18438 3.56569 8.33441C3.71572 8.48444 3.80001 8.68792 3.80001 8.9001V9.7001H4.60001C4.81218 9.7001 5.01566 9.78438 5.16569 9.93441C5.31572 10.0844 5.40001 10.2879 5.40001 10.5001C5.40001 10.7123 5.31572 10.9158 5.16569 11.0658C5.01566 11.2158 4.81218 11.3001 4.60001 11.3001H3.80001V12.1001C3.80001 12.3123 3.71572 12.5158 3.56569 12.6658C3.41566 12.8158 3.21218 12.9001 3.00001 12.9001C2.78783 12.9001 2.58435 12.8158 2.43432 12.6658C2.28429 12.5158 2.20001 12.3123 2.20001 12.1001V11.3001H1.40001C1.18783 11.3001 0.98435 11.2158 0.834321 11.0658C0.684292 10.9158 0.600006 10.7123 0.600006 10.5001C0.600006 10.2879 0.684292 10.0844 0.834321 9.93441C0.98435 9.78438 1.18783 9.7001 1.40001 9.7001H2.20001V8.9001C2.20001 8.68792 2.28429 8.48444 2.43432 8.33441C2.58435 8.18438 2.78783 8.1001 3.00001 8.1001ZM8.60001 0.100098C8.77656 0.100041 8.94817 0.158388 9.0881 0.266047C9.22802 0.373706 9.32841 0.52463 9.37361 0.695298L10.3168 4.2601L13 5.8073C13.1216 5.87751 13.2226 5.9785 13.2928 6.10011C13.363 6.22173 13.4 6.35967 13.4 6.5001C13.4 6.64052 13.363 6.77847 13.2928 6.90008C13.2226 7.02169 13.1216 7.12268 13 7.1929L10.3168 8.7409L9.37281 12.3049C9.32753 12.4754 9.22716 12.6262 9.08732 12.7337C8.94748 12.8413 8.77602 12.8996 8.59961 12.8996C8.42319 12.8996 8.25173 12.8413 8.11189 12.7337C7.97205 12.6262 7.87169 12.4754 7.82641 12.3049L6.88321 8.7401L4.20001 7.1929C4.0784 7.12268 3.97742 7.02169 3.90721 6.90008C3.837 6.77847 3.80004 6.64052 3.80004 6.5001C3.80004 6.35967 3.837 6.22173 3.90721 6.10011C3.97742 5.9785 4.0784 5.87751 4.20001 5.8073L6.88321 4.2593L7.82721 0.695298C7.87237 0.524762 7.97263 0.373937 8.1124 0.266291C8.25216 0.158646 8.42359 0.100217 8.60001 0.100098Z" fill="#5850EC" />
</svg> </svg>
<div className="h2">{mode === AppType.chat ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}</div> <div className="h2">{mode === AppType.chat ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}</div>
{!readonly && (
<Tooltip <Tooltip
htmlContent={<div className='w-[180px]'> htmlContent={<div className='w-[180px]'>
{t('appDebug.promptTip')} {t('appDebug.promptTip')}
...@@ -71,9 +76,12 @@ const Prompt: FC<IPromptProps> = ({ ...@@ -71,9 +76,12 @@ const Prompt: FC<IPromptProps> = ({
<path d="M8.66667 11.1667H8V8.5H7.33333M8 5.83333H8.00667M14 8.5C14 9.28793 13.8448 10.0681 13.5433 10.7961C13.2417 11.5241 12.7998 12.1855 12.2426 12.7426C11.6855 13.2998 11.0241 13.7417 10.2961 14.0433C9.56815 14.3448 8.78793 14.5 8 14.5C7.21207 14.5 6.43185 14.3448 5.7039 14.0433C4.97595 13.7417 4.31451 13.2998 3.75736 12.7426C3.20021 12.1855 2.75825 11.5241 2.45672 10.7961C2.15519 10.0681 2 9.28793 2 8.5C2 6.9087 2.63214 5.38258 3.75736 4.25736C4.88258 3.13214 6.4087 2.5 8 2.5C9.5913 2.5 11.1174 3.13214 12.2426 4.25736C13.3679 5.38258 14 6.9087 14 8.5Z" stroke="#9CA3AF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M8.66667 11.1667H8V8.5H7.33333M8 5.83333H8.00667M14 8.5C14 9.28793 13.8448 10.0681 13.5433 10.7961C13.2417 11.5241 12.7998 12.1855 12.2426 12.7426C11.6855 13.2998 11.0241 13.7417 10.2961 14.0433C9.56815 14.3448 8.78793 14.5 8 14.5C7.21207 14.5 6.43185 14.3448 5.7039 14.0433C4.97595 13.7417 4.31451 13.2998 3.75736 12.7426C3.20021 12.1855 2.75825 11.5241 2.45672 10.7961C2.15519 10.0681 2 9.28793 2 8.5C2 6.9087 2.63214 5.38258 3.75736 4.25736C4.88258 3.13214 6.4087 2.5 8 2.5C9.5913 2.5 11.1174 3.13214 12.2426 4.25736C13.3679 5.38258 14 6.9087 14 8.5Z" stroke="#9CA3AF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</Tooltip> </Tooltip>
)}
</div> </div>
<BlockInput <BlockInput
readonly={readonly}
value={promptTemplate} value={promptTemplate}
onConfirm={(value: string, vars: string[]) => { onConfirm={(value: string, vars: string[]) => {
handleChange(value, vars) handleChange(value, vars)
...@@ -82,7 +90,7 @@ const Prompt: FC<IPromptProps> = ({ ...@@ -82,7 +90,7 @@ const Prompt: FC<IPromptProps> = ({
{isShowConfirmAddVar && ( {isShowConfirmAddVar && (
<ConfirmAddVar <ConfirmAddVar
varNameArr={newPromptVariables.map((v) => v.name)} varNameArr={newPromptVariables.map(v => v.name)}
onConfrim={handleAutoAdd(true)} onConfrim={handleAutoAdd(true)}
onCancel={handleAutoAdd(false)} onCancel={handleAutoAdd(false)}
onHide={hideConfirmAddVar} onHide={hideConfirmAddVar}
......
.gradientBorder {
background: radial-gradient(circle at 100% 100%, #fcfcfd 0, #fcfcfd 10px, transparent 10px) 0% 0%/12px 12px no-repeat,
radial-gradient(circle at 0 100%, #fcfcfd 0, #fcfcfd 10px, transparent 10px) 100% 0%/12px 12px no-repeat,
radial-gradient(circle at 100% 0, #fcfcfd 0, #fcfcfd 10px, transparent 10px) 0% 100%/12px 12px no-repeat,
radial-gradient(circle at 0 0, #fcfcfd 0, #fcfcfd 10px, transparent 10px) 100% 100%/12px 12px no-repeat,
linear-gradient(#fcfcfd, #fcfcfd) 50% 50%/calc(100% - 4px) calc(100% - 24px) no-repeat,
linear-gradient(#fcfcfd, #fcfcfd) 50% 50%/calc(100% - 24px) calc(100% - 4px) no-repeat,
radial-gradient(at 100% 100%, rgba(45,13,238,0.8) 0%, transparent 70%),
radial-gradient(at 100% 0%, rgba(45,13,238,0.8) 0%, transparent 70%),
radial-gradient(at 0% 0%, rgba(42,135,245,0.8) 0%, transparent 70%),
radial-gradient(at 0% 100%, rgba(42,135,245,0.8) 0%, transparent 70%);
border-radius: 12px;
padding: 2px;
box-sizing: border-box;
}
\ No newline at end of file
...@@ -2,29 +2,28 @@ ...@@ -2,29 +2,28 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Panel from '../base/feature-panel'
import Tooltip from '@/app/components/base/tooltip'
import type { PromptVariable } from '@/models/debug'
import { Cog8ToothIcon, TrashIcon } from '@heroicons/react/24/outline' import { Cog8ToothIcon, TrashIcon } from '@heroicons/react/24/outline'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import Panel from '../base/feature-panel'
import OperationBtn from '../base/operation-btn'
import VarIcon from '../base/icons/var-icon'
import EditModel from './config-model' import EditModel from './config-model'
import IconTypeIcon from './input-type-icon'
import s from './style.module.css'
import Tooltip from '@/app/components/base/tooltip'
import type { PromptVariable } from '@/models/debug'
import { DEFAULT_VALUE_MAX_LEN, getMaxVarNameLength } from '@/config' import { DEFAULT_VALUE_MAX_LEN, getMaxVarNameLength } from '@/config'
import { getNewVar } from '@/utils/var' import { checkKeys, getNewVar } from '@/utils/var'
import OperationBtn from '../base/operation-btn'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import IconTypeIcon from './input-type-icon'
import { checkKeys } from '@/utils/var'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import s from './style.module.css'
import VarIcon from '../base/icons/var-icon'
export type IConfigVarProps = { export type IConfigVarProps = {
promptVariables: PromptVariable[] promptVariables: PromptVariable[]
onPromptVariablesChange: (promptVariables: PromptVariable[]) => void readonly?: boolean
onPromptVariablesChange?: (promptVariables: PromptVariable[]) => void
} }
const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChange }) => { const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVariablesChange }) => {
const { t } = useTranslation() const { t } = useTranslation()
const hasVar = promptVariables.length > 0 const hasVar = promptVariables.length > 0
const promptVariableObj = (() => { const promptVariableObj = (() => {
...@@ -39,16 +38,17 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -39,16 +38,17 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
if (!(key in promptVariableObj)) if (!(key in promptVariableObj))
return return
const newPromptVariables = promptVariables.map((item) => { const newPromptVariables = promptVariables.map((item) => {
if (item.key === key) if (item.key === key) {
return { return {
...item, ...item,
[updateKey]: newValue [updateKey]: newValue,
}
} }
return item return item
}) })
onPromptVariablesChange(newPromptVariables) onPromptVariablesChange?.(newPromptVariables)
} }
const batchUpdatePromptVariable = (key: string, updateKeys: string[], newValues: any[]) => { const batchUpdatePromptVariable = (key: string, updateKeys: string[], newValues: any[]) => {
...@@ -66,53 +66,55 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -66,53 +66,55 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
return item return item
}) })
onPromptVariablesChange(newPromptVariables) onPromptVariablesChange?.(newPromptVariables)
} }
const updatePromptKey = (index: number, newKey: string) => { const updatePromptKey = (index: number, newKey: string) => {
const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true)
if (!isValid) { if (!isValid) {
Toast.notify({ Toast.notify({
type: 'error', type: 'error',
message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }) message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }),
}) })
return return
} }
const newPromptVariables = promptVariables.map((item, i) => { const newPromptVariables = promptVariables.map((item, i) => {
if (i === index) if (i === index) {
return { return {
...item, ...item,
key: newKey, key: newKey,
} }
}
return item return item
}) })
onPromptVariablesChange(newPromptVariables) onPromptVariablesChange?.(newPromptVariables)
} }
const updatePromptNameIfNameEmpty = (index: number, newKey: string) => { const updatePromptNameIfNameEmpty = (index: number, newKey: string) => {
if (!newKey) return if (!newKey)
return
const newPromptVariables = promptVariables.map((item, i) => { const newPromptVariables = promptVariables.map((item, i) => {
if (i === index && !item.name) if (i === index && !item.name) {
return { return {
...item, ...item,
name: newKey, name: newKey,
} }
}
return item return item
}) })
onPromptVariablesChange(newPromptVariables) onPromptVariablesChange?.(newPromptVariables)
} }
const handleAddVar = () => { const handleAddVar = () => {
const newVar = getNewVar('') const newVar = getNewVar('')
onPromptVariablesChange([...promptVariables, newVar]) onPromptVariablesChange?.([...promptVariables, newVar])
} }
const handleRemoveVar = (index: number) => { const handleRemoveVar = (index: number) => {
onPromptVariablesChange(promptVariables.filter((_, i) => i !== index)) onPromptVariablesChange?.(promptVariables.filter((_, i) => i !== index))
} }
const [currKey, setCurrKey] = useState<string | null>(null) const [currKey, setCurrKey] = useState<string | null>(null)
...@@ -132,6 +134,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -132,6 +134,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
title={ title={
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<div>{t('appDebug.variableTitle')}</div> <div>{t('appDebug.variableTitle')}</div>
{!readonly && (
<Tooltip htmlContent={<div className='w-[180px]'> <Tooltip htmlContent={<div className='w-[180px]'>
{t('appDebug.variableTip')} {t('appDebug.variableTip')}
</div>} selector='config-var-tooltip'> </div>} selector='config-var-tooltip'>
...@@ -139,9 +142,10 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -139,9 +142,10 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
<path d="M8.66667 11.1667H8V8.5H7.33333M8 5.83333H8.00667M14 8.5C14 9.28793 13.8448 10.0681 13.5433 10.7961C13.2417 11.5241 12.7998 12.1855 12.2426 12.7426C11.6855 13.2998 11.0241 13.7417 10.2961 14.0433C9.56815 14.3448 8.78793 14.5 8 14.5C7.21207 14.5 6.43185 14.3448 5.7039 14.0433C4.97595 13.7417 4.31451 13.2998 3.75736 12.7426C3.20021 12.1855 2.75825 11.5241 2.45672 10.7961C2.15519 10.0681 2 9.28793 2 8.5C2 6.9087 2.63214 5.38258 3.75736 4.25736C4.88258 3.13214 6.4087 2.5 8 2.5C9.5913 2.5 11.1174 3.13214 12.2426 4.25736C13.3679 5.38258 14 6.9087 14 8.5Z" stroke="#9CA3AF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> <path d="M8.66667 11.1667H8V8.5H7.33333M8 5.83333H8.00667M14 8.5C14 9.28793 13.8448 10.0681 13.5433 10.7961C13.2417 11.5241 12.7998 12.1855 12.2426 12.7426C11.6855 13.2998 11.0241 13.7417 10.2961 14.0433C9.56815 14.3448 8.78793 14.5 8 14.5C7.21207 14.5 6.43185 14.3448 5.7039 14.0433C4.97595 13.7417 4.31451 13.2998 3.75736 12.7426C3.20021 12.1855 2.75825 11.5241 2.45672 10.7961C2.15519 10.0681 2 9.28793 2 8.5C2 6.9087 2.63214 5.38258 3.75736 4.25736C4.88258 3.13214 6.4087 2.5 8 2.5C9.5913 2.5 11.1174 3.13214 12.2426 4.25736C13.3679 5.38258 14 6.9087 14 8.5Z" stroke="#9CA3AF" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
</Tooltip> </Tooltip>
)}
</div> </div>
} }
headerRight={<OperationBtn type="add" onClick={handleAddVar} />} headerRight={!readonly ? <OperationBtn type="add" onClick={handleAddVar} /> : null}
> >
{!hasVar && ( {!hasVar && (
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.notSetVar')}</div> <div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.notSetVar')}</div>
...@@ -153,8 +157,13 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -153,8 +157,13 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
<tr className='uppercase'> <tr className='uppercase'>
<td>{t('appDebug.variableTable.key')}</td> <td>{t('appDebug.variableTable.key')}</td>
<td>{t('appDebug.variableTable.name')}</td> <td>{t('appDebug.variableTable.name')}</td>
{!readonly && (
<>
<td>{t('appDebug.variableTable.optional')}</td> <td>{t('appDebug.variableTable.optional')}</td>
<td>{t('appDebug.variableTable.action')}</td> <td>{t('appDebug.variableTable.action')}</td>
</>
)}
</tr> </tr>
</thead> </thead>
<tbody className="text-gray-700"> <tbody className="text-gray-700">
...@@ -163,6 +172,8 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -163,6 +172,8 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
<td className="w-[160px] border-b border-gray-100 pl-3"> <td className="w-[160px] border-b border-gray-100 pl-3">
<div className='flex items-center space-x-1'> <div className='flex items-center space-x-1'>
<IconTypeIcon type={type} /> <IconTypeIcon type={type} />
{!readonly
? (
<input <input
type="text" type="text"
placeholder="key" placeholder="key"
...@@ -172,9 +183,15 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -172,9 +183,15 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
maxLength={getMaxVarNameLength(name)} maxLength={getMaxVarNameLength(name)}
className="h-6 leading-6 block w-full rounded-md border-0 py-1.5 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" className="h-6 leading-6 block w-full rounded-md border-0 py-1.5 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
/> />
)
: (
<div className='h-6 leading-6 text-[13px] text-gray-700'>{key}</div>
)}
</div> </div>
</td> </td>
<td className="py-1 border-b border-gray-100"> <td className="py-1 border-b border-gray-100">
{!readonly
? (
<input <input
type="text" type="text"
placeholder={key} placeholder={key}
...@@ -182,11 +199,16 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -182,11 +199,16 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
onChange={e => updatePromptVariable(key, 'name', e.target.value)} onChange={e => updatePromptVariable(key, 'name', e.target.value)}
maxLength={getMaxVarNameLength(name)} maxLength={getMaxVarNameLength(name)}
className="h-6 leading-6 block w-full rounded-md border-0 py-1.5 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" className="h-6 leading-6 block w-full rounded-md border-0 py-1.5 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
/> />)
: (
<div className='h-6 leading-6 text-[13px] text-gray-700'>{name}</div>
)}
</td> </td>
{!readonly && (
<>
<td className='w-[84px] border-b border-gray-100'> <td className='w-[84px] border-b border-gray-100'>
<div className='flex items-center h-full'> <div className='flex items-center h-full'>
<Switch defaultValue={!required} size='md' onChange={(value) => updatePromptVariable(key, 'required', !value)} /> <Switch defaultValue={!required} size='md' onChange={value => updatePromptVariable(key, 'required', !value)} />
</div> </div>
</td> </td>
<td className='w-20 border-b border-gray-100'> <td className='w-20 border-b border-gray-100'>
...@@ -199,6 +221,8 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -199,6 +221,8 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
</div> </div>
</div> </div>
</td> </td>
</>
)}
</tr> </tr>
))} ))}
</tbody> </tbody>
...@@ -212,11 +236,12 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan ...@@ -212,11 +236,12 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, onPromptVariablesChan
isShow={isShowEditModal} isShow={isShowEditModal}
onClose={hideEditModal} onClose={hideEditModal}
onConfirm={({ type, value }) => { onConfirm={({ type, value }) => {
if (type === 'string') { if (type === 'string')
batchUpdatePromptVariable(currKey as string, ['type', 'max_length'], [type, value || DEFAULT_VALUE_MAX_LEN]) batchUpdatePromptVariable(currKey as string, ['type', 'max_length'], [type, value || DEFAULT_VALUE_MAX_LEN])
} else {
else
batchUpdatePromptVariable(currKey as string, ['type', 'options'], [type, value || []]) batchUpdatePromptVariable(currKey as string, ['type', 'options'], [type, value || []])
}
hideEditModal() hideEditModal()
}} }}
/> />
......
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
export type IAutomaticBtnProps = {
onClick: () => void
}
const leftIcon = (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.31346 0.905711C4.21464 0.708087 4.01266 0.583252 3.79171 0.583252C3.57076 0.583252 3.36877 0.708087 3.26996 0.905711L2.81236 1.82091C2.64757 2.15048 2.59736 2.24532 2.53635 2.32447C2.47515 2.40386 2.40398 2.47503 2.32459 2.53623C2.24544 2.59724 2.1506 2.64745 1.82103 2.81224L0.905833 3.26984C0.708209 3.36865 0.583374 3.57064 0.583374 3.79159C0.583374 4.01254 0.708209 4.21452 0.905833 4.31333L1.82103 4.77094C2.1506 4.93572 2.24544 4.98593 2.32459 5.04694C2.40398 5.10814 2.47515 5.17931 2.53635 5.2587C2.59736 5.33785 2.64758 5.43269 2.81236 5.76226L3.26996 6.67746C3.36877 6.87508 3.57076 6.99992 3.79171 6.99992C4.01266 6.99992 4.21465 6.87508 4.31346 6.67746L4.77106 5.76226C4.93584 5.43269 4.98605 5.33786 5.04707 5.2587C5.10826 5.17931 5.17943 5.10814 5.25883 5.04694C5.33798 4.98593 5.43282 4.93572 5.76238 4.77094L6.67758 4.31333C6.87521 4.21452 7.00004 4.01254 7.00004 3.79159C7.00004 3.57064 6.87521 3.36865 6.67758 3.26984L5.76238 2.81224C5.43282 2.64745 5.33798 2.59724 5.25883 2.53623C5.17943 2.47503 5.10826 2.40386 5.04707 2.32447C4.98605 2.24532 4.93584 2.15048 4.77106 1.82091L4.31346 0.905711Z" fill="#444CE7"/>
<path d="M11.375 1.74992C11.375 1.42775 11.1139 1.16659 10.7917 1.16659C10.4695 1.16659 10.2084 1.42775 10.2084 1.74992V2.62492H9.33337C9.01121 2.62492 8.75004 2.88609 8.75004 3.20825C8.75004 3.53042 9.01121 3.79159 9.33337 3.79159H10.2084V4.66659C10.2084 4.98875 10.4695 5.24992 10.7917 5.24992C11.1139 5.24992 11.375 4.98875 11.375 4.66659V3.79159H12.25C12.5722 3.79159 12.8334 3.53042 12.8334 3.20825C12.8334 2.88609 12.5722 2.62492 12.25 2.62492H11.375V1.74992Z" fill="#444CE7"/>
<path d="M3.79171 9.33325C3.79171 9.01109 3.53054 8.74992 3.20837 8.74992C2.88621 8.74992 2.62504 9.01109 2.62504 9.33325V10.2083H1.75004C1.42787 10.2083 1.16671 10.4694 1.16671 10.7916C1.16671 11.1138 1.42787 11.3749 1.75004 11.3749H2.62504V12.2499C2.62504 12.5721 2.88621 12.8333 3.20837 12.8333C3.53054 12.8333 3.79171 12.5721 3.79171 12.2499V11.3749H4.66671C4.98887 11.3749 5.25004 11.1138 5.25004 10.7916C5.25004 10.4694 4.98887 10.2083 4.66671 10.2083H3.79171V9.33325Z" fill="#444CE7"/>
<path d="M10.4385 6.73904C10.3396 6.54142 10.1377 6.41659 9.91671 6.41659C9.69576 6.41659 9.49377 6.54142 9.39496 6.73904L8.84014 7.84869C8.67535 8.17826 8.62514 8.27309 8.56413 8.35225C8.50293 8.43164 8.43176 8.50281 8.35237 8.56401C8.27322 8.62502 8.17838 8.67523 7.84881 8.84001L6.73917 9.39484C6.54154 9.49365 6.41671 9.69564 6.41671 9.91659C6.41671 10.1375 6.54154 10.3395 6.73917 10.4383L7.84881 10.9932C8.17838 11.1579 8.27322 11.2082 8.35237 11.2692C8.43176 11.3304 8.50293 11.4015 8.56413 11.4809C8.62514 11.5601 8.67535 11.6549 8.84014 11.9845L9.39496 13.0941C9.49377 13.2918 9.69576 13.4166 9.91671 13.4166C10.1377 13.4166 10.3396 13.2918 10.4385 13.0941L10.9933 11.9845C11.1581 11.6549 11.2083 11.5601 11.2693 11.4809C11.3305 11.4015 11.4017 11.3304 11.481 11.2692C11.5602 11.2082 11.655 11.1579 11.9846 10.9932L13.0942 10.4383C13.2919 10.3395 13.4167 10.1375 13.4167 9.91659C13.4167 9.69564 13.2919 9.49365 13.0942 9.39484L11.9846 8.84001C11.655 8.67523 11.5602 8.62502 11.481 8.56401C11.4017 8.50281 11.3305 8.43164 11.2693 8.35225C11.2083 8.27309 11.1581 8.17826 10.9933 7.84869L10.4385 6.73904Z" fill="#444CE7"/>
</svg>
)
const AutomaticBtn: FC<IAutomaticBtnProps> = ({
onClick,
}) => {
const { t } = useTranslation()
return (
<Button className='flex space-x-2 items-center !h-8'
onClick={onClick}
>
{leftIcon}
<span className='text-xs font-semibold text-primary-600 uppercase'>{t('appDebug.operation.automatic')}</span>
</Button>
)
}
export default React.memo(AutomaticBtn)
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Toast from '@/app/components/base/toast'
import { generateRule } from '@/service/debug'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import { AppType } from '@/types/app'
import ConfigVar from '@/app/components/app/configuration/config-var'
import OpeningStatement from '@/app/components/app/configuration/features/chat-group/opening-statement'
import GroupName from '@/app/components/app/configuration/base/group-name'
import Loading from '@/app/components/base/loading'
import Confirm from '@/app/components/base/confirm'
const noDataIcon = (
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.4998 51.3333V39.6666M10.4998 16.3333V4.66663M4.6665 10.5H16.3332M4.6665 45.5H16.3332M30.3332 6.99996L26.2868 17.5206C25.6287 19.2315 25.2997 20.0869 24.7881 20.8065C24.3346 21.4442 23.7774 22.0014 23.1397 22.4549C22.4202 22.9665 21.5647 23.2955 19.8538 23.9535L9.33317 28L19.8539 32.0464C21.5647 32.7044 22.4202 33.0334 23.1397 33.5451C23.7774 33.9985 24.3346 34.5557 24.7881 35.1934C25.2997 35.913 25.6287 36.7684 26.2868 38.4793L30.3332 49L34.3796 38.4793C35.0376 36.7684 35.3666 35.913 35.8783 35.1934C36.3317 34.5557 36.8889 33.9985 37.5266 33.5451C38.2462 33.0334 39.1016 32.7044 40.8125 32.0464L51.3332 28L40.8125 23.9535C39.1016 23.2955 38.2462 22.9665 37.5266 22.4549C36.8889 22.0014 36.3317 21.4442 35.8783 20.8065C35.3666 20.0869 35.0376 19.2315 34.3796 17.5206L30.3332 6.99996Z" stroke="#EAECF0" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)
export type AutomaticRes = {
prompt: string
variables: string[]
opening_statement: string
}
export type IGetAutomaticResProps = {
mode: AppType
isShow: boolean
onClose: () => void
onFinished: (res: AutomaticRes) => void
}
const genIcon = (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.6665 1.33332C3.6665 0.965133 3.36803 0.666656 2.99984 0.666656C2.63165 0.666656 2.33317 0.965133 2.33317 1.33332V2.33332H1.33317C0.964981 2.33332 0.666504 2.6318 0.666504 2.99999C0.666504 3.36818 0.964981 3.66666 1.33317 3.66666H2.33317V4.66666C2.33317 5.03485 2.63165 5.33332 2.99984 5.33332C3.36803 5.33332 3.6665 5.03485 3.6665 4.66666V3.66666H4.6665C5.03469 3.66666 5.33317 3.36818 5.33317 2.99999C5.33317 2.6318 5.03469 2.33332 4.6665 2.33332H3.6665V1.33332Z" fill="white"/>
<path d="M3.6665 11.3333C3.6665 10.9651 3.36803 10.6667 2.99984 10.6667C2.63165 10.6667 2.33317 10.9651 2.33317 11.3333V12.3333H1.33317C0.964981 12.3333 0.666504 12.6318 0.666504 13C0.666504 13.3682 0.964981 13.6667 1.33317 13.6667H2.33317V14.6667C2.33317 15.0348 2.63165 15.3333 2.99984 15.3333C3.36803 15.3333 3.6665 15.0348 3.6665 14.6667V13.6667H4.6665C5.03469 13.6667 5.33317 13.3682 5.33317 13C5.33317 12.6318 5.03469 12.3333 4.6665 12.3333H3.6665V11.3333Z" fill="white"/>
<path d="M9.28873 1.76067C9.18971 1.50321 8.94235 1.33332 8.6665 1.33332C8.39066 1.33332 8.1433 1.50321 8.04427 1.76067L6.88815 4.76658C6.68789 5.28727 6.62495 5.43732 6.53887 5.55838C6.4525 5.67986 6.34637 5.78599 6.2249 5.87236C6.10384 5.95844 5.95379 6.02137 5.43309 6.22164L2.42718 7.37776C2.16972 7.47678 1.99984 7.72414 1.99984 7.99999C1.99984 8.27584 2.16972 8.5232 2.42718 8.62222L5.43309 9.77834C5.95379 9.97861 6.10384 10.0415 6.2249 10.1276C6.34637 10.214 6.4525 10.3201 6.53887 10.4416C6.62495 10.5627 6.68789 10.7127 6.88816 11.2334L8.04427 14.2393C8.1433 14.4968 8.39066 14.6667 8.6665 14.6667C8.94235 14.6667 9.18971 14.4968 9.28873 14.2393L10.4449 11.2334C10.6451 10.7127 10.7081 10.5627 10.7941 10.4416C10.8805 10.3201 10.9866 10.214 11.1081 10.1276C11.2292 10.0415 11.3792 9.97861 11.8999 9.77834L14.9058 8.62222C15.1633 8.5232 15.3332 8.27584 15.3332 7.99999C15.3332 7.72414 15.1633 7.47678 14.9058 7.37776L11.8999 6.22164C11.3792 6.02137 11.2292 5.95844 11.1081 5.87236C10.9866 5.78599 10.8805 5.67986 10.7941 5.55838C10.7081 5.43732 10.6451 5.28727 10.4449 4.76658L9.28873 1.76067Z" fill="white"/>
</svg>
)
const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
mode,
isShow,
onClose,
// appId,
onFinished,
}) => {
const { t } = useTranslation()
const [audiences, setAudiences] = React.useState<string>('')
const [hopingToSolve, setHopingToSolve] = React.useState<string>('')
const isValid = () => {
if (audiences.trim() === '') {
Toast.notify({
type: 'error',
message: t('appDebug.automatic.audiencesRequired'),
})
return false
}
if (hopingToSolve.trim() === '') {
Toast.notify({
type: 'error',
message: t('appDebug.automatic.problemRequired'),
})
return false
}
return true
}
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
const [res, setRes] = React.useState<AutomaticRes | null>(null)
const renderLoading = (
<div className='grow flex flex-col items-center justify-center h-full space-y-3'>
<Loading />
<div className='text-[13px] text-gray-400'>{t('appDebug.automatic.loading')}</div>
</div>
)
const renderNoData = (
<div className='grow flex flex-col items-center justify-center h-full space-y-3'>
{noDataIcon}
<div className='text-[13px] text-gray-400'>{t('appDebug.automatic.noData')}</div>
</div>
)
const onGenerate = async () => {
if (!isValid())
return
if (isLoading)
return
setLoadingTrue()
try {
const res = await generateRule({
audiences,
hoping_to_solve: hopingToSolve,
})
setRes(res as AutomaticRes)
}
finally {
setLoadingFalse()
}
}
const [showConfirmOverwrite, setShowConfirmOverwrite] = React.useState(false)
return (
<Modal
isShow={isShow}
onClose={onClose}
className='min-w-[1120px] !p-0'
closable
>
<div className='flex h-[680px]'>
<div className='w-[480px] shrink-0 px-8 py-6 h-full overflow-y-auto border-r border-gray-100'>
<div>
<div className='mb-1 text-xl font-semibold text-primary-600'>{t('appDebug.automatic.title')}</div>
<div className='text-[13px] font-normal text-gray-500'>{t('appDebug.automatic.description')}</div>
</div>
{/* inputs */}
<div className='mt-12 space-y-5'>
<div className='space-y-2'>
<div className='text-[13px] font-medium text-gray-900'>{t('appDebug.automatic.intendedAudience')}</div>
<input className="w-full h-8 px-3 text-[13px] font-normal bg-gray-50 rounded-lg" placeholder={t('appDebug.automatic.intendedAudiencePlaceHolder') as string} value={audiences} onChange={e => setAudiences(e.target.value)} />
</div>
<div className='space-y-2'>
<div className='text-[13px] font-medium text-gray-900'>{t('appDebug.automatic.solveProblem')}</div>
<textarea className="w-full h-[200px] overflow-y-auto p-3 text-[13px] font-normal bg-gray-50 rounded-lg" placeholder={t('appDebug.automatic.solveProblemPlaceHolder') as string} value={hopingToSolve} onChange={e => setHopingToSolve(e.target.value)} />
</div>
<div className='mt-6 flex justify-end'>
<Button
className='flex space-x-2 items-center !h-8'
type='primary'
onClick={onGenerate}
disabled={isLoading}
>
{genIcon}
<span className='text-xs font-semibold text-white uppercase'>{t('appDebug.automatic.generate')}</span>
</Button>
</div>
</div>
</div>
{(!isLoading && res) && (
<div className='grow px-8 pt-6 h-full overflow-y-auto'>
<div className='mb-4 w-1/2 text-lg font-medium text-gray-900'>{t('appDebug.automatic.resTitle')}</div>
<ConfigPrompt
mode={mode}
promptTemplate={res?.prompt || ''}
promptVariables={[]}
readonly
/>
{(res?.variables?.length && res?.variables?.length > 0)
? (
<ConfigVar
promptVariables={res?.variables.map(key => ({ key, name: key, type: 'string', required: true })) || []}
readonly
/>
)
: ''}
{(mode === AppType.chat && res?.opening_statement) && (
<div className='mt-7'>
<GroupName name={t('appDebug.feature.groupChat.title')} />
<OpeningStatement
value={res?.opening_statement || ''}
readonly
/>
</div>
)}
<div className='sticky bottom-0 flex justify-end right-0 py-4'>
<Button onClick={onClose}>{t('common.operation.cancel')}</Button>
<Button type='primary' className='ml-2' onClick={() => {
setShowConfirmOverwrite(true)
}}>{t('appDebug.automatic.apply')}</Button>
</div>
</div>
)}
{isLoading && renderLoading}
{(!isLoading && !res) && renderNoData}
{showConfirmOverwrite && (
<Confirm
title={t('appDebug.automatic.overwriteTitle')}
content={t('appDebug.automatic.overwriteMessage')}
isShow={showConfirmOverwrite}
onClose={() => setShowConfirmOverwrite(false)}
onConfirm={() => {
setShowConfirmOverwrite(false)
onFinished(res as AutomaticRes)
}}
onCancel={() => setShowConfirmOverwrite(false)}
/>
)}
</div>
</Modal>
)
}
export default React.memo(GetAutomaticRes)
...@@ -3,19 +3,22 @@ import type { FC } from 'react' ...@@ -3,19 +3,22 @@ import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import produce from 'immer' import produce from 'immer'
import AddFeatureBtn from './feature/add-feature-btn' import { useBoolean } from 'ahooks'
import ChooseFeature from './feature/choose-feature'
import useFeature from './feature/use-feature'
import ConfigContext from '@/context/debug-configuration'
import DatasetConfig from '../dataset-config' import DatasetConfig from '../dataset-config'
import ChatGroup from '../features/chat-group' import ChatGroup from '../features/chat-group'
import ExperienceEnchanceGroup from '../features/experience-enchance-group' import ExperienceEnchanceGroup from '../features/experience-enchance-group'
import Toolbox from '../toolbox' import Toolbox from '../toolbox'
import AddFeatureBtn from './feature/add-feature-btn'
import AutomaticBtn from './automatic/automatic-btn'
import type { AutomaticRes } from './automatic/get-automatic-res'
import GetAutomaticResModal from './automatic/get-automatic-res'
import ChooseFeature from './feature/choose-feature'
import useFeature from './feature/use-feature'
import ConfigContext from '@/context/debug-configuration'
import ConfigPrompt from '@/app/components/app/configuration/config-prompt' import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import ConfigVar from '@/app/components/app/configuration/config-var' import ConfigVar from '@/app/components/app/configuration/config-var'
import type { PromptVariable } from '@/models/debug' import type { PromptVariable } from '@/models/debug'
import { AppType } from '@/types/app' import { AppType } from '@/types/app'
import { useBoolean } from 'ahooks'
const Config: FC = () => { const Config: FC = () => {
const { const {
...@@ -26,10 +29,10 @@ const Config: FC = () => { ...@@ -26,10 +29,10 @@ const Config: FC = () => {
setModelConfig, setModelConfig,
setPrevPromptConfig, setPrevPromptConfig,
setFormattingChanged, setFormattingChanged,
moreLikeThisConifg, moreLikeThisConfig,
setMoreLikeThisConifg, setMoreLikeThisConfig,
suggestedQuestionsAfterAnswerConfig, suggestedQuestionsAfterAnswerConfig,
setSuggestedQuestionsAfterAnswerConfig setSuggestedQuestionsAfterAnswerConfig,
} = useContext(ConfigContext) } = useContext(ConfigContext)
const isChatApp = mode === AppType.chat const isChatApp = mode === AppType.chat
...@@ -41,9 +44,8 @@ const Config: FC = () => { ...@@ -41,9 +44,8 @@ const Config: FC = () => {
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newVariables] draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newVariables]
}) })
if (modelConfig.configs.prompt_template !== newTemplate) { if (modelConfig.configs.prompt_template !== newTemplate)
setFormattingChanged(true) setFormattingChanged(true)
}
setPrevPromptConfig(modelConfig.configs) setPrevPromptConfig(modelConfig.configs)
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
...@@ -59,14 +61,14 @@ const Config: FC = () => { ...@@ -59,14 +61,14 @@ const Config: FC = () => {
const [showChooseFeature, { const [showChooseFeature, {
setTrue: showChooseFeatureTrue, setTrue: showChooseFeatureTrue,
setFalse: showChooseFeatureFalse setFalse: showChooseFeatureFalse,
}] = useBoolean(false) }] = useBoolean(false)
const { featureConfig, handleFeatureChange } = useFeature({ const { featureConfig, handleFeatureChange } = useFeature({
introduction, introduction,
setIntroduction, setIntroduction,
moreLikeThis: moreLikeThisConifg.enabled, moreLikeThis: moreLikeThisConfig.enabled,
setMoreLikeThis: (value) => { setMoreLikeThis: (value) => {
setMoreLikeThisConifg(produce(moreLikeThisConifg, (draft) => { setMoreLikeThisConfig(produce(moreLikeThisConfig, (draft) => {
draft.enabled = value draft.enabled = value
})) }))
}, },
...@@ -81,14 +83,24 @@ const Config: FC = () => { ...@@ -81,14 +83,24 @@ const Config: FC = () => {
const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer) const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer)
const hasToolbox = false const hasToolbox = false
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
const handleAutomaticRes = (res: AutomaticRes) => {
const newModelConfig = produce(modelConfig, (draft) => {
draft.configs.prompt_template = res.prompt
draft.configs.prompt_variables = res.variables.map(key => ({ key, name: key, type: 'string', required: true }))
})
setModelConfig(newModelConfig)
setPrevPromptConfig(modelConfig.configs)
if (mode === AppType.chat)
setIntroduction(res.opening_statement)
showAutomaticFalse()
}
return ( return (
<> <>
<div className="pb-[20px]"> <div className="pb-[20px]">
<div className='flex justify-between items-center mb-4'> <div className='flex justify-between items-center mb-4'>
<AddFeatureBtn onClick={showChooseFeatureTrue} /> <AddFeatureBtn onClick={showChooseFeatureTrue} />
<div> <AutomaticBtn onClick={showAutomaticTrue}/>
{/* AutoMatic */}
</div>
</div> </div>
{showChooseFeature && ( {showChooseFeature && (
...@@ -100,6 +112,14 @@ const Config: FC = () => { ...@@ -100,6 +112,14 @@ const Config: FC = () => {
onChange={handleFeatureChange} onChange={handleFeatureChange}
/> />
)} )}
{showAutomatic && (
<GetAutomaticResModal
mode={mode as AppType}
isShow={showAutomatic}
onClose={showAutomaticFalse}
onFinished={handleAutomaticRes}
/>
)}
{/* Template */} {/* Template */}
<ConfigPrompt <ConfigPrompt
mode={mode as AppType} mode={mode as AppType}
...@@ -124,9 +144,8 @@ const Config: FC = () => { ...@@ -124,9 +144,8 @@ const Config: FC = () => {
isShowOpeningStatement={featureConfig.openingStatement} isShowOpeningStatement={featureConfig.openingStatement}
openingStatementConfig={ openingStatementConfig={
{ {
promptTemplate,
value: introduction, value: introduction,
onChange: setIntroduction onChange: setIntroduction,
} }
} }
isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer} isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer}
...@@ -135,11 +154,10 @@ const Config: FC = () => { ...@@ -135,11 +154,10 @@ const Config: FC = () => {
} }
{/* TextnGeneration config */} {/* TextnGeneration config */}
{moreLikeThisConifg.enabled && ( {moreLikeThisConfig.enabled && (
<ExperienceEnchanceGroup /> <ExperienceEnchanceGroup />
)} )}
{/* Toolbox */} {/* Toolbox */}
{ {
hasToolbox && ( hasToolbox && (
......
...@@ -38,7 +38,7 @@ const Debug: FC<IDebug> = ({ ...@@ -38,7 +38,7 @@ const Debug: FC<IDebug> = ({
mode, mode,
introduction, introduction,
suggestedQuestionsAfterAnswerConfig, suggestedQuestionsAfterAnswerConfig,
moreLikeThisConifg, moreLikeThisConfig,
inputs, inputs,
// setInputs, // setInputs,
formattingChanged, formattingChanged,
...@@ -304,7 +304,7 @@ const Debug: FC<IDebug> = ({ ...@@ -304,7 +304,7 @@ const Debug: FC<IDebug> = ({
user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables), user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables),
opening_statement: introduction, opening_statement: introduction,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig, suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
more_like_this: moreLikeThisConifg, more_like_this: moreLikeThisConfig,
agent_mode: { agent_mode: {
enabled: true, enabled: true,
tools: [...postDatasets], tools: [...postDatasets],
......
...@@ -17,9 +17,9 @@ import { getNewVar } from '@/utils/var' ...@@ -17,9 +17,9 @@ import { getNewVar } from '@/utils/var'
import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight' import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
export type IOpeningStatementProps = { export type IOpeningStatementProps = {
promptTemplate: string
value: string value: string
onChange: (value: string) => void readonly?: boolean
onChange?: (value: string) => void
} }
// regex to match the {{}} and replace it with a span // regex to match the {{}} and replace it with a span
...@@ -27,6 +27,7 @@ const regex = /\{\{([^}]+)\}\}/g ...@@ -27,6 +27,7 @@ const regex = /\{\{([^}]+)\}\}/g
const OpeningStatement: FC<IOpeningStatementProps> = ({ const OpeningStatement: FC<IOpeningStatementProps> = ({
value = '', value = '',
readonly,
onChange, onChange,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -64,6 +65,8 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({ ...@@ -64,6 +65,8 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
.replace(/\n/g, '<br />') .replace(/\n/g, '<br />')
const handleEdit = () => { const handleEdit = () => {
if (readonly)
return
setFocus() setFocus()
} }
...@@ -93,11 +96,11 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({ ...@@ -93,11 +96,11 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
return return
} }
setBlur() setBlur()
onChange(tempValue) onChange?.(tempValue)
} }
const cancelAutoAddVar = () => { const cancelAutoAddVar = () => {
onChange(tempValue) onChange?.(tempValue)
hideConfirmAddVar() hideConfirmAddVar()
setBlur() setBlur()
} }
...@@ -106,15 +109,15 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({ ...@@ -106,15 +109,15 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
const newModelConfig = produce(modelConfig, (draft) => { const newModelConfig = produce(modelConfig, (draft) => {
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key))] draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key))]
}) })
onChange(tempValue) onChange?.(tempValue)
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
hideConfirmAddVar() hideConfirmAddVar()
setBlur() setBlur()
} }
const headerRight = ( const headerRight = !readonly ? (
<OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpner') as string} onClick={handleEdit} /> <OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpner') as string} onClick={handleEdit} />
) ) : null
return ( return (
<Panel <Panel
...@@ -130,8 +133,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({ ...@@ -130,8 +133,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
isFocus={isFocus} isFocus={isFocus}
> >
<div className='text-gray-700 text-sm'> <div className='text-gray-700 text-sm'>
{(hasValue || (!hasValue && isFocus)) {(hasValue || (!hasValue && isFocus)) ? (
? (
<> <>
{isFocus {isFocus
? ( ? (
...@@ -152,8 +154,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({ ...@@ -152,8 +154,7 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
)} )}
{/* Operation Bar */} {/* Operation Bar */}
{isFocus {isFocus && (
&& (
<div className='mt-2 flex items-center justify-between'> <div className='mt-2 flex items-center justify-between'>
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div> <div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div>
......
...@@ -5,7 +5,10 @@ import { useTranslation } from 'react-i18next' ...@@ -5,7 +5,10 @@ import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import produce from 'immer' import produce from 'immer'
import type { CompletionParams, Inputs, ModelConfig, PromptConfig, PromptVariable, MoreLikeThisConfig } from '@/models/debug' import { useBoolean } from 'ahooks'
import Button from '../../base/button'
import Loading from '../../base/loading'
import type { CompletionParams, Inputs, ModelConfig, MoreLikeThisConfig, PromptConfig, PromptVariable } from '@/models/debug'
import type { DataSet } from '@/models/datasets' import type { DataSet } from '@/models/datasets'
import type { ModelConfig as BackendModelConfig } from '@/types/app' import type { ModelConfig as BackendModelConfig } from '@/types/app'
import ConfigContext from '@/context/debug-configuration' import ConfigContext from '@/context/debug-configuration'
...@@ -17,12 +20,9 @@ import type { AppDetailResponse } from '@/models/app' ...@@ -17,12 +20,9 @@ import type { AppDetailResponse } from '@/models/app'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import { fetchTenantInfo } from '@/service/common' import { fetchTenantInfo } from '@/service/common'
import { fetchAppDetail, updateAppModelConfig } from '@/service/apps' import { fetchAppDetail, updateAppModelConfig } from '@/service/apps'
import { userInputsFormToPromptVariables, promptVariablesToUserInputsForm } from '@/utils/model-config' import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from '@/utils/model-config'
import { fetchDatasets } from '@/service/datasets' import { fetchDatasets } from '@/service/datasets'
import AccountSetting from '@/app/components/header/account-setting' import AccountSetting from '@/app/components/header/account-setting'
import { useBoolean } from 'ahooks'
import Button from '../../base/button'
import Loading from '../../base/loading'
const Configuration: FC = () => { const Configuration: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -35,8 +35,8 @@ const Configuration: FC = () => { ...@@ -35,8 +35,8 @@ const Configuration: FC = () => {
const matched = pathname.match(/\/app\/([^/]+)/) const matched = pathname.match(/\/app\/([^/]+)/)
const appId = (matched?.length && matched[1]) ? matched[1] : '' const appId = (matched?.length && matched[1]) ? matched[1] : ''
const [mode, setMode] = useState('') const [mode, setMode] = useState('')
const [pusblisedConfig, setPusblisedConfig] = useState<{ const [publishedConfig, setPublishedConfig] = useState<{
modelConfig: ModelConfig, modelConfig: ModelConfig
completionParams: CompletionParams completionParams: CompletionParams
} | null>(null) } | null>(null)
...@@ -47,7 +47,7 @@ const Configuration: FC = () => { ...@@ -47,7 +47,7 @@ const Configuration: FC = () => {
prompt_template: '', prompt_template: '',
prompt_variables: [], prompt_variables: [],
}) })
const [moreLikeThisConifg, setMoreLikeThisConifg] = useState<MoreLikeThisConfig>({ const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig>({
enabled: false, enabled: false,
}) })
const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<MoreLikeThisConfig>({ const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<MoreLikeThisConfig>({
...@@ -70,6 +70,10 @@ const Configuration: FC = () => { ...@@ -70,6 +70,10 @@ const Configuration: FC = () => {
prompt_template: '', prompt_template: '',
prompt_variables: [] as PromptVariable[], prompt_variables: [] as PromptVariable[],
}, },
opening_statement: '',
more_like_this: null,
suggested_questions_after_answer: null,
dataSets: [],
}) })
const setModelConfig = (newModelConfig: ModelConfig) => { const setModelConfig = (newModelConfig: ModelConfig) => {
...@@ -77,19 +81,29 @@ const Configuration: FC = () => { ...@@ -77,19 +81,29 @@ const Configuration: FC = () => {
} }
const setModelId = (modelId: string) => { const setModelId = (modelId: string) => {
const newModelConfig = produce(modelConfig, (draft) => { const newModelConfig = produce(modelConfig, (draft: any) => {
draft.model_id = modelId draft.model_id = modelId
}) })
setModelConfig(newModelConfig) setModelConfig(newModelConfig)
} }
const syncToPublishedConfig = (_pusblisedConfig: any) => {
setModelConfig(_pusblisedConfig.modelConfig)
setCompletionParams(_pusblisedConfig.completionParams)
}
const [dataSets, setDataSets] = useState<DataSet[]>([]) const [dataSets, setDataSets] = useState<DataSet[]>([])
const syncToPublishedConfig = (_publishedConfig: any) => {
const modelConfig = _publishedConfig.modelConfig
setModelConfig(_publishedConfig.modelConfig)
setCompletionParams(_publishedConfig.completionParams)
setDataSets(modelConfig.dataSets || [])
// feature
setIntroduction(modelConfig.opening_statement)
setMoreLikeThisConfig(modelConfig.more_like_this || {
enabled: false,
})
setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer || {
enabled: false,
})
}
const [hasSetCustomAPIKEY, setHasSetCustomerAPIKEY] = useState(true) const [hasSetCustomAPIKEY, setHasSetCustomerAPIKEY] = useState(true)
const [isTrailFinished, setIsTrailFinished] = useState(false) const [isTrailFinished, setIsTrailFinished] = useState(false)
const hasSetAPIKEY = hasSetCustomAPIKEY || !isTrailFinished const hasSetAPIKEY = hasSetCustomAPIKEY || !isTrailFinished
...@@ -116,35 +130,40 @@ const Configuration: FC = () => { ...@@ -116,35 +130,40 @@ const Configuration: FC = () => {
const model = res.model_config.model const model = res.model_config.model
let datasets: any = null let datasets: any = null
if (modelConfig.agent_mode?.enabled) { if (modelConfig.agent_mode?.enabled)
datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled) datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled)
}
if (dataSets && datasets?.length && datasets?.length > 0) { if (dataSets && datasets?.length && datasets?.length > 0) {
const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } }) const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } })
datasets = dataSetsWithDetail datasets = dataSetsWithDetail
setDataSets(datasets) setDataSets(datasets)
} }
setIntroduction(modelConfig.opening_statement)
if (modelConfig.more_like_this)
setMoreLikeThisConfig(modelConfig.more_like_this)
if (modelConfig.suggested_questions_after_answer)
setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer)
const config = { const config = {
modelConfig: { modelConfig: {
provider: model.provider, provider: model.provider,
model_id: model.name, model_id: model.name,
configs: { configs: {
prompt_template: modelConfig.pre_prompt, prompt_template: modelConfig.pre_prompt,
prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form) prompt_variables: userInputsFormToPromptVariables(modelConfig.user_input_form),
}, },
opening_statement: modelConfig.opening_statement,
more_like_this: modelConfig.more_like_this,
suggested_questions_after_answer: modelConfig.suggested_questions_after_answer,
dataSets: datasets || [],
}, },
completionParams: model.completion_params, completionParams: model.completion_params,
} }
syncToPublishedConfig(config) syncToPublishedConfig(config)
setPusblisedConfig(config) setPublishedConfig(config)
setIntroduction(modelConfig.opening_statement)
if (modelConfig.more_like_this) {
setMoreLikeThisConifg(modelConfig.more_like_this)
}
if (modelConfig.suggested_questions_after_answer) {
setSuggestedQuestionsAfterAnswerConfig(modelConfig.suggested_questions_after_answer)
}
setHasFetchedDetail(true) setHasFetchedDetail(true)
}) })
}, [appId]) }, [appId])
...@@ -154,18 +173,11 @@ const Configuration: FC = () => { ...@@ -154,18 +173,11 @@ const Configuration: FC = () => {
const promptTemplate = modelConfig.configs.prompt_template const promptTemplate = modelConfig.configs.prompt_template
const promptVariables = modelConfig.configs.prompt_variables const promptVariables = modelConfig.configs.prompt_variables
// not save empty key adn name
// const missingNameItem = promptVariables.find(item => item.name.trim() === '')
// if (missingNameItem) {
// notify({ type: 'error', message: t('appDebug.errorMessage.nameOfKeyRequired', { key: missingNameItem.key }) })
// return
// }
const postDatasets = dataSets.map(({ id }) => ({ const postDatasets = dataSets.map(({ id }) => ({
dataset: { dataset: {
enabled: true, enabled: true,
id, id,
} },
})) }))
// new model config data struct // new model config data struct
...@@ -173,11 +185,11 @@ const Configuration: FC = () => { ...@@ -173,11 +185,11 @@ const Configuration: FC = () => {
pre_prompt: promptTemplate, pre_prompt: promptTemplate,
user_input_form: promptVariablesToUserInputsForm(promptVariables), user_input_form: promptVariablesToUserInputsForm(promptVariables),
opening_statement: introduction || '', opening_statement: introduction || '',
more_like_this: moreLikeThisConifg, more_like_this: moreLikeThisConfig,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig, suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
agent_mode: { agent_mode: {
enabled: true, enabled: true,
tools: [...postDatasets] tools: [...postDatasets],
}, },
model: { model: {
provider: modelConfig.provider, provider: modelConfig.provider,
...@@ -187,8 +199,14 @@ const Configuration: FC = () => { ...@@ -187,8 +199,14 @@ const Configuration: FC = () => {
} }
await updateAppModelConfig({ url: `/apps/${appId}/model-config`, body: data }) await updateAppModelConfig({ url: `/apps/${appId}/model-config`, body: data })
setPusblisedConfig({ const newModelConfig = produce(modelConfig, (draft: any) => {
modelConfig, draft.opening_statement = introduction
draft.more_like_this = moreLikeThisConfig
draft.suggested_questions_after_answer = suggestedQuestionsAfterAnswerConfig
draft.dataSets = dataSets
})
setPublishedConfig({
modelConfig: newModelConfig,
completionParams, completionParams,
}) })
notify({ type: 'success', message: t('common.api.success'), duration: 3000 }) notify({ type: 'success', message: t('common.api.success'), duration: 3000 })
...@@ -196,8 +214,7 @@ const Configuration: FC = () => { ...@@ -196,8 +214,7 @@ const Configuration: FC = () => {
const [showConfirm, setShowConfirm] = useState(false) const [showConfirm, setShowConfirm] = useState(false)
const resetAppConfig = () => { const resetAppConfig = () => {
// debugger syncToPublishedConfig(publishedConfig)
syncToPublishedConfig(pusblisedConfig)
setShowConfirm(false) setShowConfirm(false)
} }
...@@ -224,8 +241,8 @@ const Configuration: FC = () => { ...@@ -224,8 +241,8 @@ const Configuration: FC = () => {
setControlClearChatMessage, setControlClearChatMessage,
prevPromptConfig, prevPromptConfig,
setPrevPromptConfig, setPrevPromptConfig,
moreLikeThisConifg, moreLikeThisConfig,
setMoreLikeThisConifg, setMoreLikeThisConfig,
suggestedQuestionsAfterAnswerConfig, suggestedQuestionsAfterAnswerConfig,
setSuggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig,
formattingChanged, formattingChanged,
...@@ -239,7 +256,7 @@ const Configuration: FC = () => { ...@@ -239,7 +256,7 @@ const Configuration: FC = () => {
modelConfig, modelConfig,
setModelConfig, setModelConfig,
dataSets, dataSets,
setDataSets setDataSets,
}} }}
> >
<> <>
......
...@@ -60,9 +60,14 @@ const sum = (arr: number[]): number => { ...@@ -60,9 +60,14 @@ const sum = (arr: number[]): number => {
}) })
} }
const defaultPeriod = {
start: dayjs().subtract(7, 'day').format(commonDateFormat),
end: dayjs().format(commonDateFormat),
}
export type PeriodParams = { export type PeriodParams = {
name: string name: string
query: { query?: {
start: string start: string
end: string end: string
} }
...@@ -257,7 +262,7 @@ export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => { ...@@ -257,7 +262,7 @@ export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0 const noDataFlag = !response.data || response.data.length === 0
return <Chart return <Chart
basicInfo={{ title: t('appOverview.analysis.totalMessages.title'), explanation: t('appOverview.analysis.totalMessages.explanation'), timePeriod: period.name }} basicInfo={{ title: t('appOverview.analysis.totalMessages.title'), explanation: t('appOverview.analysis.totalMessages.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query) }} chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartType='conversations' chartType='conversations'
{...(noDataFlag && { yMax: 500 })} {...(noDataFlag && { yMax: 500 })}
/> />
...@@ -272,7 +277,7 @@ export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => { ...@@ -272,7 +277,7 @@ export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0 const noDataFlag = !response.data || response.data.length === 0
return <Chart return <Chart
basicInfo={{ title: t('appOverview.analysis.activeUsers.title'), explanation: t('appOverview.analysis.activeUsers.explanation'), timePeriod: period.name }} basicInfo={{ title: t('appOverview.analysis.activeUsers.title'), explanation: t('appOverview.analysis.activeUsers.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query) }} chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartType='endUsers' chartType='endUsers'
{...(noDataFlag && { yMax: 500 })} {...(noDataFlag && { yMax: 500 })}
/> />
...@@ -286,7 +291,7 @@ export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => { ...@@ -286,7 +291,7 @@ export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0 const noDataFlag = !response.data || response.data.length === 0
return <Chart return <Chart
basicInfo={{ title: t('appOverview.analysis.avgSessionInteractions.title'), explanation: t('appOverview.analysis.avgSessionInteractions.explanation'), timePeriod: period.name }} basicInfo={{ title: t('appOverview.analysis.avgSessionInteractions.title'), explanation: t('appOverview.analysis.avgSessionInteractions.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...period.query, key: 'interactions' }) } as any} chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...(period.query ?? defaultPeriod), key: 'interactions' }) } as any}
chartType='conversations' chartType='conversations'
valueKey='interactions' valueKey='interactions'
isAvg isAvg
...@@ -302,7 +307,7 @@ export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => { ...@@ -302,7 +307,7 @@ export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0 const noDataFlag = !response.data || response.data.length === 0
return <Chart return <Chart
basicInfo={{ title: t('appOverview.analysis.avgResponseTime.title'), explanation: t('appOverview.analysis.avgResponseTime.explanation'), timePeriod: period.name }} basicInfo={{ title: t('appOverview.analysis.avgResponseTime.title'), explanation: t('appOverview.analysis.avgResponseTime.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...period.query, key: 'latency' }) } as any} chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...(period.query ?? defaultPeriod), key: 'latency' }) } as any}
valueKey='latency' valueKey='latency'
chartType='conversations' chartType='conversations'
isAvg isAvg
...@@ -319,7 +324,7 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => { ...@@ -319,7 +324,7 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0 const noDataFlag = !response.data || response.data.length === 0
return <Chart return <Chart
basicInfo={{ title: t('appOverview.analysis.userSatisfactionRate.title'), explanation: t('appOverview.analysis.userSatisfactionRate.explanation'), timePeriod: period.name }} basicInfo={{ title: t('appOverview.analysis.userSatisfactionRate.title'), explanation: t('appOverview.analysis.userSatisfactionRate.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...period.query, key: 'rate' }) } as any} chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...(period.query ?? defaultPeriod), key: 'rate' }) } as any}
valueKey='rate' valueKey='rate'
chartType='endUsers' chartType='endUsers'
isAvg isAvg
...@@ -336,7 +341,7 @@ export const CostChart: FC<IBizChartProps> = ({ id, period }) => { ...@@ -336,7 +341,7 @@ export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0 const noDataFlag = !response.data || response.data.length === 0
return <Chart return <Chart
basicInfo={{ title: t('appOverview.analysis.tokenUsage.title'), explanation: t('appOverview.analysis.tokenUsage.explanation'), timePeriod: period.name }} basicInfo={{ title: t('appOverview.analysis.tokenUsage.title'), explanation: t('appOverview.analysis.tokenUsage.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query) }} chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartType='costs' chartType='costs'
{...(noDataFlag && { yMax: 100 })} {...(noDataFlag && { yMax: 100 })}
/> />
......
...@@ -33,12 +33,14 @@ export type IBlockInputProps = { ...@@ -33,12 +33,14 @@ export type IBlockInputProps = {
value: string value: string
className?: string // wrapper class className?: string // wrapper class
highLightClassName?: string // class for the highlighted text default is text-blue-500 highLightClassName?: string // class for the highlighted text default is text-blue-500
readonly?: boolean
onConfirm?: (value: string, keys: string[]) => void onConfirm?: (value: string, keys: string[]) => void
} }
const BlockInput: FC<IBlockInputProps> = ({ const BlockInput: FC<IBlockInputProps> = ({
value = '', value = '',
className, className,
readonly = false,
onConfirm, onConfirm,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -113,7 +115,7 @@ const BlockInput: FC<IBlockInputProps> = ({ ...@@ -113,7 +115,7 @@ const BlockInput: FC<IBlockInputProps> = ({
const editAreaClassName = 'focus:outline-none bg-transparent text-sm' const editAreaClassName = 'focus:outline-none bg-transparent text-sm'
const textAreaContent = ( const textAreaContent = (
<div className='h-[180px] overflow-y-auto' onClick={() => setIsEditing(true)}> <div className={classNames(readonly ? 'max-h-[180px] pb-5' : 'h-[180px]', ' overflow-y-auto')} onClick={() => !readonly && setIsEditing(true)}>
{isEditing {isEditing
? <div className='h-full px-4 py-1'> ? <div className='h-full px-4 py-1'>
<textarea <textarea
...@@ -141,11 +143,12 @@ const BlockInput: FC<IBlockInputProps> = ({ ...@@ -141,11 +143,12 @@ const BlockInput: FC<IBlockInputProps> = ({
<div className={classNames('block-input w-full overflow-y-auto border-none rounded-lg')}> <div className={classNames('block-input w-full overflow-y-auto border-none rounded-lg')}>
{textAreaContent} {textAreaContent}
{/* footer */} {/* footer */}
{!readonly && (
<div className='flex item-center h-14 px-4'> <div className='flex item-center h-14 px-4'>
{isContentChanged {isContentChanged
? ( ? (
<div className='flex items-center justify-between w-full'> <div className='flex items-center justify-between w-full'>
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{currentValue.length}</div> <div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{currentValue?.length}</div>
<div className='flex space-x-2'> <div className='flex space-x-2'>
<Button <Button
onClick={handleCancel} onClick={handleCancel}
...@@ -170,6 +173,7 @@ const BlockInput: FC<IBlockInputProps> = ({ ...@@ -170,6 +173,7 @@ const BlockInput: FC<IBlockInputProps> = ({
</p> </p>
)} )}
</div> </div>
)}
</div> </div>
) )
......
'use client' 'use client'
import React, { useState } from 'react' import React, { useEffect, useState } from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import { Switch as OriginalSwitch } from '@headlessui/react' import { Switch as OriginalSwitch } from '@headlessui/react'
...@@ -12,25 +12,29 @@ type SwitchProps = { ...@@ -12,25 +12,29 @@ type SwitchProps = {
const Switch = ({ onChange, size = 'lg', defaultValue = false, disabled = false }: SwitchProps) => { const Switch = ({ onChange, size = 'lg', defaultValue = false, disabled = false }: SwitchProps) => {
const [enabled, setEnabled] = useState(defaultValue) const [enabled, setEnabled] = useState(defaultValue)
useEffect(() => {
setEnabled(defaultValue)
}, [defaultValue])
const wrapStyle = { const wrapStyle = {
lg: 'h-6 w-11', lg: 'h-6 w-11',
md: 'h-4 w-7' md: 'h-4 w-7',
} }
const circleStyle = { const circleStyle = {
lg: 'h-5 w-5', lg: 'h-5 w-5',
md: 'h-3 w-3' md: 'h-3 w-3',
} }
const translateLeft = { const translateLeft = {
lg: 'translate-x-5', lg: 'translate-x-5',
md: 'translate-x-3' md: 'translate-x-3',
} }
return ( return (
<OriginalSwitch <OriginalSwitch
checked={enabled} checked={enabled}
onChange={(checked: boolean) => { onChange={(checked: boolean) => {
if (disabled) return; if (disabled)
return
setEnabled(checked) setEnabled(checked)
onChange(checked) onChange(checked)
}} }}
......
...@@ -35,7 +35,7 @@ const Tooltip: FC<TooltipProps> = ({ ...@@ -35,7 +35,7 @@ const Tooltip: FC<TooltipProps> = ({
<ReactTooltip <ReactTooltip
id={selector} id={selector}
content={content} content={content}
className={classNames('!bg-white !text-xs !font-normal !text-gray-700 !shadow-lg !opacity-100', className)} className={classNames('!z-[999] !bg-white !text-xs !font-normal !text-gray-700 !shadow-lg !opacity-100', className)}
place={position} place={position}
clickable={clickable} clickable={clickable}
isOpen={disabled ? false : undefined} isOpen={disabled ? false : undefined}
......
<svg width="24" height="26" viewBox="0 0 24 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_5938_919)">
<path d="M3 5.8C3 4.11984 3 3.27976 3.32698 2.63803C3.6146 2.07354 4.07354 1.6146 4.63803 1.32698C5.27976 1 6.11984 1 7.8 1H14L21 8V18.2C21 19.8802 21 20.7202 20.673 21.362C20.3854 21.9265 19.9265 22.3854 19.362 22.673C18.7202 23 17.8802 23 16.2 23H7.8C6.11984 23 5.27976 23 4.63803 22.673C4.07354 22.3854 3.6146 21.9265 3.32698 21.362C3 20.7202 3 19.8802 3 18.2V5.8Z" fill="#169951"/>
</g>
<g opacity="0.96">
<path d="M9.81332 16.4181C9.63132 17.5171 8.86832 18.0421 7.92332 18.0421C7.34232 18.0421 6.90132 17.8461 6.53732 17.4821C6.01232 16.9571 6.03332 16.2571 6.03332 15.5081C6.03332 14.7591 6.01232 14.0591 6.53732 13.5341C6.90132 13.1701 7.34232 12.9741 7.92332 12.9741C8.86832 12.9741 9.63132 13.4991 9.81332 14.5981H8.56732C8.49032 14.3181 8.33632 14.0661 7.93032 14.0661C7.70632 14.0661 7.53832 14.1571 7.44732 14.2761C7.33532 14.4231 7.25832 14.5981 7.25832 15.5081C7.25832 16.4181 7.33532 16.5931 7.44732 16.7401C7.53832 16.8591 7.70632 16.9501 7.93032 16.9501C8.33632 16.9501 8.49032 16.6981 8.56732 16.4181H9.81332Z" fill="white"/>
<path d="M13.8059 16.4741C13.8059 17.4891 12.9309 18.0421 11.8809 18.0421C11.1179 18.0421 10.4949 17.9021 9.99094 17.3841L10.7749 16.6001C11.0339 16.8591 11.4889 16.9501 11.8879 16.9501C12.3709 16.9501 12.6019 16.7891 12.6019 16.5021C12.6019 16.3831 12.5739 16.2851 12.5039 16.2081C12.4409 16.1451 12.3359 16.0961 12.1749 16.0751L11.5729 15.9911C11.1319 15.9281 10.7959 15.7811 10.5719 15.5501C10.3409 15.3121 10.2289 14.9761 10.2289 14.5491C10.2289 13.6391 10.9149 12.9741 12.0489 12.9741C12.7629 12.9741 13.3019 13.1421 13.7289 13.5691L12.9589 14.3391C12.6439 14.0241 12.2309 14.0451 12.0139 14.0451C11.5869 14.0451 11.4119 14.2901 11.4119 14.5071C11.4119 14.5701 11.4329 14.6611 11.5099 14.7381C11.5729 14.8011 11.6779 14.8641 11.8529 14.8851L12.4549 14.9691C12.9029 15.0321 13.2249 15.1721 13.4349 15.3821C13.7009 15.6411 13.8059 16.0121 13.8059 16.4741Z" fill="white"/>
<path d="M18.3124 13.0161L16.6604 18.0001H15.7504L14.1054 13.0161H15.3724L16.2124 15.8021L17.0384 13.0161H18.3124Z" fill="white"/>
</g>
<path opacity="0.5" d="M14 1L21 8H16C14.8954 8 14 7.10457 14 6V1Z" fill="white"/>
<defs>
<filter id="filter0_d_5938_919" x="1" y="0" width="22" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5938_919"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5938_919" result="shape"/>
</filter>
</defs>
</svg>
<svg width="24" height="26" viewBox="0 0 24 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_5938_927)">
<path d="M3 5.8C3 4.11984 3 3.27976 3.32698 2.63803C3.6146 2.07354 4.07354 1.6146 4.63803 1.32698C5.27976 1 6.11984 1 7.8 1H14L21 8V18.2C21 19.8802 21 20.7202 20.673 21.362C20.3854 21.9265 19.9265 22.3854 19.362 22.673C18.7202 23 17.8802 23 16.2 23H7.8C6.11984 23 5.27976 23 4.63803 22.673C4.07354 22.3854 3.6146 21.9265 3.32698 21.362C3 20.7202 3 19.8802 3 18.2V5.8Z" fill="#169951"/>
</g>
<path opacity="0.5" d="M14 1L21 8H16C14.8954 8 14 7.10457 14 6V1Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 12C17.5523 12 18 12.4477 18 13V18C18 18.5523 17.5523 19 17 19H7C6.44772 19 6 18.5523 6 18V13C6 12.4477 6.44772 12 7 12H17ZM11.5 13H7L7 15H11.5V13ZM12.5 18H17V16H12.5V18ZM11.5 16V18H7L7 16H11.5ZM12.5 15H17V13H12.5V15Z" fill="white" fill-opacity="0.96"/>
<defs>
<filter id="filter0_d_5938_927" x="1" y="0" width="22" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5938_927"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5938_927" result="shape"/>
</filter>
</defs>
</svg>
...@@ -100,6 +100,14 @@ ...@@ -100,6 +100,14 @@
background-image: url(../assets/unknow.svg); background-image: url(../assets/unknow.svg);
background-size: 32px; background-size: 32px;
} }
.fileIcon.csv {
background-image: url(../assets/csv.svg);
}
.fileIcon.xlsx,
.fileIcon.xls {
background-image: url(../assets/xlsx.svg);
}
.fileIcon.pdf { .fileIcon.pdf {
background-image: url(../assets/pdf.svg); background-image: url(../assets/pdf.svg);
} }
......
'use client' 'use client'
import React, { useState, useRef, useEffect, useCallback } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { File as FileEntity } from '@/models/datasets'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import cn from 'classnames'
import s from './index.module.css'
import type { File as FileEntity } from '@/models/datasets'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { upload } from '@/service/base' import { upload } from '@/service/base'
import cn from 'classnames'
import s from './index.module.css'
type IFileUploaderProps = { type IFileUploaderProps = {
file?: FileEntity; file?: FileEntity
onFileUpdate: (file?: FileEntity) => void; onFileUpdate: (file?: FileEntity) => void
} }
const ACCEPTS = [ const ACCEPTS = [
...@@ -23,9 +22,12 @@ const ACCEPTS = [ ...@@ -23,9 +22,12 @@ const ACCEPTS = [
'.md', '.md',
'.markdown', '.markdown',
'.txt', '.txt',
'.xls',
'.xlsx',
'.csv',
] ]
const MAX_SIZE = 15 * 1024 *1024 const MAX_SIZE = 15 * 1024 * 1024
const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => { const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -39,60 +41,55 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => { ...@@ -39,60 +41,55 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
const [uploading, setUploading] = useState(false) const [uploading, setUploading] = useState(false)
const [percent, setPercent] = useState(0) const [percent, setPercent] = useState(0)
const handleDragEnter = (e: DragEvent) => { // utils
e.preventDefault() const getFileType = (currentFile: File) => {
e.stopPropagation() if (!currentFile)
e.target !== dragRef.current && setDragging(true) return ''
}
const handleDragOver = (e: DragEvent) => { const arr = currentFile.name.split('.')
e.preventDefault() return arr[arr.length - 1]
e.stopPropagation()
}
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
}
const handleDrop = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
setDragging(false)
if (!e.dataTransfer) {
return
}
const files = [...e.dataTransfer.files]
if (files.length > 1) {
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.count') })
return;
} }
onFileUpdate() const getFileName = (name: string) => {
fileUpload(files[0]) const arr = name.split('.')
return arr.slice(0, -1).join()
} }
const getFileSize = (size: number) => {
if (size / 1024 < 10)
return `${(size / 1024).toFixed(2)}KB`
const selectHandle = () => { return `${(size / 1024 / 1024).toFixed(2)}MB`
if (fileUploader.current) {
fileUploader.current.click();
}
} }
const removeFile = () => {
if (fileUploader.current) { const isValid = (file: File) => {
fileUploader.current.value = '' const { size } = file
const ext = `.${getFileType(file)}`
const isValidType = ACCEPTS.includes(ext)
if (!isValidType)
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.typeError') })
const isValidSize = size <= MAX_SIZE
if (!isValidSize)
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.size') })
return isValidType && isValidSize
} }
setCurrentFile(undefined) const onProgress = useCallback((e: ProgressEvent) => {
onFileUpdate() if (e.lengthComputable) {
const percent = Math.floor(e.loaded / e.total * 100)
setPercent(percent)
} }
const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => { }, [setPercent])
const currentFile = e.target.files?.[0] const abort = () => {
onFileUpdate() const currentXHR = uploadPromise.current
fileUpload(currentFile) currentXHR.abort()
} }
const fileUpload = async (file?: File) => { const fileUpload = async (file?: File) => {
if (!file) { if (!file)
return return
}
if (!isValid(file)) { if (!isValid(file))
return return
}
setCurrentFile(file) setCurrentFile(file)
setUploading(true) setUploading(true)
const formData = new FormData() const formData = new FormData()
...@@ -105,7 +102,7 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => { ...@@ -105,7 +102,7 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
xhr: currentXHR, xhr: currentXHR,
data: formData, data: formData,
onprogress: onProgress, onprogress: onProgress,
}) as FileEntity; }) as FileEntity
onFileUpdate(result) onFileUpdate(result)
setUploading(false) setUploading(false)
} }
...@@ -113,69 +110,72 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => { ...@@ -113,69 +110,72 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
setUploading(false) setUploading(false)
// abort handle // abort handle
if (xhr.readyState === 0 && xhr.status === 0) { if (xhr.readyState === 0 && xhr.status === 0) {
if (fileUploader.current) { if (fileUploader.current)
fileUploader.current.value = '' fileUploader.current.value = ''
}
setCurrentFile(undefined) setCurrentFile(undefined)
return return
} }
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.failed') }) notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.failed') })
return
}
} }
const onProgress = useCallback((e: ProgressEvent) => {
if (e.lengthComputable) {
const percent = Math.floor(e.loaded / e.total * 100)
setPercent(percent)
}
}, [setPercent])
const abort = () => {
const currentXHR = uploadPromise.current
currentXHR.abort();
} }
const handleDragEnter = (e: DragEvent) => {
// utils e.preventDefault()
const getFileType = (currentFile: File) => { e.stopPropagation()
if (!currentFile) { e.target !== dragRef.current && setDragging(true)
return ''
} }
const arr = currentFile.name.split('.') const handleDragOver = (e: DragEvent) => {
return arr[arr.length-1] e.preventDefault()
e.stopPropagation()
} }
const getFileName = (name: string) => { const handleDragLeave = (e: DragEvent) => {
const arr = name.split('.') e.preventDefault()
return arr.slice(0, -1).join() e.stopPropagation()
e.target === dragRef.current && setDragging(false)
} }
const getFileSize = (size: number) => { const handleDrop = (e: DragEvent) => {
if (size / 1024 < 10) { e.preventDefault()
return `${(size / 1024).toFixed(2)}KB` e.stopPropagation()
setDragging(false)
if (!e.dataTransfer)
return
const files = [...e.dataTransfer.files]
if (files.length > 1) {
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.count') })
return
} }
return `${(size / 1024 / 1024).toFixed(2)}MB` onFileUpdate()
fileUpload(files[0])
} }
const isValid = (file: File) => {
const { size } = file const selectHandle = () => {
const ext = `.${getFileType(file)}` if (fileUploader.current)
const isValidType = ACCEPTS.includes(ext) fileUploader.current.click()
if (!isValidType) {
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.typeError') })
} }
const isValidSize = size <= MAX_SIZE; const removeFile = () => {
if (!isValidSize) { if (fileUploader.current)
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.size') }) fileUploader.current.value = ''
setCurrentFile(undefined)
onFileUpdate()
} }
return isValidType && isValidSize; const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => {
const currentFile = e.target.files?.[0]
onFileUpdate()
fileUpload(currentFile)
} }
useEffect(() => { useEffect(() => {
dropRef.current?.addEventListener('dragenter', handleDragEnter); dropRef.current?.addEventListener('dragenter', handleDragEnter)
dropRef.current?.addEventListener('dragover', handleDragOver); dropRef.current?.addEventListener('dragover', handleDragOver)
dropRef.current?.addEventListener('dragleave', handleDragLeave); dropRef.current?.addEventListener('dragleave', handleDragLeave)
dropRef.current?.addEventListener('drop', handleDrop); dropRef.current?.addEventListener('drop', handleDrop)
return () => { return () => {
dropRef.current?.removeEventListener('dragenter', handleDragEnter); dropRef.current?.removeEventListener('dragenter', handleDragEnter)
dropRef.current?.removeEventListener('dragover', handleDragOver); dropRef.current?.removeEventListener('dragover', handleDragOver)
dropRef.current?.removeEventListener('dragleave', handleDragLeave); dropRef.current?.removeEventListener('dragleave', handleDragLeave)
dropRef.current?.removeEventListener('drop', handleDrop); dropRef.current?.removeEventListener('drop', handleDrop)
} }
}, []) }, [])
...@@ -202,7 +202,7 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => { ...@@ -202,7 +202,7 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
{currentFile && ( {currentFile && (
<div className={cn(s.file, uploading && s.uploading)}> <div className={cn(s.file, uploading && s.uploading)}>
{uploading && ( {uploading && (
<div className={s.progressbar} style={{ width: `${percent}%`}}/> <div className={s.progressbar} style={{ width: `${percent}%` }}/>
)} )}
<div className={cn(s.fileIcon, s[getFileType(currentFile)])}/> <div className={cn(s.fileIcon, s[getFileType(currentFile)])}/>
<div className={s.fileInfo}> <div className={s.fileInfo}>
...@@ -264,4 +264,4 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => { ...@@ -264,4 +264,4 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
) )
} }
export default FileUploader; export default FileUploader
...@@ -292,6 +292,15 @@ ...@@ -292,6 +292,15 @@
background-image: url(../assets/pdf.svg); background-image: url(../assets/pdf.svg);
} }
.fileIcon.csv {
background-image: url(../assets/csv.svg);
}
.fileIcon.xlsx,
.fileIcon.xls {
background-image: url(../assets/xlsx.svg);
}
.fileIcon.html, .fileIcon.html,
.fileIcon.htm { .fileIcon.htm {
background-image: url(../assets/html.svg); background-image: url(../assets/html.svg);
......
/* eslint-disable no-mixed-operators */
'use client' 'use client'
import React, { useState, useRef, useEffect, useLayoutEffect } from 'react' import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import type { File, PreProcessingRule, Rules, FileIndexingEstimateResponse as IndexingEstimateResponse } from '@/models/datasets' import { XMarkIcon } from '@heroicons/react/20/solid'
import cn from 'classnames'
import Link from 'next/link'
import PreviewItem from './preview-item'
import s from './index.module.css'
import type { CreateDocumentReq, File, FullDocumentDetail, FileIndexingEstimateResponse as IndexingEstimateResponse, PreProcessingRule, Rules, createDocumentResponse } from '@/models/datasets'
import { import {
fetchDefaultProcessRule,
createFirstDocument,
createDocument, createDocument,
createFirstDocument,
fetchFileIndexingEstimate as didFetchFileIndexingEstimate, fetchFileIndexingEstimate as didFetchFileIndexingEstimate,
fetchDefaultProcessRule,
} from '@/service/datasets' } from '@/service/datasets'
import type { CreateDocumentReq, createDocumentResponse, FullDocumentDetail } from '@/models/datasets'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import PreviewItem from './preview-item'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { XMarkIcon } from '@heroicons/react/20/solid'
import cn from 'classnames'
import s from './index.module.css'
import Link from 'next/link'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { formatNumber } from '@/utils/format' import { formatNumber } from '@/utils/format'
type StepTwoProps = { type StepTwoProps = {
isSetting?: boolean, isSetting?: boolean
documentDetail?: FullDocumentDetail documentDetail?: FullDocumentDetail
hasSetAPIKEY: boolean, hasSetAPIKEY: boolean
onSetting: () => void, onSetting: () => void
datasetId?: string, datasetId?: string
indexingType?: string, indexingType?: string
file?: File, file?: File
onStepChange?: (delta: number) => void, onStepChange?: (delta: number) => void
updateIndexingTypeCache?: (type: string) => void, updateIndexingTypeCache?: (type: string) => void
updateResultCache?: (res: createDocumentResponse) => void updateResultCache?: (res: createDocumentResponse) => void
onSave?: () => void onSave?: () => void
onCancel?: () => void onCancel?: () => void
...@@ -71,8 +71,10 @@ const StepTwo = ({ ...@@ -71,8 +71,10 @@ const StepTwo = ({
const [defaultConfig, setDefaultConfig] = useState<Rules>() const [defaultConfig, setDefaultConfig] = useState<Rules>()
const hasSetIndexType = !!indexingType const hasSetIndexType = !!indexingType
const [indexType, setIndexType] = useState<IndexingType>( const [indexType, setIndexType] = useState<IndexingType>(
indexingType || indexingType
hasSetAPIKEY ? IndexingType.QUALIFIED : IndexingType.ECONOMICAL || hasSetAPIKEY
? IndexingType.QUALIFIED
: IndexingType.ECONOMICAL,
) )
const [showPreview, { setTrue: setShowPreview, setFalse: hidePreview }] = useBoolean() const [showPreview, { setTrue: setShowPreview, setFalse: hidePreview }] = useBoolean()
const [customFileIndexingEstimate, setCustomFileIndexingEstimate] = useState<IndexingEstimateResponse | null>(null) const [customFileIndexingEstimate, setCustomFileIndexingEstimate] = useState<IndexingEstimateResponse | null>(null)
...@@ -82,38 +84,37 @@ const StepTwo = ({ ...@@ -82,38 +84,37 @@ const StepTwo = ({
})() })()
const scrollHandle = (e: any) => { const scrollHandle = (e: any) => {
if (e.target.scrollTop > 0) { if (e.target.scrollTop > 0)
setScrolled(true) setScrolled(true)
} else {
else
setScrolled(false) setScrolled(false)
} }
}
const previewScrollHandle = (e: any) => { const previewScrollHandle = (e: any) => {
if (e.target.scrollTop > 0) { if (e.target.scrollTop > 0)
setPreviewScrolled(true) setPreviewScrolled(true)
} else {
else
setPreviewScrolled(false) setPreviewScrolled(false)
} }
}
const getFileName = (name: string) => { const getFileName = (name: string) => {
const arr = name.split('.') const arr = name.split('.')
return arr.slice(0, -1).join('.') return arr.slice(0, -1).join('.')
} }
const getRuleName = (key: string) => { const getRuleName = (key: string) => {
if (key === 'remove_extra_spaces') { if (key === 'remove_extra_spaces')
return t('datasetCreation.stepTwo.removeExtraSpaces') return t('datasetCreation.stepTwo.removeExtraSpaces')
}
if (key === 'remove_urls_emails') { if (key === 'remove_urls_emails')
return t('datasetCreation.stepTwo.removeUrlEmails') return t('datasetCreation.stepTwo.removeUrlEmails')
}
if (key === 'remove_stopwords') { if (key === 'remove_stopwords')
return t('datasetCreation.stepTwo.removeStopwords') return t('datasetCreation.stepTwo.removeStopwords')
} }
}
const ruleChangeHandle = (id: string) => { const ruleChangeHandle = (id: string) => {
const newRules = rules.map(rule => { const newRules = rules.map((rule) => {
if (rule.id === id) { if (rule.id === id) {
return { return {
id: rule.id, id: rule.id,
...@@ -132,13 +133,23 @@ const StepTwo = ({ ...@@ -132,13 +133,23 @@ const StepTwo = ({
} }
} }
const fetchFileIndexingEstimate = async () => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams())
if (segmentationType === SegmentType.CUSTOM)
setCustomFileIndexingEstimate(res)
else
setAutomaticFileIndexingEstimate(res)
}
const confirmChangeCustomConfig = async () => { const confirmChangeCustomConfig = async () => {
setCustomFileIndexingEstimate(null) setCustomFileIndexingEstimate(null)
setShowPreview() setShowPreview()
await fetchFileIndexingEstimate() await fetchFileIndexingEstimate()
} }
const getIndexing_technique = () => indexingType ? indexingType : indexType const getIndexing_technique = () => indexingType || indexType
const getProcessRule = () => { const getProcessRule = () => {
const processRule: any = { const processRule: any = {
...@@ -168,16 +179,6 @@ const StepTwo = ({ ...@@ -168,16 +179,6 @@ const StepTwo = ({
return params return params
} }
const fetchFileIndexingEstimate = async () => {
const res = await didFetchFileIndexingEstimate(getFileIndexingEstimateParams())
if (segmentationType === SegmentType.CUSTOM) {
setCustomFileIndexingEstimate(res)
}
else {
setAutomaticFileIndexingEstimate(res)
}
}
const getCreationParams = () => { const getCreationParams = () => {
let params let params
if (isSetting) { if (isSetting) {
...@@ -185,7 +186,8 @@ const StepTwo = ({ ...@@ -185,7 +186,8 @@ const StepTwo = ({
original_document_id: documentDetail?.id, original_document_id: documentDetail?.id,
process_rule: getProcessRule(), process_rule: getProcessRule(),
} as CreateDocumentReq } as CreateDocumentReq
} else { }
else {
params = { params = {
data_source: { data_source: {
type: 'upload_file', type: 'upload_file',
...@@ -226,25 +228,25 @@ const StepTwo = ({ ...@@ -226,25 +228,25 @@ const StepTwo = ({
} }
const getDefaultMode = () => { const getDefaultMode = () => {
if (documentDetail) { if (documentDetail)
setSegmentationType(documentDetail.dataset_process_rule.mode) setSegmentationType(documentDetail.dataset_process_rule.mode)
} }
}
const createHandle = async () => { const createHandle = async () => {
try { try {
let res; let res
const params = getCreationParams() const params = getCreationParams()
if (!datasetId) { if (!datasetId) {
res = await createFirstDocument({ res = await createFirstDocument({
body: params body: params,
}) })
updateIndexingTypeCache && updateIndexingTypeCache(indexType) updateIndexingTypeCache && updateIndexingTypeCache(indexType)
updateResultCache && updateResultCache(res) updateResultCache && updateResultCache(res)
} else { }
else {
res = await createDocument({ res = await createDocument({
datasetId, datasetId,
body: params body: params,
}) })
updateIndexingTypeCache && updateIndexingTypeCache(indexType) updateIndexingTypeCache && updateIndexingTypeCache(indexType)
updateResultCache && updateResultCache({ updateResultCache && updateResultCache({
...@@ -257,7 +259,7 @@ const StepTwo = ({ ...@@ -257,7 +259,7 @@ const StepTwo = ({
catch (err) { catch (err) {
Toast.notify({ Toast.notify({
type: 'error', type: 'error',
message: err + '', message: `${err}`,
}) })
} }
} }
...@@ -266,35 +268,36 @@ const StepTwo = ({ ...@@ -266,35 +268,36 @@ const StepTwo = ({
// fetch rules // fetch rules
if (!isSetting) { if (!isSetting) {
getRules() getRules()
} else { }
else {
getRulesFromDetail() getRulesFromDetail()
getDefaultMode() getDefaultMode()
} }
}, []) }, [])
useEffect(() => { useEffect(() => {
scrollRef.current?.addEventListener('scroll', scrollHandle); scrollRef.current?.addEventListener('scroll', scrollHandle)
return () => { return () => {
scrollRef.current?.removeEventListener('scroll', scrollHandle); scrollRef.current?.removeEventListener('scroll', scrollHandle)
} }
}, []) }, [])
useLayoutEffect(() => { useLayoutEffect(() => {
if (showPreview) { if (showPreview) {
previewScrollRef.current?.addEventListener('scroll', previewScrollHandle); previewScrollRef.current?.addEventListener('scroll', previewScrollHandle)
return () => { return () => {
previewScrollRef.current?.removeEventListener('scroll', previewScrollHandle); previewScrollRef.current?.removeEventListener('scroll', previewScrollHandle)
} }
} }
}, [showPreview]) }, [showPreview])
useEffect(() => { useEffect(() => {
// get indexing type by props // get indexing type by props
if (indexingType) { if (indexingType)
setIndexType(indexingType as IndexingType) setIndexType(indexingType as IndexingType)
} else {
else
setIndexType(hasSetAPIKEY ? IndexingType.QUALIFIED : IndexingType.ECONOMICAL) setIndexType(hasSetAPIKEY ? IndexingType.QUALIFIED : IndexingType.ECONOMICAL)
}
}, [hasSetAPIKEY, indexingType, datasetId]) }, [hasSetAPIKEY, indexingType, datasetId])
useEffect(() => { useEffect(() => {
...@@ -302,7 +305,8 @@ const StepTwo = ({ ...@@ -302,7 +305,8 @@ const StepTwo = ({
setAutomaticFileIndexingEstimate(null) setAutomaticFileIndexingEstimate(null)
setShowPreview() setShowPreview()
fetchFileIndexingEstimate() fetchFileIndexingEstimate()
} else { }
else {
hidePreview() hidePreview()
setCustomFileIndexingEstimate(null) setCustomFileIndexingEstimate(null)
} }
...@@ -320,7 +324,7 @@ const StepTwo = ({ ...@@ -320,7 +324,7 @@ const StepTwo = ({
className={cn( className={cn(
s.radioItem, s.radioItem,
s.segmentationItem, s.segmentationItem,
segmentationType === SegmentType.AUTO && s.active segmentationType === SegmentType.AUTO && s.active,
)} )}
onClick={() => setSegmentationType(SegmentType.AUTO)} onClick={() => setSegmentationType(SegmentType.AUTO)}
> >
...@@ -355,7 +359,7 @@ const StepTwo = ({ ...@@ -355,7 +359,7 @@ const StepTwo = ({
type="text" type="text"
className={s.input} className={s.input}
placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={segmentIdentifier} placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={segmentIdentifier}
onChange={(e) => setSegmentIdentifier(e.target.value)} onChange={e => setSegmentIdentifier(e.target.value)}
/> />
</div> </div>
</div> </div>
...@@ -366,7 +370,7 @@ const StepTwo = ({ ...@@ -366,7 +370,7 @@ const StepTwo = ({
type="number" type="number"
className={s.input} className={s.input}
placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={max} placeholder={t('datasetCreation.stepTwo.separatorPlaceholder') || ''} value={max}
onChange={(e) => setMax(Number(e.target.value))} onChange={e => setMax(Number(e.target.value))}
/> />
</div> </div>
</div> </div>
...@@ -403,9 +407,8 @@ const StepTwo = ({ ...@@ -403,9 +407,8 @@ const StepTwo = ({
hasSetIndexType && '!w-full', hasSetIndexType && '!w-full',
)} )}
onClick={() => { onClick={() => {
if (hasSetAPIKEY) { if (hasSetAPIKEY)
setIndexType(IndexingType.QUALIFIED) setIndexType(IndexingType.QUALIFIED)
}
}} }}
> >
<span className={cn(s.typeIcon, s.qualified)} /> <span className={cn(s.typeIcon, s.qualified)} />
...@@ -418,9 +421,11 @@ const StepTwo = ({ ...@@ -418,9 +421,11 @@ const StepTwo = ({
<div className={s.tip}>{t('datasetCreation.stepTwo.qualifiedTip')}</div> <div className={s.tip}>{t('datasetCreation.stepTwo.qualifiedTip')}</div>
<div className='pb-0.5 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateCost')}</div> <div className='pb-0.5 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateCost')}</div>
{ {
!!fileIndexingEstimate ? ( fileIndexingEstimate
? (
<div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.tokens)} tokens(<span className='text-yellow-500'>${formatNumber(fileIndexingEstimate.total_price)}</span>)</div> <div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.tokens)} tokens(<span className='text-yellow-500'>${formatNumber(fileIndexingEstimate.total_price)}</span>)</div>
) : ( )
: (
<div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div> <div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div>
) )
} }
...@@ -434,7 +439,6 @@ const StepTwo = ({ ...@@ -434,7 +439,6 @@ const StepTwo = ({
</div> </div>
)} )}
{(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.ECONOMICAL)) && ( {(!hasSetIndexType || (hasSetIndexType && indexingType === IndexingType.ECONOMICAL)) && (
<div <div
className={cn( className={cn(
...@@ -476,22 +480,26 @@ const StepTwo = ({ ...@@ -476,22 +480,26 @@ const StepTwo = ({
<div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateSegment')}</div> <div className='mb-2 text-xs font-medium text-gray-500'>{t('datasetCreation.stepTwo.emstimateSegment')}</div>
<div className='flex items-center text-sm leading-6 font-medium text-gray-800'> <div className='flex items-center text-sm leading-6 font-medium text-gray-800'>
{ {
!!fileIndexingEstimate ? ( fileIndexingEstimate
? (
<div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.total_segments)} </div> <div className='text-xs font-medium text-gray-800'>{formatNumber(fileIndexingEstimate.total_segments)} </div>
) : ( )
: (
<div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div> <div className={s.calculating}>{t('datasetCreation.stepTwo.calculating')}</div>
) )
} }
</div> </div>
</div> </div>
</div> </div>
{!isSetting ? ( {!isSetting
? (
<div className='flex items-center mt-8 py-2'> <div className='flex items-center mt-8 py-2'>
<Button onClick={() => onStepChange && onStepChange(-1)}>{t('datasetCreation.stepTwo.lastStep')}</Button> <Button onClick={() => onStepChange && onStepChange(-1)}>{t('datasetCreation.stepTwo.lastStep')}</Button>
<div className={s.divider} /> <div className={s.divider} />
<Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.nextStep')}</Button> <Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.nextStep')}</Button>
</div> </div>
) : ( )
: (
<div className='flex items-center mt-8 py-2'> <div className='flex items-center mt-8 py-2'>
<Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.save')}</Button> <Button type='primary' onClick={createHandle}>{t('datasetCreation.stepTwo.save')}</Button>
<Button className='ml-2' onClick={onCancel}>{t('datasetCreation.stepTwo.cancel')}</Button> <Button className='ml-2' onClick={onCancel}>{t('datasetCreation.stepTwo.cancel')}</Button>
...@@ -500,7 +508,8 @@ const StepTwo = ({ ...@@ -500,7 +508,8 @@ const StepTwo = ({
</div> </div>
</div> </div>
</div> </div>
{(showPreview) ? ( {(showPreview)
? (
<div ref={previewScrollRef} className={cn(s.previewWrap, 'relativeh-full overflow-y-scroll border-l border-[#F2F4F7]')}> <div ref={previewScrollRef} className={cn(s.previewWrap, 'relativeh-full overflow-y-scroll border-l border-[#F2F4F7]')}>
<div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`, ' flex items-center justify-between px-8')}> <div className={cn(s.previewHeader, previewScrolled && `${s.fixed} pb-3`, ' flex items-center justify-between px-8')}>
<span>{t('datasetCreation.stepTwo.previewTitle')}</span> <span>{t('datasetCreation.stepTwo.previewTitle')}</span>
...@@ -509,18 +518,20 @@ const StepTwo = ({ ...@@ -509,18 +518,20 @@ const StepTwo = ({
</div> </div>
</div> </div>
<div className='my-4 px-8 space-y-4'> <div className='my-4 px-8 space-y-4'>
{fileIndexingEstimate?.preview ? ( {fileIndexingEstimate?.preview
? (
<> <>
{fileIndexingEstimate?.preview.map((item, index) => ( {fileIndexingEstimate?.preview.map((item, index) => (
<PreviewItem key={item} content={item} index={index + 1} /> <PreviewItem key={item} content={item} index={index + 1} />
))} ))}
</> </>
) : <div className='flex items-center justify-center h-[200px]'><Loading type='area'></Loading></div> )
: <div className='flex items-center justify-center h-[200px]'><Loading type='area'></Loading></div>
} }
</div> </div>
</div> </div>
) : )
(<div className={cn(s.sideTip)}> : (<div className={cn(s.sideTip)}>
<div className={s.tipCard}> <div className={s.tipCard}>
<span className={s.icon} /> <span className={s.icon} />
<div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div> <div className={s.title}>{t('datasetCreation.stepTwo.sideTipTitle')}</div>
......
<svg width="24" height="26" viewBox="0 0 24 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_5938_919)">
<path d="M3 5.8C3 4.11984 3 3.27976 3.32698 2.63803C3.6146 2.07354 4.07354 1.6146 4.63803 1.32698C5.27976 1 6.11984 1 7.8 1H14L21 8V18.2C21 19.8802 21 20.7202 20.673 21.362C20.3854 21.9265 19.9265 22.3854 19.362 22.673C18.7202 23 17.8802 23 16.2 23H7.8C6.11984 23 5.27976 23 4.63803 22.673C4.07354 22.3854 3.6146 21.9265 3.32698 21.362C3 20.7202 3 19.8802 3 18.2V5.8Z" fill="#169951"/>
</g>
<g opacity="0.96">
<path d="M9.81332 16.4181C9.63132 17.5171 8.86832 18.0421 7.92332 18.0421C7.34232 18.0421 6.90132 17.8461 6.53732 17.4821C6.01232 16.9571 6.03332 16.2571 6.03332 15.5081C6.03332 14.7591 6.01232 14.0591 6.53732 13.5341C6.90132 13.1701 7.34232 12.9741 7.92332 12.9741C8.86832 12.9741 9.63132 13.4991 9.81332 14.5981H8.56732C8.49032 14.3181 8.33632 14.0661 7.93032 14.0661C7.70632 14.0661 7.53832 14.1571 7.44732 14.2761C7.33532 14.4231 7.25832 14.5981 7.25832 15.5081C7.25832 16.4181 7.33532 16.5931 7.44732 16.7401C7.53832 16.8591 7.70632 16.9501 7.93032 16.9501C8.33632 16.9501 8.49032 16.6981 8.56732 16.4181H9.81332Z" fill="white"/>
<path d="M13.8059 16.4741C13.8059 17.4891 12.9309 18.0421 11.8809 18.0421C11.1179 18.0421 10.4949 17.9021 9.99094 17.3841L10.7749 16.6001C11.0339 16.8591 11.4889 16.9501 11.8879 16.9501C12.3709 16.9501 12.6019 16.7891 12.6019 16.5021C12.6019 16.3831 12.5739 16.2851 12.5039 16.2081C12.4409 16.1451 12.3359 16.0961 12.1749 16.0751L11.5729 15.9911C11.1319 15.9281 10.7959 15.7811 10.5719 15.5501C10.3409 15.3121 10.2289 14.9761 10.2289 14.5491C10.2289 13.6391 10.9149 12.9741 12.0489 12.9741C12.7629 12.9741 13.3019 13.1421 13.7289 13.5691L12.9589 14.3391C12.6439 14.0241 12.2309 14.0451 12.0139 14.0451C11.5869 14.0451 11.4119 14.2901 11.4119 14.5071C11.4119 14.5701 11.4329 14.6611 11.5099 14.7381C11.5729 14.8011 11.6779 14.8641 11.8529 14.8851L12.4549 14.9691C12.9029 15.0321 13.2249 15.1721 13.4349 15.3821C13.7009 15.6411 13.8059 16.0121 13.8059 16.4741Z" fill="white"/>
<path d="M18.3124 13.0161L16.6604 18.0001H15.7504L14.1054 13.0161H15.3724L16.2124 15.8021L17.0384 13.0161H18.3124Z" fill="white"/>
</g>
<path opacity="0.5" d="M14 1L21 8H16C14.8954 8 14 7.10457 14 6V1Z" fill="white"/>
<defs>
<filter id="filter0_d_5938_919" x="1" y="0" width="22" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5938_919"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5938_919" result="shape"/>
</filter>
</defs>
</svg>
<svg width="24" height="26" viewBox="0 0 24 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_5938_927)">
<path d="M3 5.8C3 4.11984 3 3.27976 3.32698 2.63803C3.6146 2.07354 4.07354 1.6146 4.63803 1.32698C5.27976 1 6.11984 1 7.8 1H14L21 8V18.2C21 19.8802 21 20.7202 20.673 21.362C20.3854 21.9265 19.9265 22.3854 19.362 22.673C18.7202 23 17.8802 23 16.2 23H7.8C6.11984 23 5.27976 23 4.63803 22.673C4.07354 22.3854 3.6146 21.9265 3.32698 21.362C3 20.7202 3 19.8802 3 18.2V5.8Z" fill="#169951"/>
</g>
<path opacity="0.5" d="M14 1L21 8H16C14.8954 8 14 7.10457 14 6V1Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 12C17.5523 12 18 12.4477 18 13V18C18 18.5523 17.5523 19 17 19H7C6.44772 19 6 18.5523 6 18V13C6 12.4477 6.44772 12 7 12H17ZM11.5 13H7L7 15H11.5V13ZM12.5 18H17V16H12.5V18ZM11.5 16V18H7L7 16H11.5ZM12.5 15H17V13H12.5V15Z" fill="white" fill-opacity="0.96"/>
<defs>
<filter id="filter0_d_5938_927" x="1" y="0" width="22" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.0941176 0 0 0 0 0.156863 0 0 0 0.05 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5938_927"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5938_927" result="shape"/>
</filter>
</defs>
</svg>
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { omit } from 'lodash-es' import { omit } from 'lodash-es'
import cn from 'classnames'
import { ArrowRightIcon } from '@heroicons/react/24/solid' import { ArrowRightIcon } from '@heroicons/react/24/solid'
import { useGetState } from 'ahooks'
import cn from 'classnames'
import SegmentCard from '../completed/SegmentCard' import SegmentCard from '../completed/SegmentCard'
import { FieldInfo } from '../metadata' import { FieldInfo } from '../metadata'
import style from '../completed/style.module.css' import style from '../completed/style.module.css'
...@@ -19,7 +20,7 @@ import type { FullDocumentDetail, ProcessRuleResponse } from '@/models/datasets' ...@@ -19,7 +20,7 @@ import type { FullDocumentDetail, ProcessRuleResponse } from '@/models/datasets'
import type { CommonResponse } from '@/models/common' import type { CommonResponse } from '@/models/common'
import { asyncRunSafe } from '@/utils' import { asyncRunSafe } from '@/utils'
import { formatNumber } from '@/utils/format' import { formatNumber } from '@/utils/format'
import { fetchIndexingEstimate, fetchProcessRule, pauseDocIndexing, resumeDocIndexing } from '@/service/datasets' import { fetchIndexingStatus as doFetchIndexingStatus, fetchIndexingEstimate, fetchProcessRule, pauseDocIndexing, resumeDocIndexing } from '@/service/datasets'
import DatasetDetailContext from '@/context/dataset-detail' import DatasetDetailContext from '@/context/dataset-detail'
import StopEmbeddingModal from '@/app/components/datasets/create/stop-embedding-modal' import StopEmbeddingModal from '@/app/components/datasets/create/stop-embedding-modal'
...@@ -134,11 +135,15 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d ...@@ -134,11 +135,15 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d
} }
const [runId, setRunId, getRunId] = useGetState<any>(null) const [runId, setRunId, getRunId] = useGetState<any>(null)
const stopQueryStatus = () => {
clearInterval(getRunId())
}
const startQueryStatus = () => { const startQueryStatus = () => {
const runId = setInterval(() => { const runId = setInterval(() => {
const indexingStatusDetail = getIndexingStatusDetail() const indexingStatusDetail = getIndexingStatusDetail()
if (indexingStatusDetail?.indexing_status === 'completed') { if (indexingStatusDetail?.indexing_status === 'completed') {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
stopQueryStatus() stopQueryStatus()
return return
} }
...@@ -146,9 +151,6 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d ...@@ -146,9 +151,6 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d
}, 2500) }, 2500)
setRunId(runId) setRunId(runId)
} }
const stopQueryStatus = () => {
clearInterval(getRunId())
}
useEffect(() => { useEffect(() => {
fetchIndexingStatus() fetchIndexingStatus()
...@@ -235,13 +237,14 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d ...@@ -235,13 +237,14 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d
key={idx} key={idx}
className={cn(s.progressBgItem, isEmbedding ? 'bg-primary-50' : 'bg-gray-100')} className={cn(s.progressBgItem, isEmbedding ? 'bg-primary-50' : 'bg-gray-100')}
/>)} />)}
<div className={ <div
cn('rounded-l-md', className={cn(
'rounded-l-md',
s.progressBar, s.progressBar,
(isEmbedding || isEmbeddingCompleted) && s.barProcessing, (isEmbedding || isEmbeddingCompleted) && s.barProcessing,
(isEmbeddingPaused || isEmbeddingError) && s.barPaused, (isEmbeddingPaused || isEmbeddingError) && s.barPaused,
indexingStatusDetail?.indexing_status === 'completed' && 'rounded-r-md') indexingStatusDetail?.indexing_status === 'completed' && 'rounded-r-md',
} )}
style={{ width: `${percent}%` }} style={{ width: `${percent}%` }}
/> />
</div> </div>
......
'use client' 'use client'
import React, { useState, useCallback, useEffect } from 'react' import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks' import { useBoolean } from 'ahooks'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
...@@ -7,7 +7,8 @@ import { useRouter } from 'next/navigation' ...@@ -7,7 +7,8 @@ import { useRouter } from 'next/navigation'
import DatasetDetailContext from '@/context/dataset-detail' import DatasetDetailContext from '@/context/dataset-detail'
import type { FullDocumentDetail } from '@/models/datasets' import type { FullDocumentDetail } from '@/models/datasets'
import { fetchTenantInfo } from '@/service/common' import { fetchTenantInfo } from '@/service/common'
import { fetchDocumentDetail, MetadataType } from '@/service/datasets' import type { MetadataType } from '@/service/datasets'
import { fetchDocumentDetail } from '@/service/datasets'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import StepTwo from '@/app/components/datasets/create/step-two' import StepTwo from '@/app/components/datasets/create/step-two'
...@@ -15,8 +16,8 @@ import AccountSetting from '@/app/components/header/account-setting' ...@@ -15,8 +16,8 @@ import AccountSetting from '@/app/components/header/account-setting'
import AppUnavailable from '@/app/components/base/app-unavailable' import AppUnavailable from '@/app/components/base/app-unavailable'
type DocumentSettingsProps = { type DocumentSettingsProps = {
datasetId: string; datasetId: string
documentId: string; documentId: string
} }
const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => { const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
...@@ -48,18 +49,18 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => { ...@@ -48,18 +49,18 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
const detail = await fetchDocumentDetail({ const detail = await fetchDocumentDetail({
datasetId, datasetId,
documentId, documentId,
params: { metadata: 'without' as MetadataType } params: { metadata: 'without' as MetadataType },
}) })
setDocumentDetail(detail) setDocumentDetail(detail)
} catch (e) { }
catch (e) {
setHasError(true) setHasError(true)
} }
})() })()
}, [datasetId, documentId]) }, [datasetId, documentId])
if (hasError) { if (hasError)
return <AppUnavailable code={500} unknownReason={t('datasetCreation.error.unavailable') as string} /> return <AppUnavailable code={500} unknownReason={t('datasetCreation.error.unavailable') as string} />
}
return ( return (
<div className='flex' style={{ height: 'calc(100vh - 56px)' }}> <div className='flex' style={{ height: 'calc(100vh - 56px)' }}>
......
...@@ -75,6 +75,15 @@ ...@@ -75,6 +75,15 @@
.markdownIcon { .markdownIcon {
background-image: url(./assets/md.svg); background-image: url(./assets/md.svg);
} }
.xlsIcon {
background-image: url(./assets/xlsx.svg);
}
.xlsxIcon {
background-image: url(./assets/xlsx.svg);
}
.csvIcon {
background-image: url(./assets/csv.svg);
}
.statusItemDetail { .statusItemDetail {
@apply h-8 font-medium border border-gray-200 inline-flex items-center rounded-lg pl-3 pr-4 mr-2; @apply h-8 font-medium border border-gray-200 inline-flex items-center rounded-lg pl-3 pr-4 mr-2;
} }
......
...@@ -83,9 +83,9 @@ const Apps: FC = () => { ...@@ -83,9 +83,9 @@ const Apps: FC = () => {
} }
return ( return (
<div className='h-full flex flex-col'> <div className='h-full flex flex-col border-l border-gray-200'>
<div className='shrink-0 pt-6 px-12'> <div className='shrink-0 pt-6 px-12'>
<div className='mb-1 text-primary-600 text-xl font-semibold'>{t('explore.apps.title')}</div> <div className={`mb-1 ${s.textGradient} text-xl font-semibold`}>{t('explore.apps.title')}</div>
<div className='text-gray-500 text-sm'>{t('explore.apps.description')}</div> <div className='text-gray-500 text-sm'>{t('explore.apps.description')}</div>
</div> </div>
<Category <Category
......
.textGradient {
background: linear-gradient(to right, rgba(16, 74, 225, 1) 0, rgba(0, 152, 238, 1) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
}
@media (min-width: 1624px) { @media (min-width: 1624px) {
.appList { .appList {
grid-template-columns: repeat(4, minmax(0, 1fr)) grid-template-columns: repeat(4, minmax(0, 1fr))
......
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1.125C4.64906 1.125 1.125 4.64906 1.125 9C1.125 12.4847 3.37922 15.428 6.50953 16.4714C6.90328 16.5403 7.05094 16.3041 7.05094 16.0973C7.05094 15.9103 7.04109 15.2902 7.04109 14.6306C5.0625 14.9948 4.55062 14.1483 4.39312 13.7053C4.30453 13.4789 3.92062 12.78 3.58594 12.593C3.31031 12.4453 2.91656 12.0811 3.57609 12.0712C4.19625 12.0614 4.63922 12.6422 4.78688 12.8784C5.49563 14.0695 6.62766 13.7348 7.08047 13.5281C7.14937 13.0163 7.35609 12.6717 7.5825 12.4748C5.83031 12.278 3.99938 11.5988 3.99938 8.58656C3.99938 7.73016 4.30453 7.02141 4.80656 6.47016C4.72781 6.27328 4.45219 5.46609 4.88531 4.38328C4.88531 4.38328 5.54484 4.17656 7.05094 5.19047C7.68094 5.01328 8.35031 4.92469 9.01969 4.92469C9.68906 4.92469 10.3584 5.01328 10.9884 5.19047C12.4945 4.16672 13.1541 4.38328 13.1541 4.38328C13.5872 5.46609 13.3116 6.27328 13.2328 6.47016C13.7348 7.02141 14.04 7.72031 14.04 8.58656C14.04 11.6086 12.1992 12.278 10.447 12.4748C10.7325 12.7209 10.9786 13.1934 10.9786 13.9317C10.9786 14.985 10.9688 15.8316 10.9688 16.0973C10.9688 16.3041 11.1164 16.5502 11.5102 16.4714C13.0735 15.9436 14.432 14.9389 15.3943 13.5986C16.3567 12.2583 16.8746 10.65 16.875 9C16.875 4.64906 13.3509 1.125 9 1.125Z" fill="#24292F"/>
</svg>
...@@ -16,9 +16,10 @@ ...@@ -16,9 +16,10 @@
} }
.github-icon { .github-icon {
width: 16px; width: 18px;
height: 16px; height: 18px;
background: url(./assets/github.svg) center center no-repeat; background: url(./assets/github-icon.svg) center center no-repeat;
background-size: contain;
} }
.alpha { .alpha {
......
'use client'
import { useCallback, useEffect, useState } from 'react'
import type { FC } from 'react' import type { FC } from 'react'
import { useState } from 'react' import useSWRInfinite from 'swr/infinite'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { flatten } from 'lodash-es'
import { useRouter, useSelectedLayoutSegment } from 'next/navigation' import { useRouter, useSelectedLayoutSegment } from 'next/navigation'
import classNames from 'classnames' import classNames from 'classnames'
import { CircleStackIcon, PuzzlePieceIcon } from '@heroicons/react/24/outline' import { CircleStackIcon, PuzzlePieceIcon } from '@heroicons/react/24/outline'
...@@ -9,11 +12,12 @@ import Link from 'next/link' ...@@ -9,11 +12,12 @@ import Link from 'next/link'
import AccountDropdown from './account-dropdown' import AccountDropdown from './account-dropdown'
import Nav from './nav' import Nav from './nav'
import s from './index.module.css' import s from './index.module.css'
import type { AppDetailResponse } from '@/models/app' import type { GithubRepo, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import type { LangGeniusVersionResponse, UserProfileResponse } from '@/models/common' import type { AppListResponse } from '@/models/app'
import NewAppDialog from '@/app/(commonLayout)/apps/NewAppDialog' import NewAppDialog from '@/app/(commonLayout)/apps/NewAppDialog'
import { WorkspaceProvider } from '@/context/workspace-context' import { WorkspaceProvider } from '@/context/workspace-context'
import { useDatasetsContext } from '@/context/datasets-context' import { useDatasetsContext } from '@/context/datasets-context'
import { fetchAppList } from '@/service/apps'
const BuildAppsIcon = ({ isSelected }: { isSelected: boolean }) => ( const BuildAppsIcon = ({ isSelected }: { isSelected: boolean }) => (
<svg className='mr-1' width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg className='mr-1' width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
...@@ -22,8 +26,7 @@ const BuildAppsIcon = ({ isSelected }: { isSelected: boolean }) => ( ...@@ -22,8 +26,7 @@ const BuildAppsIcon = ({ isSelected }: { isSelected: boolean }) => (
) )
export type IHeaderProps = { export type IHeaderProps = {
appItems: AppDetailResponse[] curAppId?: string
curApp: AppDetailResponse
userProfile: UserProfileResponse userProfile: UserProfileResponse
onLogout: () => void onLogout: () => void
langeniusVersionInfo: LangGeniusVersionResponse langeniusVersionInfo: LangGeniusVersionResponse
...@@ -38,15 +41,42 @@ const headerEnvClassName: { [k: string]: string } = { ...@@ -38,15 +41,42 @@ const headerEnvClassName: { [k: string]: string } = {
DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]', DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]',
TESTING: 'bg-[#A5F0FC] border-[#67E3F9] text-[#164C63]', TESTING: 'bg-[#A5F0FC] border-[#67E3F9] text-[#164C63]',
} }
const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, langeniusVersionInfo, isBordered }) => { const getKey = (pageIndex: number, previousPageData: AppListResponse) => {
if (!pageIndex || previousPageData.has_more)
return { url: 'apps', params: { page: pageIndex + 1, limit: 30 } }
return null
}
const Header: FC<IHeaderProps> = ({
curAppId,
userProfile,
onLogout,
langeniusVersionInfo,
isBordered,
}) => {
const { t } = useTranslation() const { t } = useTranslation()
const [showNewAppDialog, setShowNewAppDialog] = useState(false) const [showNewAppDialog, setShowNewAppDialog] = useState(false)
const { data: appsData, isLoading, setSize } = useSWRInfinite(curAppId ? getKey : () => null, fetchAppList, { revalidateFirstPage: false })
const { datasets, currentDataset } = useDatasetsContext() const { datasets, currentDataset } = useDatasetsContext()
const router = useRouter() const router = useRouter()
const showEnvTag = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT' const showEnvTag = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT'
const selectedSegment = useSelectedLayoutSegment() const selectedSegment = useSelectedLayoutSegment()
const isPluginsComingSoon = selectedSegment === 'plugins-coming-soon' const isPluginsComingSoon = selectedSegment === 'plugins-coming-soon'
const isExplore = selectedSegment === 'explore' const isExplore = selectedSegment === 'explore'
const [starCount, setStarCount] = useState(0)
useEffect(() => {
globalThis.fetch('https://api.github.com/repos/langgenius/dify').then(res => res.json()).then((data: GithubRepo) => {
setStarCount(data.stargazers_count)
})
}, [])
const appItems = flatten(appsData?.map(appData => appData.data))
const handleLoadmore = useCallback(() => {
if (isLoading)
return
setSize(size => size + 1)
}, [setSize, isLoading])
return ( return (
<div className={classNames( <div className={classNames(
...@@ -59,17 +89,23 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan ...@@ -59,17 +89,23 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
'flex flex-1 items-center justify-between px-4', 'flex flex-1 items-center justify-between px-4',
)}> )}>
<div className='flex items-center'> <div className='flex items-center'>
<Link href="/apps" className='flex items-center mr-3'> <Link href="/apps" className='flex items-center mr-4'>
<div className={s.logo} /> <div className={s.logo} />
</Link> </Link>
{/* Add it when has many stars */} {
<div className=' starCount > 0 && (
flex items-center h-[26px] px-2 bg-white <Link
border border-solid border-[#E5E7EB] rounded-l-[6px] rounded-r-[6px] href='https://github.com/langgenius/dify'
'> target='_blank'
<div className={s.alpha} /> className='flex items-center leading-[18px] border border-gray-200 rounded-md text-xs text-gray-700 font-semibold overflow-hidden'>
<div className='ml-1 text-xs font-semibold text-gray-700'>{t('common.menus.status')}</div> <div className='flex items-center px-2 py-1 bg-gray-100'>
<div className={`${s['github-icon']} mr-1 rounded-full`} />
Star
</div> </div>
<div className='px-2 py-1 bg-white border-l border-gray-200'>{`${starCount}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}</div>
</Link>
)
}
</div> </div>
<div className='flex items-center'> <div className='flex items-center'>
<Link href="/explore/apps" className={classNames( <Link href="/explore/apps" className={classNames(
...@@ -85,7 +121,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan ...@@ -85,7 +121,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
text={t('common.menus.apps')} text={t('common.menus.apps')}
activeSegment={['apps', 'app']} activeSegment={['apps', 'app']}
link='/apps' link='/apps'
curNav={curApp && { id: curApp.id, name: curApp.name, icon: curApp.icon, icon_background: curApp.icon_background }} curNav={appItems.find(appItem => appItem.id === curAppId)}
navs={appItems.map(item => ({ navs={appItems.map(item => ({
id: item.id, id: item.id,
name: item.name, name: item.name,
...@@ -95,6 +131,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan ...@@ -95,6 +131,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
}))} }))}
createText={t('common.menus.newApp')} createText={t('common.menus.newApp')}
onCreate={() => setShowNewAppDialog(true)} onCreate={() => setShowNewAppDialog(true)}
onLoadmore={handleLoadmore}
/> />
<Link href="/plugins-coming-soon" className={classNames( <Link href="/plugins-coming-soon" className={classNames(
navClassName, 'group', navClassName, 'group',
......
...@@ -24,6 +24,7 @@ const Nav = ({ ...@@ -24,6 +24,7 @@ const Nav = ({
navs, navs,
createText, createText,
onCreate, onCreate,
onLoadmore,
}: INavProps) => { }: INavProps) => {
const [hovered, setHovered] = useState(false) const [hovered, setHovered] = useState(false)
const segment = useSelectedLayoutSegment() const segment = useSelectedLayoutSegment()
...@@ -62,6 +63,7 @@ const Nav = ({ ...@@ -62,6 +63,7 @@ const Nav = ({
navs={navs} navs={navs}
createText={createText} createText={createText}
onCreate={onCreate} onCreate={onCreate}
onLoadmore={onLoadmore}
/> />
</> </>
) )
......
'use client' 'use client'
import { Fragment } from 'react' import { useCallback } from 'react'
import { ChevronDownIcon, PlusIcon } from '@heroicons/react/24/solid' import { ChevronDownIcon, PlusIcon } from '@heroicons/react/24/solid'
import { Menu, Transition } from '@headlessui/react' import { Menu } from '@headlessui/react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { debounce } from 'lodash-es'
import Indicator from '../../indicator' import Indicator from '../../indicator'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
...@@ -13,11 +14,12 @@ type NavItem = { ...@@ -13,11 +14,12 @@ type NavItem = {
icon: string icon: string
icon_background: string icon_background: string
} }
export interface INavSelectorProps { export type INavSelectorProps = {
navs: NavItem[] navs: NavItem[]
curNav?: Omit<NavItem, 'link'> curNav?: Omit<NavItem, 'link'>
createText: string createText: string
onCreate: () => void onCreate: () => void
onLoadmore?: () => void
} }
const itemClassName = ` const itemClassName = `
...@@ -25,9 +27,18 @@ const itemClassName = ` ...@@ -25,9 +27,18 @@ const itemClassName = `
rounded-lg font-normal hover:bg-gray-100 cursor-pointer rounded-lg font-normal hover:bg-gray-100 cursor-pointer
` `
const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps) => { const NavSelector = ({ curNav, navs, createText, onCreate, onLoadmore }: INavSelectorProps) => {
const router = useRouter() const router = useRouter()
const handleScroll = useCallback(debounce((e) => {
if (typeof onLoadmore === 'function') {
const { clientHeight, scrollHeight, scrollTop } = e.target
if (clientHeight + scrollTop > scrollHeight - 50)
onLoadmore()
}
}, 50), [])
return ( return (
<div className=""> <div className="">
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
...@@ -46,15 +57,6 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps) ...@@ -46,15 +57,6 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps)
/> />
</Menu.Button> </Menu.Button>
</div> </div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items <Menu.Items
className=" className="
absolute -left-11 right-0 mt-1.5 w-60 max-w-80 absolute -left-11 right-0 mt-1.5 w-60 max-w-80
...@@ -62,9 +64,9 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps) ...@@ -62,9 +64,9 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps)
shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_rgba(0,0,0,0.05)] shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_rgba(0,0,0,0.05)]
" "
> >
<div className="px-1 py-1 overflow-auto" style={{ maxHeight: '50vh' }}> <div className="px-1 py-1 overflow-auto" style={{ maxHeight: '50vh' }} onScroll={handleScroll}>
{ {
navs.map((nav) => ( navs.map(nav => (
<Menu.Item key={nav.id}> <Menu.Item key={nav.id}>
<div className={itemClassName} onClick={() => router.push(nav.link)}> <div className={itemClassName} onClick={() => router.push(nav.link)}>
<div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'> <div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'>
...@@ -98,7 +100,6 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps) ...@@ -98,7 +100,6 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps)
</div> </div>
</Menu.Item> </Menu.Item>
</Menu.Items> </Menu.Items>
</Transition>
</Menu> </Menu>
</div> </div>
) )
......
...@@ -47,7 +47,7 @@ const TextGeneration: FC<IMainProps> = ({ ...@@ -47,7 +47,7 @@ const TextGeneration: FC<IMainProps> = ({
const [appId, setAppId] = useState<string>('') const [appId, setAppId] = useState<string>('')
const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null) const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null)
const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null) const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null)
const [moreLikeThisConifg, setMoreLikeThisConifg] = useState<MoreLikeThisConfig | null>(null) const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig | null>(null)
const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false)
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const [completionRes, setCompletionRes] = useState('') const [completionRes, setCompletionRes] = useState('')
...@@ -193,7 +193,7 @@ const TextGeneration: FC<IMainProps> = ({ ...@@ -193,7 +193,7 @@ const TextGeneration: FC<IMainProps> = ({
prompt_template: '', // placeholder for feture prompt_template: '', // placeholder for feture
prompt_variables, prompt_variables,
} as PromptConfig) } as PromptConfig)
setMoreLikeThisConifg(more_like_this) setMoreLikeThisConfig(more_like_this)
})() })()
}, []) }, [])
...@@ -251,7 +251,7 @@ const TextGeneration: FC<IMainProps> = ({ ...@@ -251,7 +251,7 @@ const TextGeneration: FC<IMainProps> = ({
content={completionRes} content={completionRes}
messageId={messageId} messageId={messageId}
isInWebApp isInWebApp
moreLikeThis={moreLikeThisConifg?.enabled} moreLikeThis={moreLikeThisConfig?.enabled}
onFeedback={handleFeedback} onFeedback={handleFeedback}
feedback={feedback} feedback={feedback}
onSave={handleSaveMessage} onSave={handleSaveMessage}
......
...@@ -138,3 +138,11 @@ button:focus-within { ...@@ -138,3 +138,11 @@ button:focus-within {
color: rgb(28 100 242); color: rgb(28 100 242);
background-color: rgb(235 245 255); background-color: rgb(235 245 255);
} }
/* support safari 14 and below */
.inset-0 {
left: 0;
right: 0;
top: 0;
bottom: 0;
}
\ No newline at end of file
...@@ -743,13 +743,14 @@ ...@@ -743,13 +743,14 @@
.markdown-body pre code { .markdown-body pre code {
font-size: 100%; font-size: 100%;
white-space: pre-wrap !important;
} }
.markdown-body pre>code { .markdown-body pre>code {
padding: 0; padding: 0;
margin: 0; margin: 0;
word-break: normal; word-break: normal;
white-space: pre; white-space: pre-wrap;
background: transparent; background: transparent;
border: 0; border: 0;
} }
......
import { createContext } from 'use-context-selector' import { createContext } from 'use-context-selector'
import type { CompletionParams, Inputs, ModelConfig, PromptConfig, MoreLikeThisConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug' import type { CompletionParams, Inputs, ModelConfig, MoreLikeThisConfig, PromptConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
import type { DataSet } from '@/models/datasets' import type { DataSet } from '@/models/datasets'
type IDebugConfiguration = { type IDebugConfiguration = {
...@@ -15,9 +15,9 @@ type IDebugConfiguration = { ...@@ -15,9 +15,9 @@ type IDebugConfiguration = {
setControlClearChatMessage: (controlClearChatMessage: number) => void setControlClearChatMessage: (controlClearChatMessage: number) => void
prevPromptConfig: PromptConfig prevPromptConfig: PromptConfig
setPrevPromptConfig: (prevPromptConfig: PromptConfig) => void setPrevPromptConfig: (prevPromptConfig: PromptConfig) => void
moreLikeThisConifg: MoreLikeThisConfig, moreLikeThisConfig: MoreLikeThisConfig
setMoreLikeThisConifg: (moreLikeThisConfig: MoreLikeThisConfig) => void setMoreLikeThisConfig: (moreLikeThisConfig: MoreLikeThisConfig) => void
suggestedQuestionsAfterAnswerConfig: SuggestedQuestionsAfterAnswerConfig, suggestedQuestionsAfterAnswerConfig: SuggestedQuestionsAfterAnswerConfig
setSuggestedQuestionsAfterAnswerConfig: (suggestedQuestionsAfterAnswerConfig: SuggestedQuestionsAfterAnswerConfig) => void setSuggestedQuestionsAfterAnswerConfig: (suggestedQuestionsAfterAnswerConfig: SuggestedQuestionsAfterAnswerConfig) => void
formattingChanged: boolean formattingChanged: boolean
setFormattingChanged: (formattingChanged: boolean) => void setFormattingChanged: (formattingChanged: boolean) => void
...@@ -51,10 +51,10 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({ ...@@ -51,10 +51,10 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({
prompt_variables: [], prompt_variables: [],
}, },
setPrevPromptConfig: () => { }, setPrevPromptConfig: () => { },
moreLikeThisConifg: { moreLikeThisConfig: {
enabled: false, enabled: false,
}, },
setMoreLikeThisConifg: () => { }, setMoreLikeThisConfig: () => { },
suggestedQuestionsAfterAnswerConfig: { suggestedQuestionsAfterAnswerConfig: {
enabled: false, enabled: false,
}, },
......
const translation = { const translation = {
pageTitle: "Prompt Engineering", pageTitle: 'Prompt Engineering',
operation: { operation: {
applyConfig: "Publish", applyConfig: 'Publish',
resetConfig: "Reset", resetConfig: 'Reset',
addFeature: "Add Feature", addFeature: 'Add Feature',
stopResponding: "Stop responding", automatic: 'Automatic',
stopResponding: 'Stop responding',
}, },
notSetAPIKey: { notSetAPIKey: {
title: "LLM provider key has not been set", title: 'LLM provider key has not been set',
trailFinished: "Trail finished", trailFinished: 'Trail finished',
description: "The LLM provider key has not been set, and it needs to be set before debugging.", description: 'The LLM provider key has not been set, and it needs to be set before debugging.',
settingBtn: "Go to settings", settingBtn: 'Go to settings',
}, },
trailUseGPT4Info: { trailUseGPT4Info: {
title: 'Does not support gpt-4 now', title: 'Does not support gpt-4 now',
...@@ -19,14 +20,14 @@ const translation = { ...@@ -19,14 +20,14 @@ const translation = {
feature: { feature: {
groupChat: { groupChat: {
title: 'Chat enhance', title: 'Chat enhance',
description: 'Add pre-conversation settings for apps can enhance user experience.' description: 'Add pre-conversation settings for apps can enhance user experience.',
}, },
groupExperience: { groupExperience: {
title: 'Experience enhance', title: 'Experience enhance',
}, },
conversationOpener: { conversationOpener: {
title: "Conversation remakers", title: 'Conversation remakers',
description: "In a chat app, the first sentence that the AI actively speaks to the user is usually used as a welcome." description: 'In a chat app, the first sentence that the AI actively speaks to the user is usually used as a welcome.',
}, },
suggestedQuestionsAfterAnswer: { suggestedQuestionsAfterAnswer: {
title: 'Follow-up', title: 'Follow-up',
...@@ -35,105 +36,122 @@ const translation = { ...@@ -35,105 +36,122 @@ const translation = {
tryToAsk: 'Try to ask', tryToAsk: 'Try to ask',
}, },
moreLikeThis: { moreLikeThis: {
title: "More like this", title: 'More like this',
description: "Generate multiple texts at once, and then edit and continue to generate", description: 'Generate multiple texts at once, and then edit and continue to generate',
generateNumTip: "Number of each generated times", generateNumTip: 'Number of each generated times',
tip: "Using this feature will incur additional tokens overhead" tip: 'Using this feature will incur additional tokens overhead',
}, },
dataSet: { dataSet: {
title: "Context", title: 'Context',
noData: "You can import datasets as context", noData: 'You can import datasets as context',
words: "Words", words: 'Words',
textBlocks: "Text Blocks", textBlocks: 'Text Blocks',
selectTitle: "Select reference dataset", selectTitle: 'Select reference dataset',
selected: "Datasets selected", selected: 'Datasets selected',
noDataSet: "No dataset found", noDataSet: 'No dataset found',
toCreate: "Go to create", toCreate: 'Go to create',
notSupportSelectMulti: 'Currently only support one dataset' notSupportSelectMulti: 'Currently only support one dataset',
} },
},
automatic: {
title: 'Automated application orchestration',
description: 'Describe your scenario, Dify will orchestrate an application for you.',
intendedAudience: 'Who is the intended audience?',
intendedAudiencePlaceHolder: 'e.g. Student',
solveProblem: 'What problems do they hope AI can solve for them?',
solveProblemPlaceHolder: 'e.g. Assessing academic performance',
generate: 'Generate',
audiencesRequired: 'Audiences required',
problemRequired: 'Problem required',
resTitle: 'We have orchestrated the following application for you.',
apply: 'Apply this orchestration',
noData: 'Describe your use case on the left, the orchestration preview will show here.',
loading: 'Orchestrating the application for you...',
overwriteTitle: 'Override existing configuration?',
overwriteMessage: 'Applying this orchestration will override existing configuration.',
}, },
resetConfig: { resetConfig: {
title: "Confirm reset?", title: 'Confirm reset?',
message: message:
"Reset discards changes, restoring the last published configuration.", 'Reset discards changes, restoring the last published configuration.',
}, },
errorMessage: { errorMessage: {
nameOfKeyRequired: "name of the key: {{key}} required", nameOfKeyRequired: 'name of the key: {{key}} required',
valueOfVarRequired: "Variables value can not be empty", valueOfVarRequired: 'Variables value can not be empty',
queryRequired: "Request text is required.", queryRequired: 'Request text is required.',
waitForResponse: waitForResponse:
"Please wait for the response to the previous message to complete.", 'Please wait for the response to the previous message to complete.',
}, },
chatSubTitle: "Pre Prompt", chatSubTitle: 'Pre Prompt',
completionSubTitle: "Prefix Prompt", completionSubTitle: 'Prefix Prompt',
promptTip: promptTip:
"Prompts guide AI responses with instructions and constraints. Insert variables like {{input}}. This prompt won't be visible to users.", 'Prompts guide AI responses with instructions and constraints. Insert variables like {{input}}. This prompt won\'t be visible to users.',
formattingChangedTitle: "Formatting changed", formattingChangedTitle: 'Formatting changed',
formattingChangedText: formattingChangedText:
"Modifying the formatting will reset the debug area, are you sure?", 'Modifying the formatting will reset the debug area, are you sure?',
variableTitle: "Variables", variableTitle: 'Variables',
variableTip: variableTip:
"Users fill variables in a form, automatically replacing variables in the prompt.", 'Users fill variables in a form, automatically replacing variables in the prompt.',
notSetVar: "Variables allow users to introduce prompt words or opening remarks when filling out forms. You can try entering \"{{input}}\" in the prompt words.", notSetVar: 'Variables allow users to introduce prompt words or opening remarks when filling out forms. You can try entering "{{input}}" in the prompt words.',
autoAddVar: "Undefined variables referenced in pre-prompt, are you want to add them in user input form?", autoAddVar: 'Undefined variables referenced in pre-prompt, are you want to add them in user input form?',
variableTable: { variableTable: {
key: "Variable Key", key: 'Variable Key',
name: "User Input Field Name", name: 'User Input Field Name',
optional: "Optional", optional: 'Optional',
type: "Input Type", type: 'Input Type',
action: "Actions", action: 'Actions',
typeString: "String", typeString: 'String',
typeSelect: "Select", typeSelect: 'Select',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: "Variable key can not be empty", canNoBeEmpty: 'Variable key can not be empty',
tooLong: "Variable key: {{key}} too length. Can not be longer then 16 characters", tooLong: 'Variable key: {{key}} too length. Can not be longer then 16 characters',
notValid: "Variable key: {{key}} is invalid. Can only contain letters, numbers, and underscores", notValid: 'Variable key: {{key}} is invalid. Can only contain letters, numbers, and underscores',
notStartWithNumber: "Variable key: {{key}} can not start with a number", notStartWithNumber: 'Variable key: {{key}} can not start with a number',
}, },
variableConig: { variableConig: {
modalTitle: "Field settings", modalTitle: 'Field settings',
description: "Setting for variable {{varName}}", description: 'Setting for variable {{varName}}',
fieldType: 'Field type', fieldType: 'Field type',
string: 'Text', string: 'Text',
select: 'Select', select: 'Select',
notSet: 'Not set, try typing {{input}} in the prefix prompt', notSet: 'Not set, try typing {{input}} in the prefix prompt',
stringTitle: "Form text box options", stringTitle: 'Form text box options',
maxLength: "Max length", maxLength: 'Max length',
options: "Options", options: 'Options',
addOption: "Add option", addOption: 'Add option',
}, },
openingStatement: { openingStatement: {
title: "Opening remarks", title: 'Opening remarks',
add: "Add", add: 'Add',
writeOpner: "Write remarks", writeOpner: 'Write remarks',
placeholder: "Write your remarks message here", placeholder: 'Write your remarks message here',
noDataPlaceHolder: noDataPlaceHolder:
"Starting the conversation with the user can help AI establish a closer connection with them in conversational applications.", 'Starting the conversation with the user can help AI establish a closer connection with them in conversational applications.',
varTip: 'You can use variables, try type {{variable}}', varTip: 'You can use variables, try type {{variable}}',
tooShort: "At least 20 words of initial prompt are required to generate an opening remarks for the conversation.", tooShort: 'At least 20 words of initial prompt are required to generate an opening remarks for the conversation.',
notIncludeKey: "The initial prompt does not include the variable: {{key}}. Please add it to the initial prompt.", notIncludeKey: 'The initial prompt does not include the variable: {{key}}. Please add it to the initial prompt.',
}, },
modelConfig: { modelConfig: {
model: "Model", model: 'Model',
setTone: "Set tone of responses", setTone: 'Set tone of responses',
title: "Model and Parameters", title: 'Model and Parameters',
}, },
inputs: { inputs: {
title: "Debugging and Previewing", title: 'Debugging and Previewing',
noPrompt: "Try write some prompt in pre-prompt input", noPrompt: 'Try write some prompt in pre-prompt input',
userInputField: "User Input Field", userInputField: 'User Input Field',
noVar: "Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started.", noVar: 'Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started.',
chatVarTip: chatVarTip:
"Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started", 'Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started',
completionVarTip: completionVarTip:
"Fill in the value of the variable, which will be automatically replaced in the prompt words every time a question is submitted.", 'Fill in the value of the variable, which will be automatically replaced in the prompt words every time a question is submitted.',
previewTitle: "Prompt preview", previewTitle: 'Prompt preview',
queryTitle: "Query content", queryTitle: 'Query content',
queryPlaceholder: "Please enter the request text.", queryPlaceholder: 'Please enter the request text.',
run: "RUN", run: 'RUN',
}, },
result: "Output Text", result: 'Output Text',
}; }
export default translation; export default translation
const translation = { const translation = {
pageTitle: "提示词编排", pageTitle: '提示词编排',
operation: { operation: {
applyConfig: "发布", applyConfig: '发布',
resetConfig: "重置", resetConfig: '重置',
addFeature: "添加功能", addFeature: '添加功能',
stopResponding: "停止响应", automatic: '自动编排',
stopResponding: '停止响应',
}, },
notSetAPIKey: { notSetAPIKey: {
title: "LLM 提供者的密钥未设置", title: 'LLM 提供者的密钥未设置',
trailFinished: "试用已结束", trailFinished: '试用已结束',
description: "在调试之前需要设置 LLM 提供者的密钥。", description: '在调试之前需要设置 LLM 提供者的密钥。',
settingBtn: "去设置", settingBtn: '去设置',
}, },
trailUseGPT4Info: { trailUseGPT4Info: {
title: '当前不支持使用 gpt-4', title: '当前不支持使用 gpt-4',
...@@ -19,14 +20,14 @@ const translation = { ...@@ -19,14 +20,14 @@ const translation = {
feature: { feature: {
groupChat: { groupChat: {
title: '聊天增强', title: '聊天增强',
description: '为聊天型应用添加预对话设置,可以提升用户体验。' description: '为聊天型应用添加预对话设置,可以提升用户体验。',
}, },
groupExperience: { groupExperience: {
title: '体验增强', title: '体验增强',
}, },
conversationOpener: { conversationOpener: {
title: "对话开场白", title: '对话开场白',
description: "在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。" description: '在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。',
}, },
suggestedQuestionsAfterAnswer: { suggestedQuestionsAfterAnswer: {
title: '下一步问题建议', title: '下一步问题建议',
...@@ -35,100 +36,117 @@ const translation = { ...@@ -35,100 +36,117 @@ const translation = {
tryToAsk: '试着问问', tryToAsk: '试着问问',
}, },
moreLikeThis: { moreLikeThis: {
title: "更多类似的", title: '更多类似的',
description: '一次生成多条文本,可在此基础上编辑并继续生成', description: '一次生成多条文本,可在此基础上编辑并继续生成',
generateNumTip: "每次生成数", generateNumTip: '每次生成数',
tip: "使用此功能将会额外消耗 tokens" tip: '使用此功能将会额外消耗 tokens',
}, },
dataSet: { dataSet: {
title: "上下文", title: '上下文',
noData: "您可以导入数据集作为上下文", noData: '您可以导入数据集作为上下文',
words: "词", words: '词',
textBlocks: "文本块", textBlocks: '文本块',
selectTitle: "选择引用数据集", selectTitle: '选择引用数据集',
selected: "个数据集被选中", selected: '个数据集被选中',
noDataSet: "未找到数据集", noDataSet: '未找到数据集',
toCreate: "去创建", toCreate: '去创建',
notSupportSelectMulti: '目前只支持引用一个数据集' notSupportSelectMulti: '目前只支持引用一个数据集',
} },
},
automatic: {
title: '自动编排',
description: '描述您的场景,Dify 将为您编排一个应用。',
intendedAudience: '目标用户是谁?',
intendedAudiencePlaceHolder: '例如:学生',
solveProblem: '希望 AI 为他们解决什么问题?',
solveProblemPlaceHolder: '例如:评估学业水平',
generate: '生成',
audiencesRequired: '目标用户必填',
problemRequired: '解决问题必填',
resTitle: '我们为您编排了以下应用程序',
apply: '应用',
noData: '在左侧描述您的用例,编排预览将在此处显示。',
loading: '为您编排应用程序中…',
overwriteTitle: '覆盖现有配置?',
overwriteMessage: '应用此编排将覆盖现有配置。',
}, },
resetConfig: { resetConfig: {
title: "确认重置?", title: '确认重置?',
message: "重置将丢失当前页面所有修改,恢复至上次发布时的配置", message: '重置将丢失当前页面所有修改,恢复至上次发布时的配置',
}, },
errorMessage: { errorMessage: {
nameOfKeyRequired: "变量 {{key}} 对应的名称必填", nameOfKeyRequired: '变量 {{key}} 对应的名称必填',
valueOfVarRequired: "变量值必填", valueOfVarRequired: '变量值必填',
queryRequired: "主要文本必填", queryRequired: '主要文本必填',
waitForResponse: "请等待上条信息响应完成", waitForResponse: '请等待上条信息响应完成',
}, },
chatSubTitle: "对话前提示词", chatSubTitle: '对话前提示词',
completionSubTitle: "前缀提示词", completionSubTitle: '前缀提示词',
promptTip: promptTip:
"提示词用于对 AI 的回复做出一系列指令和约束。可插入表单变量,例如 {{input}}。这段提示词不会被最终用户所看到。", '提示词用于对 AI 的回复做出一系列指令和约束。可插入表单变量,例如 {{input}}。这段提示词不会被最终用户所看到。',
formattingChangedTitle: "编排已改变", formattingChangedTitle: '编排已改变',
formattingChangedText: "修改编排将重置调试区域,确定吗?", formattingChangedText: '修改编排将重置调试区域,确定吗?',
variableTitle: "变量", variableTitle: '变量',
notSetVar: "变量能使用户输入表单引入提示词或开场白,你可以试试在提示词中输入输入 {{input}}", notSetVar: '变量能使用户输入表单引入提示词或开场白,你可以试试在提示词中输入输入 {{input}}',
variableTip: variableTip:
"变量将以表单形式让用户在对话前填写,用户填写的表单内容将自动替换提示词中的变量。", '变量将以表单形式让用户在对话前填写,用户填写的表单内容将自动替换提示词中的变量。',
autoAddVar: "提示词中引用了未定义的变量,是否自动添加到用户输入表单中?", autoAddVar: '提示词中引用了未定义的变量,是否自动添加到用户输入表单中?',
variableTable: { variableTable: {
key: "变量 Key", key: '变量 Key',
name: "字段名称", name: '字段名称',
optional: "可选", optional: '可选',
type: "类型", type: '类型',
action: "操作", action: '操作',
typeString: "文本", typeString: '文本',
typeSelect: "下拉选项", typeSelect: '下拉选项',
}, },
varKeyError: { varKeyError: {
canNoBeEmpty: "变量不能为空", canNoBeEmpty: '变量不能为空',
tooLong: "变量: {{key}} 长度太长。不能超过 16 个字符", tooLong: '变量: {{key}} 长度太长。不能超过 16 个字符',
notValid: "变量: {{key}} 非法。只能包含英文字符,数字和下划线", notValid: '变量: {{key}} 非法。只能包含英文字符,数字和下划线',
notStartWithNumber: "变量: {{key}} 不能以数字开头", notStartWithNumber: '变量: {{key}} 不能以数字开头',
}, },
variableConig: { variableConig: {
modalTitle: "变量设置", modalTitle: '变量设置',
description: "设置变量 {{varName}}", description: '设置变量 {{varName}}',
fieldType: '字段类型', fieldType: '字段类型',
string: '文本', string: '文本',
select: '下拉选项', select: '下拉选项',
notSet: '未设置,在 Prompt 中输入 {{input}} 试试', notSet: '未设置,在 Prompt 中输入 {{input}} 试试',
stringTitle: "文本框设置", stringTitle: '文本框设置',
maxLength: "最大长度", maxLength: '最大长度',
options: "选项", options: '选项',
addOption: "添加选项", addOption: '添加选项',
}, },
openingStatement: { openingStatement: {
title: "对话开场白", title: '对话开场白',
add: "添加开场白", add: '添加开场白',
writeOpner: "编写开场白", writeOpner: '编写开场白',
placeholder: "请在这里输入开场白", placeholder: '请在这里输入开场白',
noDataPlaceHolder: noDataPlaceHolder:
"在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。", '在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。',
varTip: '你可以使用变量, 试试输入 {{variable}}', varTip: '你可以使用变量, 试试输入 {{variable}}',
tooShort: "对话前提示词至少 20 字才能生成开场白", tooShort: '对话前提示词至少 20 字才能生成开场白',
notIncludeKey: "前缀提示词中不包含变量 {{key}}。请在前缀提示词中添加该变量", notIncludeKey: '前缀提示词中不包含变量 {{key}}。请在前缀提示词中添加该变量',
}, },
modelConfig: { modelConfig: {
model: "语言模型", model: '语言模型',
setTone: "模型设置", setTone: '模型设置',
title: "模型及参数", title: '模型及参数',
}, },
inputs: { inputs: {
title: "调试与预览", title: '调试与预览',
noPrompt: "尝试在对话前提示框中编写一些提示词", noPrompt: '尝试在对话前提示框中编写一些提示词',
userInputField: "用户输入", userInputField: '用户输入',
noVar: "填入变量的值,每次启动新会话时该变量将自动替换提示词中的变量。", noVar: '填入变量的值,每次启动新会话时该变量将自动替换提示词中的变量。',
chatVarTip: "填入变量的值,该值将在每次开启一个新会话时自动替换到提示词中", chatVarTip: '填入变量的值,该值将在每次开启一个新会话时自动替换到提示词中',
completionVarTip: "填入变量的值,该值将在每次提交问题时自动替换到提示词中", completionVarTip: '填入变量的值,该值将在每次提交问题时自动替换到提示词中',
previewTitle: "提示词预览", previewTitle: '提示词预览',
queryTitle: "查询内容", queryTitle: '查询内容',
queryPlaceholder: "请输入文本内容", queryPlaceholder: '请输入文本内容',
run: "运行", run: '运行',
}, },
result: "结果", result: '结果',
}; }
export default translation; export default translation
...@@ -22,7 +22,7 @@ const translation = { ...@@ -22,7 +22,7 @@ const translation = {
title: 'Upload text file', title: 'Upload text file',
button: 'Drag and drop file, or', button: 'Drag and drop file, or',
browse: 'Browse', browse: 'Browse',
tip: 'Supports txt, html, markdown, and pdf.', tip: 'Supports txt, html, markdown, xlsx, xls, and pdf.',
validation: { validation: {
typeError: 'File type not supported', typeError: 'File type not supported',
size: 'File too large. Maximum is 15MB', size: 'File too large. Maximum is 15MB',
...@@ -102,8 +102,8 @@ const translation = { ...@@ -102,8 +102,8 @@ const translation = {
sideTipContent: 'After the document finishes indexing, the dataset can be integrated into the application as context, you can find the context setting in the prompt orchestration page. You can also create it as an independent ChatGPT indexing plugin for release.', sideTipContent: 'After the document finishes indexing, the dataset can be integrated into the application as context, you can find the context setting in the prompt orchestration page. You can also create it as an independent ChatGPT indexing plugin for release.',
modelTitle: 'Are you sure to stop embedding?', modelTitle: 'Are you sure to stop embedding?',
modelContent: 'If you need to resume processing later, you will continue from where you left off.', modelContent: 'If you need to resume processing later, you will continue from where you left off.',
modelButtonConfirm: "Confirm", modelButtonConfirm: 'Confirm',
modelButtonCancel: 'Cancel' modelButtonCancel: 'Cancel',
}, },
} }
......
...@@ -22,7 +22,7 @@ const translation = { ...@@ -22,7 +22,7 @@ const translation = {
title: '上传文本文件', title: '上传文本文件',
button: '拖拽文件至此,或者', button: '拖拽文件至此,或者',
browse: '选择文件', browse: '选择文件',
tip: '已支持 TXT, HTML, Markdown, PDF', tip: '已支持 TXT, HTML, Markdown, PDF, XLSX, XLS',
validation: { validation: {
typeError: '文件类型不支持', typeError: '文件类型不支持',
size: '文件太大了,不能超过 15MB', size: '文件太大了,不能超过 15MB',
...@@ -101,9 +101,9 @@ const translation = { ...@@ -101,9 +101,9 @@ const translation = {
sideTipTitle: '接下来做什么', sideTipTitle: '接下来做什么',
sideTipContent: '当文档完成索引处理后,数据集即可集成至应用内作为上下文使用,你可以在提示词编排页找到上下文设置。你也可以创建成可独立使用的 ChatGPT 索引插件发布。', sideTipContent: '当文档完成索引处理后,数据集即可集成至应用内作为上下文使用,你可以在提示词编排页找到上下文设置。你也可以创建成可独立使用的 ChatGPT 索引插件发布。',
modelTitle: '确认停止索引过程吗?', modelTitle: '确认停止索引过程吗?',
modelContent:'如果您需要稍后恢复处理,则从停止处继续。', modelContent: '如果您需要稍后恢复处理,则从停止处继续。',
modelButtonConfirm: "确认停止", modelButtonConfirm: '确认停止',
modelButtonCancel: '取消' modelButtonCancel: '取消',
}, },
} }
......
const translation = { const translation = {
list: { list: {
title: "Documents", title: 'Documents',
desc: "All files of the dataset are shown here, and the entire dataset can be linked to Dify citations or indexed via the Chat plugin.", desc: 'All files of the dataset are shown here, and the entire dataset can be linked to Dify citations or indexed via the Chat plugin.',
addFile: "add file", addFile: 'add file',
table: { table: {
header: { header: {
fileName: "FILE NAME", fileName: 'FILE NAME',
words: "WORDS", words: 'WORDS',
hitCount: "HIT COUNT", hitCount: 'HIT COUNT',
uploadTime: "UPLOAD TIME", uploadTime: 'UPLOAD TIME',
status: "STATUS", status: 'STATUS',
action: "ACTION", action: 'ACTION',
}, },
}, },
action: { action: {
uploadFile: 'Upload new file', uploadFile: 'Upload new file',
settings: 'Segment settings', settings: 'Segment settings',
archive: 'Archive', archive: 'Archive',
delete: "Delete", delete: 'Delete',
enableWarning: 'Archived file cannot be enabled', enableWarning: 'Archived file cannot be enabled',
}, },
index: { index: {
...@@ -38,48 +38,48 @@ const translation = { ...@@ -38,48 +38,48 @@ const translation = {
archived: 'Archived', archived: 'Archived',
}, },
empty: { empty: {
title: "There is no documentation yet", title: 'There is no documentation yet',
upload: { upload: {
tip: "You can upload files, sync from the website, or from webb apps like Notion, GitHub, etc.", tip: 'You can upload files, sync from the website, or from webb apps like Notion, GitHub, etc.',
}, },
sync: { sync: {
tip: "Dify will periodically download files from your Notion and complete processing.", tip: 'Dify will periodically download files from your Notion and complete processing.',
}, },
}, },
delete: { delete: {
title: 'Are you sure Delete?', title: 'Are you sure Delete?',
content: 'If you need to resume processing later, you will continue from where you left off' content: 'If you need to resume processing later, you will continue from where you left off',
} },
}, },
metadata: { metadata: {
title: "Metadata", title: 'Metadata',
desc: "Labeling metadata for documents allows AI to access them in a timely manner and exposes the source of references for users.", desc: 'Labeling metadata for documents allows AI to access them in a timely manner and exposes the source of references for users.',
dateTimeFormat: 'MMMM D, YYYY hh:mm A', dateTimeFormat: 'MMMM D, YYYY hh:mm A',
docTypeSelectTitle: "Please select a document type", docTypeSelectTitle: 'Please select a document type',
docTypeChangeTitle: "Change document type", docTypeChangeTitle: 'Change document type',
docTypeSelectWarning: docTypeSelectWarning:
"If the document type is changed, the now filled metadata will no longer be preserved", 'If the document type is changed, the now filled metadata will no longer be preserved',
firstMetaAction: "Let's go", firstMetaAction: 'Let\'s go',
placeholder: { placeholder: {
add: 'Add ', add: 'Add ',
select: 'Select ', select: 'Select ',
}, },
source: { source: {
upload_file: 'Upload File', upload_file: 'Upload File',
notion: "Sync form Notion", notion: 'Sync form Notion',
github: "Sync form Github", github: 'Sync form Github',
}, },
type: { type: {
book: "Book", book: 'Book',
webPage: "Web Page", webPage: 'Web Page',
paper: "Paper", paper: 'Paper',
socialMediaPost: "Social Media Post", socialMediaPost: 'Social Media Post',
personalDocument: "Personal Document", personalDocument: 'Personal Document',
businessDocument: "Business Document", businessDocument: 'Business Document',
IMChat: "IM Chat", IMChat: 'IM Chat',
wikipediaEntry: "Wikipedia Entry", wikipediaEntry: 'Wikipedia Entry',
notion: "Sync form Notion", notion: 'Sync form Notion',
github: "Sync form Github", github: 'Sync form Github',
technicalParameters: 'Technical Parameters', technicalParameters: 'Technical Parameters',
}, },
field: { field: {
...@@ -90,102 +90,102 @@ const translation = { ...@@ -90,102 +90,102 @@ const translation = {
processClean: 'Text Process Clean', processClean: 'Text Process Clean',
}, },
book: { book: {
title: "Title", title: 'Title',
language: "Language", language: 'Language',
author: "Author", author: 'Author',
publisher: "Publisher", publisher: 'Publisher',
publicationDate: "Publication Date", publicationDate: 'Publication Date',
ISBN: "ISBN", ISBN: 'ISBN',
category: "Category", category: 'Category',
}, },
webPage: { webPage: {
title: "Title", title: 'Title',
url: "URL", url: 'URL',
language: "Language", language: 'Language',
authorPublisher: "Author/Publisher", authorPublisher: 'Author/Publisher',
publishDate: "Publish Date", publishDate: 'Publish Date',
topicsKeywords: "Topics/Keywords", topicsKeywords: 'Topics/Keywords',
description: "Description", description: 'Description',
}, },
paper: { paper: {
title: "Title", title: 'Title',
language: "Language", language: 'Language',
author: "Author", author: 'Author',
publishDate: "Publish Date", publishDate: 'Publish Date',
journalConferenceName: "Journal/Conference Name", journalConferenceName: 'Journal/Conference Name',
volumeIssuePage: "Volume/Issue/Page", volumeIssuePage: 'Volume/Issue/Page',
DOI: "DOI", DOI: 'DOI',
topicsKeywords: "Topics/Keywords", topicsKeywords: 'Topics/Keywords',
abstract: "Abstract", abstract: 'Abstract',
}, },
socialMediaPost: { socialMediaPost: {
platform: "Platform", platform: 'Platform',
authorUsername: "Author/Username", authorUsername: 'Author/Username',
publishDate: "Publish Date", publishDate: 'Publish Date',
postURL: "Post URL", postURL: 'Post URL',
topicsTags: "Topics/Tags", topicsTags: 'Topics/Tags',
}, },
personalDocument: { personalDocument: {
title: "Title", title: 'Title',
author: "Author", author: 'Author',
creationDate: "Creation Date", creationDate: 'Creation Date',
lastModifiedDate: "Last Modified Date", lastModifiedDate: 'Last Modified Date',
documentType: "Document Type", documentType: 'Document Type',
tagsCategory: "Tags/Category", tagsCategory: 'Tags/Category',
}, },
businessDocument: { businessDocument: {
title: "Title", title: 'Title',
author: "Author", author: 'Author',
creationDate: "Creation Date", creationDate: 'Creation Date',
lastModifiedDate: "Last Modified Date", lastModifiedDate: 'Last Modified Date',
documentType: "Document Type", documentType: 'Document Type',
departmentTeam: "Department/Team", departmentTeam: 'Department/Team',
}, },
IMChat: { IMChat: {
chatPlatform: "Chat Platform", chatPlatform: 'Chat Platform',
chatPartiesGroupName: "Chat Parties/Group Name", chatPartiesGroupName: 'Chat Parties/Group Name',
participants: "Participants", participants: 'Participants',
startDate: "Start Date", startDate: 'Start Date',
endDate: "End Date", endDate: 'End Date',
topicsKeywords: "Topics/Keywords", topicsKeywords: 'Topics/Keywords',
fileType: "File Type", fileType: 'File Type',
}, },
wikipediaEntry: { wikipediaEntry: {
title: "Title", title: 'Title',
language: "Language", language: 'Language',
webpageURL: "Webpage URL", webpageURL: 'Webpage URL',
editorContributor: "Editor/Contributor", editorContributor: 'Editor/Contributor',
lastEditDate: "Last Edit Date", lastEditDate: 'Last Edit Date',
summaryIntroduction: "Summary/Introduction", summaryIntroduction: 'Summary/Introduction',
}, },
notion: { notion: {
title: "Title", title: 'Title',
language: "Language", language: 'Language',
author: "Author", author: 'Author',
createdTime: "Created Time", createdTime: 'Created Time',
lastModifiedTime: "Last Modified Time", lastModifiedTime: 'Last Modified Time',
url: "URL", url: 'URL',
tag: "Tag", tag: 'Tag',
description: "Description", description: 'Description',
}, },
github: { github: {
repoName: "Repo Name", repoName: 'Repo Name',
repoDesc: "Repo Description", repoDesc: 'Repo Description',
repoOwner: "Repo Owner", repoOwner: 'Repo Owner',
fileName: "File Name", fileName: 'File Name',
filePath: "File Path", filePath: 'File Path',
programmingLang: "Programming Language", programmingLang: 'Programming Language',
url: "URL", url: 'URL',
license: "License", license: 'License',
lastCommitTime: "Last Commit Time", lastCommitTime: 'Last Commit Time',
lastCommitAuthor: "Last Commit Author", lastCommitAuthor: 'Last Commit Author',
}, },
originInfo: { originInfo: {
originalFilename: "Original filename", originalFilename: 'Original filename',
originalFileSize: "Original file size", originalFileSize: 'Original file size',
uploadDate: "Upload date", uploadDate: 'Upload date',
lastUpdateDate: "Last update date", lastUpdateDate: 'Last update date',
source: "Source", source: 'Source',
}, },
technicalParameters: { technicalParameters: {
segmentSpecification: 'Segment specification', segmentSpecification: 'Segment specification',
...@@ -194,92 +194,92 @@ const translation = { ...@@ -194,92 +194,92 @@ const translation = {
paragraphs: 'Paragraphs', paragraphs: 'Paragraphs',
hitCount: 'Hit count', hitCount: 'Hit count',
embeddingTime: 'Embedding time', embeddingTime: 'Embedding time',
embeddedSpend: 'Embedded spend' embeddedSpend: 'Embedded spend',
} },
}, },
languageMap: { languageMap: {
zh: "Chinese", zh: 'Chinese',
en: "English", en: 'English',
es: "Spanish", es: 'Spanish',
fr: "French", fr: 'French',
de: "German", de: 'German',
ja: "Japanese", ja: 'Japanese',
ko: "Korean", ko: 'Korean',
ru: "Russian", ru: 'Russian',
ar: "Arabic", ar: 'Arabic',
pt: "Portuguese", pt: 'Portuguese',
it: "Italian", it: 'Italian',
nl: "Dutch", nl: 'Dutch',
pl: "Polish", pl: 'Polish',
sv: "Swedish", sv: 'Swedish',
tr: "Turkish", tr: 'Turkish',
he: "Hebrew", he: 'Hebrew',
hi: "Hindi", hi: 'Hindi',
da: "Danish", da: 'Danish',
fi: "Finnish", fi: 'Finnish',
no: "Norwegian", no: 'Norwegian',
hu: "Hungarian", hu: 'Hungarian',
el: "Greek", el: 'Greek',
cs: "Czech", cs: 'Czech',
th: "Thai", th: 'Thai',
id: "Indonesian", id: 'Indonesian',
}, },
categoryMap: { categoryMap: {
book: { book: {
fiction: "Fiction", fiction: 'Fiction',
biography: "Biography", biography: 'Biography',
history: "History", history: 'History',
science: "Science", science: 'Science',
technology: "Technology", technology: 'Technology',
education: "Education", education: 'Education',
philosophy: "Philosophy", philosophy: 'Philosophy',
religion: "Religion", religion: 'Religion',
socialSciences: "SocialSciences", socialSciences: 'SocialSciences',
art: "Art", art: 'Art',
travel: "Travel", travel: 'Travel',
health: "Health", health: 'Health',
selfHelp: "SelfHelp", selfHelp: 'SelfHelp',
businessEconomics: "BusinessEconomics", businessEconomics: 'BusinessEconomics',
cooking: "Cooking", cooking: 'Cooking',
childrenYoungAdults: "ChildrenYoungAdults", childrenYoungAdults: 'ChildrenYoungAdults',
comicsGraphicNovels: "ComicsGraphicNovels", comicsGraphicNovels: 'ComicsGraphicNovels',
poetry: "Poetry", poetry: 'Poetry',
drama: "Drama", drama: 'Drama',
other: "Other", other: 'Other',
}, },
personalDoc: { personalDoc: {
notes: "Notes", notes: 'Notes',
blogDraft: "Blog Draft", blogDraft: 'Blog Draft',
diary: "Diary", diary: 'Diary',
researchReport: "Research Report", researchReport: 'Research Report',
bookExcerpt: "Book Excerpt", bookExcerpt: 'Book Excerpt',
schedule: "Schedule", schedule: 'Schedule',
list: "List", list: 'List',
projectOverview: "Project Overview", projectOverview: 'Project Overview',
photoCollection: "Photo Collection", photoCollection: 'Photo Collection',
creativeWriting: "Creative Writing", creativeWriting: 'Creative Writing',
codeSnippet: "Code Snippet", codeSnippet: 'Code Snippet',
designDraft: "Design Draft", designDraft: 'Design Draft',
personalResume: "Personal Resume", personalResume: 'Personal Resume',
other: "Other", other: 'Other',
}, },
businessDoc: { businessDoc: {
meetingMinutes: "Meeting Minutes", meetingMinutes: 'Meeting Minutes',
researchReport: "Research Report", researchReport: 'Research Report',
proposal: "Proposal", proposal: 'Proposal',
employeeHandbook: "Employee Handbook", employeeHandbook: 'Employee Handbook',
trainingMaterials: "Training Materials", trainingMaterials: 'Training Materials',
requirementsDocument: "Requirements Document", requirementsDocument: 'Requirements Document',
designDocument: "Design Document", designDocument: 'Design Document',
productSpecification: "Product Specification", productSpecification: 'Product Specification',
financialReport: "Financial Report", financialReport: 'Financial Report',
marketAnalysis: "Market Analysis", marketAnalysis: 'Market Analysis',
projectPlan: "Project Plan", projectPlan: 'Project Plan',
teamStructure: "Team Structure", teamStructure: 'Team Structure',
policiesProcedures: "Policies & Procedures", policiesProcedures: 'Policies & Procedures',
contractsAgreements: "Contracts & Agreements", contractsAgreements: 'Contracts & Agreements',
emailCorrespondence: "Email Correspondence", emailCorrespondence: 'Email Correspondence',
other: "Other", other: 'Other',
}, },
}, },
}, },
...@@ -300,7 +300,7 @@ const translation = { ...@@ -300,7 +300,7 @@ const translation = {
resume: 'Resume processing', resume: 'Resume processing',
automatic: 'Automatic', automatic: 'Automatic',
custom: 'Custom', custom: 'Custom',
previewTip: 'Paragraph preview will be available after embedding is complete' previewTip: 'Paragraph preview will be available after embedding is complete',
}, },
segment: { segment: {
paragraphs: 'Paragraphs', paragraphs: 'Paragraphs',
...@@ -308,7 +308,7 @@ const translation = { ...@@ -308,7 +308,7 @@ const translation = {
characters: 'characters', characters: 'characters',
hitCount: 'hit count', hitCount: 'hit count',
vectorHash: 'Vector hash: ', vectorHash: 'Vector hash: ',
} },
}; }
export default translation; export default translation
const translation = { const translation = {
list: { list: {
title: "文档", title: '文档',
desc: "数据集的所有文件都在这里显示,整个数据集都可以链接到 Dify 引用或通过 Chat 插件进行索引。", desc: '数据集的所有文件都在这里显示,整个数据集都可以链接到 Dify 引用或通过 Chat 插件进行索引。',
addFile: "添加文件", addFile: '添加文件',
table: { table: {
header: { header: {
fileName: "文件名", fileName: '文件名',
words: "字符数", words: '字符数',
hitCount: "命中次数", hitCount: '命中次数',
uploadTime: "上传时间", uploadTime: '上传时间',
status: "状态", status: '状态',
action: "操作", action: '操作',
}, },
}, },
action: { action: {
uploadFile: '上传新文件', uploadFile: '上传新文件',
settings: '分段设置', settings: '分段设置',
archive: '归档', archive: '归档',
delete: "删除", delete: '删除',
enableWarning: '归档的文件无法启用', enableWarning: '归档的文件无法启用',
}, },
index: { index: {
...@@ -38,47 +38,47 @@ const translation = { ...@@ -38,47 +38,47 @@ const translation = {
archived: '已归档', archived: '已归档',
}, },
empty: { empty: {
title: "还没有文档", title: '还没有文档',
upload: { upload: {
tip: "您可以上传文件,从网站同步,或者从网络应用程序(如概念、GitHub 等)同步。", tip: '您可以上传文件,从网站同步,或者从网络应用程序(如概念、GitHub 等)同步。',
}, },
sync: { sync: {
tip: "Dify 会定期从您的 Notion 中下载文件并完成处理。", tip: 'Dify 会定期从您的 Notion 中下载文件并完成处理。',
}, },
}, },
delete: { delete: {
title: '确定删除吗?', title: '确定删除吗?',
content: '如果您需要稍后恢复处理,您将从您离开的地方继续' content: '如果您需要稍后恢复处理,您将从您离开的地方继续',
} },
}, },
metadata: { metadata: {
title: "元数据", title: '元数据',
desc: "标记文档的元数据允许 AI 及时访问它们并为用户公开参考来源。", desc: '标记文档的元数据允许 AI 及时访问它们并为用户公开参考来源。',
dateTimeFormat: 'YYYY-MM-DD HH:mm', dateTimeFormat: 'YYYY-MM-DD HH:mm',
docTypeSelectTitle: "请选择一种文档类型", docTypeSelectTitle: '请选择一种文档类型',
docTypeChangeTitle: "更换文档类型", docTypeChangeTitle: '更换文档类型',
docTypeSelectWarning: "如果更改文档类型,将不再保留现在填充的元数据", docTypeSelectWarning: '如果更改文档类型,将不再保留现在填充的元数据',
firstMetaAction: "开始", firstMetaAction: '开始',
placeholder: { placeholder: {
add: '输入', add: '输入',
select: '选择', select: '选择',
}, },
source: { source: {
upload_file: '文件上传', upload_file: '文件上传',
notion: "从 Notion 同步的文档", notion: '从 Notion 同步的文档',
github: "从 Github 同步的代码", github: '从 Github 同步的代码',
}, },
type: { type: {
book: "书籍", book: '书籍',
webPage: "网页", webPage: '网页',
paper: "论文", paper: '论文',
socialMediaPost: "社交媒体帖子", socialMediaPost: '社交媒体帖子',
personalDocument: "个人文档", personalDocument: '个人文档',
businessDocument: "商务文档", businessDocument: '商务文档',
IMChat: "IM 聊天记录", IMChat: 'IM 聊天记录',
wikipediaEntry: "维基百科条目", wikipediaEntry: '维基百科条目',
notion: "从 Notion 同步的文档", notion: '从 Notion 同步的文档',
github: "从 Github 同步的代码", github: '从 Github 同步的代码',
technicalParameters: '技术参数', technicalParameters: '技术参数',
}, },
field: { field: {
...@@ -89,102 +89,102 @@ const translation = { ...@@ -89,102 +89,102 @@ const translation = {
processClean: '文本预处理与清洗', processClean: '文本预处理与清洗',
}, },
book: { book: {
title: "标题", title: '标题',
language: "语言", language: '语言',
author: "作者", author: '作者',
publisher: "出版商", publisher: '出版商',
publicationDate: "出版日期", publicationDate: '出版日期',
ISBN: "ISBN", ISBN: 'ISBN',
category: "类别", category: '类别',
}, },
webPage: { webPage: {
title: "标题", title: '标题',
url: "网址", url: '网址',
language: "语言", language: '语言',
authorPublisher: "作者/出版商", authorPublisher: '作者/出版商',
publishDate: "发布日期", publishDate: '发布日期',
topicsKeywords: "主题/关键词", topicsKeywords: '主题/关键词',
description: "描述", description: '描述',
}, },
paper: { paper: {
title: "标题", title: '标题',
language: "语言", language: '语言',
author: "作者", author: '作者',
publishDate: "发布日期", publishDate: '发布日期',
journalConferenceName: "期刊/会议名称", journalConferenceName: '期刊/会议名称',
volumeIssuePage: "卷/期/页码", volumeIssuePage: '卷/期/页码',
DOI: "DOI", DOI: 'DOI',
topicsKeywords: "主题/关键词", topicsKeywords: '主题/关键词',
abstract: "摘要", abstract: '摘要',
}, },
socialMediaPost: { socialMediaPost: {
platform: "平台", platform: '平台',
authorUsername: "作者/用户名", authorUsername: '作者/用户名',
publishDate: "发布日期", publishDate: '发布日期',
postURL: "帖子网址", postURL: '帖子网址',
topicsTags: "主题/标签", topicsTags: '主题/标签',
}, },
personalDocument: { personalDocument: {
title: "标题", title: '标题',
author: "作者", author: '作者',
creationDate: "创建日期", creationDate: '创建日期',
lastModifiedDate: "最后修改日期", lastModifiedDate: '最后修改日期',
documentType: "文档类型", documentType: '文档类型',
tagsCategory: "标签/类别", tagsCategory: '标签/类别',
}, },
businessDocument: { businessDocument: {
title: "标题", title: '标题',
author: "作者", author: '作者',
creationDate: "创建日期", creationDate: '创建日期',
lastModifiedDate: "最后修改日期", lastModifiedDate: '最后修改日期',
documentType: "文档类型", documentType: '文档类型',
departmentTeam: "部门/团队", departmentTeam: '部门/团队',
}, },
IMChat: { IMChat: {
chatPlatform: "聊天平台", chatPlatform: '聊天平台',
chatPartiesGroupName: "聊天参与方/群组名称", chatPartiesGroupName: '聊天参与方/群组名称',
participants: "参与者", participants: '参与者',
startDate: "开始日期", startDate: '开始日期',
endDate: "结束日期", endDate: '结束日期',
topicsKeywords: "主题/关键词", topicsKeywords: '主题/关键词',
fileType: "文件类型", fileType: '文件类型',
}, },
wikipediaEntry: { wikipediaEntry: {
title: "标题", title: '标题',
language: "语言", language: '语言',
webpageURL: "网页网址", webpageURL: '网页网址',
editorContributor: "编辑/贡献者", editorContributor: '编辑/贡献者',
lastEditDate: "最后编辑日期", lastEditDate: '最后编辑日期',
summaryIntroduction: "摘要/介绍", summaryIntroduction: '摘要/介绍',
}, },
notion: { notion: {
title: "标题", title: '标题',
language: "语言", language: '语言',
author: "作者", author: '作者',
createdTime: "创建时间", createdTime: '创建时间',
lastModifiedTime: "最后修改时间", lastModifiedTime: '最后修改时间',
url: "网址", url: '网址',
tag: "标签", tag: '标签',
description: "描述", description: '描述',
}, },
github: { github: {
repoName: "仓库名", repoName: '仓库名',
repoDesc: "仓库描述", repoDesc: '仓库描述',
repoOwner: "仓库所有者", repoOwner: '仓库所有者',
fileName: "文件名", fileName: '文件名',
filePath: "文件路径", filePath: '文件路径',
programmingLang: "编程语言", programmingLang: '编程语言',
url: "网址", url: '网址',
license: "许可证", license: '许可证',
lastCommitTime: "最后提交时间", lastCommitTime: '最后提交时间',
lastCommitAuthor: "最后提交者", lastCommitAuthor: '最后提交者',
}, },
originInfo: { originInfo: {
originalFilename: "原始文件名称", originalFilename: '原始文件名称',
originalFileSize: "原始文件大小", originalFileSize: '原始文件大小',
uploadDate: "上传日期", uploadDate: '上传日期',
lastUpdateDate: "最后更新日期", lastUpdateDate: '最后更新日期',
source: "来源", source: '来源',
}, },
technicalParameters: { technicalParameters: {
segmentSpecification: '分段规则', segmentSpecification: '分段规则',
...@@ -194,91 +194,91 @@ const translation = { ...@@ -194,91 +194,91 @@ const translation = {
hitCount: '命中次数', hitCount: '命中次数',
embeddingTime: '嵌入时间', embeddingTime: '嵌入时间',
embeddedSpend: '嵌入花费', embeddedSpend: '嵌入花费',
} },
}, },
languageMap: { languageMap: {
zh: "中文", zh: '中文',
en: "英文", en: '英文',
es: "西班牙语", es: '西班牙语',
fr: "法语", fr: '法语',
de: "德语", de: '德语',
ja: "日语", ja: '日语',
ko: "韩语", ko: '韩语',
ru: "俄语", ru: '俄语',
ar: "阿拉伯语", ar: '阿拉伯语',
pt: "葡萄牙语", pt: '葡萄牙语',
it: "意大利语", it: '意大利语',
nl: "荷兰语", nl: '荷兰语',
pl: "波兰语", pl: '波兰语',
sv: "瑞典语", sv: '瑞典语',
tr: "土耳其语", tr: '土耳其语',
he: "希伯来语", he: '希伯来语',
hi: "印地语", hi: '印地语',
da: "丹麦语", da: '丹麦语',
fi: "芬兰语", fi: '芬兰语',
no: "挪威语", no: '挪威语',
hu: "匈牙利语", hu: '匈牙利语',
el: "希腊语", el: '希腊语',
cs: "捷克语", cs: '捷克语',
th: "泰语", th: '泰语',
id: "印度尼西亚语", id: '印度尼西亚语',
}, },
categoryMap: { categoryMap: {
book: { book: {
fiction: "小说", fiction: '小说',
biography: "传记", biography: '传记',
history: "历史", history: '历史',
science: "科学", science: '科学',
technology: "技术", technology: '技术',
education: "教育", education: '教育',
philosophy: "哲学", philosophy: '哲学',
religion: "宗教", religion: '宗教',
socialSciences: "社会科学", socialSciences: '社会科学',
art: "艺术", art: '艺术',
travel: "旅行", travel: '旅行',
health: "健康", health: '健康',
selfHelp: "自助", selfHelp: '自助',
businessEconomics: "商业/经济", businessEconomics: '商业/经济',
cooking: "烹饪", cooking: '烹饪',
childrenYoungAdults: "儿童/青少年", childrenYoungAdults: '儿童/青少年',
comicsGraphicNovels: "漫画/图形小说", comicsGraphicNovels: '漫画/图形小说',
poetry: "诗歌", poetry: '诗歌',
drama: "戏剧", drama: '戏剧',
other: "其他", other: '其他',
}, },
personalDoc: { personalDoc: {
notes: "笔记", notes: '笔记',
blogDraft: "博客草稿", blogDraft: '博客草稿',
diary: "日记", diary: '日记',
researchReport: "研究报告", researchReport: '研究报告',
bookExcerpt: "书籍摘录", bookExcerpt: '书籍摘录',
schedule: "日程安排", schedule: '日程安排',
list: "列表", list: '列表',
projectOverview: "项目概述", projectOverview: '项目概述',
photoCollection: "照片集", photoCollection: '照片集',
creativeWriting: "创意写作", creativeWriting: '创意写作',
codeSnippet: "代码片段", codeSnippet: '代码片段',
designDraft: "设计草稿", designDraft: '设计草稿',
personalResume: "个人简历", personalResume: '个人简历',
other: "其他", other: '其他',
}, },
businessDoc: { businessDoc: {
meetingMinutes: "会议纪要", meetingMinutes: '会议纪要',
researchReport: "研究报告", researchReport: '研究报告',
proposal: "提案", proposal: '提案',
employeeHandbook: "员工手册", employeeHandbook: '员工手册',
trainingMaterials: "培训材料", trainingMaterials: '培训材料',
requirementsDocument: "需求文档", requirementsDocument: '需求文档',
designDocument: "设计文档", designDocument: '设计文档',
productSpecification: "产品规格", productSpecification: '产品规格',
financialReport: "财务报告", financialReport: '财务报告',
marketAnalysis: "市场分析", marketAnalysis: '市场分析',
projectPlan: "项目计划", projectPlan: '项目计划',
teamStructure: "团队结构", teamStructure: '团队结构',
policiesProcedures: "政策和流程", policiesProcedures: '政策和流程',
contractsAgreements: "合同和协议", contractsAgreements: '合同和协议',
emailCorrespondence: "邮件往来", emailCorrespondence: '邮件往来',
other: "其他", other: '其他',
}, },
}, },
}, },
...@@ -299,7 +299,7 @@ const translation = { ...@@ -299,7 +299,7 @@ const translation = {
resume: '恢复处理', resume: '恢复处理',
automatic: '自动', automatic: '自动',
custom: '自定义', custom: '自定义',
previewTip: '段落预览将在嵌入完成后可用' previewTip: '段落预览将在嵌入完成后可用',
}, },
segment: { segment: {
paragraphs: '段落', paragraphs: '段落',
...@@ -307,7 +307,7 @@ const translation = { ...@@ -307,7 +307,7 @@ const translation = {
characters: '字符', characters: '字符',
hitCount: '命中次数', hitCount: '命中次数',
vectorHash: '向量哈希:', vectorHash: '向量哈希:',
} },
}; }
export default translation; export default translation
...@@ -120,3 +120,7 @@ export type DataSourceNotion = { ...@@ -120,3 +120,7 @@ export type DataSourceNotion = {
is_bound: boolean is_bound: boolean
source_info: DataSourceNotionWorkspace source_info: DataSourceNotionWorkspace
} }
export type GithubRepo = {
stargazers_count: number
}
export type Inputs = Record<string, string | number | object> export type Inputs = Record<string, string | number | object>
export type PromptVariable = { export type PromptVariable = {
key: string, key: string
name: string, name: string
type: string, // "string" | "number" | "select", type: string // "string" | "number" | "select",
default?: string | number, default?: string | number
required: boolean, required: boolean
options?: string[] options?: string[]
max_length?: number max_length?: number
} }
export type CompletionParams = { export type CompletionParams = {
max_tokens: number, max_tokens: number
temperature: number, temperature: number
top_p: number, top_p: number
presence_penalty: number, presence_penalty: number
frequency_penalty: number, frequency_penalty: number
} }
export type ModelId = "gpt-3.5-turbo" | "text-davinci-003" export type ModelId = 'gpt-3.5-turbo' | 'text-davinci-003'
export type PromptConfig = { export type PromptConfig = {
prompt_template: string, prompt_template: string
prompt_variables: PromptVariable[], prompt_variables: PromptVariable[]
} }
export type MoreLikeThisConfig = { export type MoreLikeThisConfig = {
...@@ -33,83 +33,88 @@ export type SuggestedQuestionsAfterAnswerConfig = MoreLikeThisConfig ...@@ -33,83 +33,88 @@ export type SuggestedQuestionsAfterAnswerConfig = MoreLikeThisConfig
// frontend use. Not the same as backend // frontend use. Not the same as backend
export type ModelConfig = { export type ModelConfig = {
provider: string, // LLM Provider: for example "OPENAI" provider: string // LLM Provider: for example "OPENAI"
model_id: string, model_id: string
configs: PromptConfig configs: PromptConfig
opening_statement: string | null
more_like_this: {
enabled: boolean
} | null
suggested_questions_after_answer: {
enabled: boolean
} | null
dataSets: any[]
} }
export type DebugRequestBody = { export type DebugRequestBody = {
inputs: Inputs, inputs: Inputs
query: string, query: string
completion_params: CompletionParams, completion_params: CompletionParams
model_config: ModelConfig model_config: ModelConfig
} }
export type DebugResponse = { export type DebugResponse = {
id: string, id: string
answer: string, answer: string
created_at: string, created_at: string
} }
export type DebugResponseStream = { export type DebugResponseStream = {
id: string, id: string
data: string, data: string
created_at: string, created_at: string
} }
export type FeedBackRequestBody = { export type FeedBackRequestBody = {
message_id: string, message_id: string
rating: 'like' | 'dislike', rating: 'like' | 'dislike'
content?: string, content?: string
from_source: 'api' | 'log' from_source: 'api' | 'log'
} }
export type FeedBackResponse = { export type FeedBackResponse = {
message_id: string, message_id: string
rating: 'like' | 'dislike' rating: 'like' | 'dislike'
} }
// Log session list // Log session list
export type LogSessionListQuery = { export type LogSessionListQuery = {
keyword?: string, keyword?: string
start?: string, // format datetime(YYYY-mm-dd HH:ii) start?: string // format datetime(YYYY-mm-dd HH:ii)
end?: string, // format datetime(YYYY-mm-dd HH:ii) end?: string // format datetime(YYYY-mm-dd HH:ii)
page: number, page: number
limit: number, // default 20. 1-100 limit: number // default 20. 1-100
} }
export type LogSessionListResponse = { export type LogSessionListResponse = {
data: { data: {
id: string, id: string
conversation_id: string, conversation_id: string
query: string, // user's query question query: string // user's query question
message: string, // prompt send to LLM message: string // prompt send to LLM
answer: string, answer: string
creat_at: string, creat_at: string
}[], }[]
total: number, total: number
page: number, page: number
} }
// log session detail and debug // log session detail and debug
export type LogSessionDetailResponse = { export type LogSessionDetailResponse = {
id: string, id: string
cnversation_id: string, cnversation_id: string
model_provider: string, model_provider: string
query: string, query: string
inputs: Record<string, string | number | object>[], inputs: Record<string, string | number | object>[]
message: string, message: string
message_tokens: number, // number of tokens in message message_tokens: number // number of tokens in message
answer: string, answer: string
answer_tokens: number, // number of tokens in answer answer_tokens: number // number of tokens in answer
provider_response_latency: number, // used time in ms provider_response_latency: number // used time in ms
from_source: 'api' | 'log', from_source: 'api' | 'log'
} }
export type SavedMessage = { export type SavedMessage = {
id: string, id: string
answer: string answer: string
} }
const { withSentryConfig } = require('@sentry/nextjs') const { withSentryConfig } = require('@sentry/nextjs')
const EDITION = process.env.NEXT_PUBLIC_EDITION
const IS_CE_EDITION = EDITION === 'SELF_HOSTED'
const isDevelopment = process.env.NODE_ENV === 'development' const isDevelopment = process.env.NODE_ENV === 'development'
const isHideSentry = isDevelopment || IS_CE_EDITION const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN
const SENTRY_ORG = process.env.NEXT_PUBLIC_SENTRY_ORG
const SENTRY_PROJECT = process.env.NEXT_PUBLIC_SENTRY_PROJECT
const isHideSentry = isDevelopment || !SENTRY_DSN || !SENTRY_ORG || !SENTRY_PROJECT
const withMDX = require('@next/mdx')({ const withMDX = require('@next/mdx')({
extension: /\.mdx?$/, extension: /\.mdx?$/,
...@@ -56,8 +57,8 @@ const nextConfig = { ...@@ -56,8 +57,8 @@ const nextConfig = {
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup
const sentryWebpackPluginOptions = { const sentryWebpackPluginOptions = {
org: 'perfectworld', org: SENTRY_ORG,
project: 'javascript-nextjs', project: SENTRY_PROJECT,
silent: true, // Suppresses all logs silent: true, // Suppresses all logs
sourcemaps: { sourcemaps: {
assets: './**', assets: './**',
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
"prepare": "cd ../ && husky install ./web/.husky" "prepare": "cd ../ && husky install ./web/.husky"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.22.3",
"@emoji-mart/data": "^1.1.2", "@emoji-mart/data": "^1.1.2",
"@formatjs/intl-localematcher": "^0.2.32", "@formatjs/intl-localematcher": "^0.2.32",
"@headlessui/react": "^1.7.13", "@headlessui/react": "^1.7.13",
...@@ -20,6 +21,8 @@ ...@@ -20,6 +21,8 @@
"@mdx-js/react": "^2.3.0", "@mdx-js/react": "^2.3.0",
"@next/mdx": "^13.2.4", "@next/mdx": "^13.2.4",
"@sentry/nextjs": "^7.53.1", "@sentry/nextjs": "^7.53.1",
"@sentry/utils": "^7.54.0",
"@tailwindcss/typography": "^0.5.9",
"@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/line-clamp": "^0.4.2",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/lodash-es": "^4.17.7", "@types/lodash-es": "^4.17.7",
...@@ -30,6 +33,7 @@ ...@@ -30,6 +33,7 @@
"@types/react-syntax-highlighter": "^15.5.6", "@types/react-syntax-highlighter": "^15.5.6",
"@types/react-window": "^1.8.5", "@types/react-window": "^1.8.5",
"@types/react-window-infinite-loader": "^1.0.6", "@types/react-window-infinite-loader": "^1.0.6",
"autoprefixer": "^10.4.14",
"ahooks": "^3.7.5", "ahooks": "^3.7.5",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"copy-to-clipboard": "^3.3.3", "copy-to-clipboard": "^3.3.3",
...@@ -40,10 +44,12 @@ ...@@ -40,10 +44,12 @@
"emoji-mart": "^5.5.2", "emoji-mart": "^5.5.2",
"eslint": "8.36.0", "eslint": "8.36.0",
"eslint-config-next": "13.2.4", "eslint-config-next": "13.2.4",
"husky": "^8.0.3",
"i18next": "^22.4.13", "i18next": "^22.4.13",
"i18next-resources-to-backend": "^1.1.3", "i18next-resources-to-backend": "^1.1.3",
"immer": "^9.0.19", "immer": "^9.0.19",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"katex": "^0.16.7",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"negotiator": "^0.6.3", "negotiator": "^0.6.3",
"next": "13.2.4", "next": "13.2.4",
...@@ -68,23 +74,20 @@ ...@@ -68,23 +74,20 @@
"scheduler": "^0.23.0", "scheduler": "^0.23.0",
"server-only": "^0.0.1", "server-only": "^0.0.1",
"swr": "^2.1.0", "swr": "^2.1.0",
"tailwindcss": "^3.2.7",
"typescript": "4.9.5", "typescript": "4.9.5",
"use-context-selector": "^1.4.1" "use-context-selector": "^1.4.1"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^0.36.0", "@antfu/eslint-config": "^0.36.0",
"@faker-js/faker": "^7.6.0", "@faker-js/faker": "^7.6.0",
"@tailwindcss/typography": "^0.5.9",
"@types/js-cookie": "^3.0.3", "@types/js-cookie": "^3.0.3",
"@types/negotiator": "^0.6.1", "@types/negotiator": "^0.6.1",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"autoprefixer": "^10.4.14",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.3",
"lint-staged": "^13.2.2", "lint-staged": "^13.2.2",
"miragejs": "^0.1.47", "miragejs": "^0.1.47",
"postcss": "^8.4.21", "postcss": "^8.4.21"
"tailwindcss": "^3.2.7"
}, },
"lint-staged": { "lint-staged": {
"**/*.js?(x)": [ "**/*.js?(x)": [
......
import * as Sentry from '@sentry/nextjs' import * as Sentry from '@sentry/nextjs'
Sentry.init({ Sentry.init({
dsn: 'https://6bf48a450f054d749398c02a61bae343@o4505264807215104.ingest.sentry.io/4505264809115648', dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Replay may only be enabled for the client-side // Replay may only be enabled for the client-side
integrations: [new Sentry.Replay()], integrations: [new Sentry.Replay()],
// Set tracesSampleRate to 1.0 to capture 100% // Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring. // of transactions for performance monitoring.
// We recommend adjusting this value in production // We recommend adjusting this value in production
tracesSampleRate: 1.0, tracesSampleRate: 0.1,
// Capture Replay for 10% of all sessions, // Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error // plus for 100% of sessions with an error
......
import * as Sentry from '@sentry/nextjs' import * as Sentry from '@sentry/nextjs'
Sentry.init({ Sentry.init({
dsn: 'https://6bf48a450f054d749398c02a61bae343@o4505264807215104.ingest.sentry.io/4505264809115648', dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Set tracesSampleRate to 1.0 to capture 100% // Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring. // of transactions for performance monitoring.
// We recommend adjusting this value in production // We recommend adjusting this value in production
tracesSampleRate: 1.0, tracesSampleRate: 0.1,
// ... // ...
......
import * as Sentry from '@sentry/nextjs' import * as Sentry from '@sentry/nextjs'
Sentry.init({ Sentry.init({
dsn: 'https://6bf48a450f054d749398c02a61bae343@o4505264807215104.ingest.sentry.io/4505264809115648', dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Set tracesSampleRate to 1.0 to capture 100% // Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring. // of transactions for performance monitoring.
// We recommend adjusting this value in production // We recommend adjusting this value in production
tracesSampleRate: 1.0, tracesSampleRate: 0.1,
}) })
import { API_PREFIX, PUBLIC_API_PREFIX, IS_CE_EDITION } from '@/config' /* eslint-disable no-new, prefer-promise-reject-errors */
import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
const TIME_OUT = 100000 const TIME_OUT = 100000
...@@ -46,12 +47,11 @@ function unicodeToChar(text: string) { ...@@ -46,12 +47,11 @@ function unicodeToChar(text: string) {
}) })
} }
export function format(text: string) { export function format(text: string) {
let res = text.trim() let res = text.trim()
if (res.startsWith('\n')) { if (res.startsWith('\n'))
res = res.replace('\n', '') res = res.replace('\n', '')
}
return res.replaceAll('\n', '<br/>').replaceAll('```', '') return res.replaceAll('\n', '<br/>').replaceAll('```', '')
} }
...@@ -77,12 +77,22 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted ...@@ -77,12 +77,22 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted
lines.forEach((message) => { lines.forEach((message) => {
if (message.startsWith('data: ')) { // check if it starts with data: if (message.startsWith('data: ')) { // check if it starts with data:
// console.log(message); // console.log(message);
try {
bufferObj = JSON.parse(message.substring(6)) // remove data: and parse as json bufferObj = JSON.parse(message.substring(6)) // remove data: and parse as json
}
catch (e) {
// mute handle message cut off
onData('', isFirstMessage, {
conversationId: bufferObj?.conversation_id,
messageId: bufferObj?.id,
})
return
}
if (bufferObj.status === 400 || !bufferObj.event) { if (bufferObj.status === 400 || !bufferObj.event) {
onData('', false, { onData('', false, {
conversationId: undefined, conversationId: undefined,
messageId: '', messageId: '',
errorMessage: bufferObj.message errorMessage: bufferObj.message,
}) })
hasError = true hasError = true
onCompleted && onCompleted(true) onCompleted && onCompleted(true)
...@@ -97,19 +107,19 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted ...@@ -97,19 +107,19 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted
} }
}) })
buffer = lines[lines.length - 1] buffer = lines[lines.length - 1]
} catch (e) { }
catch (e) {
onData('', false, { onData('', false, {
conversationId: undefined, conversationId: undefined,
messageId: '', messageId: '',
errorMessage: e + '' errorMessage: `${e}`,
}) })
hasError = true hasError = true
onCompleted && onCompleted(true) onCompleted && onCompleted(true)
return return
} }
if (!hasError) { if (!hasError)
read() read()
}
}) })
} }
read() read()
...@@ -120,8 +130,8 @@ const baseFetch = ( ...@@ -120,8 +130,8 @@ const baseFetch = (
fetchOptions: any, fetchOptions: any,
{ {
isPublicAPI = false, isPublicAPI = false,
needAllResponseContent needAllResponseContent,
}: IOtherOptions }: IOtherOptions,
) => { ) => {
const options = Object.assign({}, baseOptions, fetchOptions) const options = Object.assign({}, baseOptions, fetchOptions)
if (isPublicAPI) { if (isPublicAPI) {
...@@ -129,7 +139,7 @@ const baseFetch = ( ...@@ -129,7 +139,7 @@ const baseFetch = (
options.headers.set('Authorization', `bearer ${sharedToken}`) options.headers.set('Authorization', `bearer ${sharedToken}`)
} }
let urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX const urlPrefix = isPublicAPI ? PUBLIC_API_PREFIX : API_PREFIX
let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}` let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`
const { method, params, body } = options const { method, params, body } = options
...@@ -176,12 +186,14 @@ const baseFetch = ( ...@@ -176,12 +186,14 @@ const baseFetch = (
bodyJson.then((data: any) => { bodyJson.then((data: any) => {
if (data.code === 'not_setup') { if (data.code === 'not_setup') {
globalThis.location.href = `${globalThis.location.origin}/install` globalThis.location.href = `${globalThis.location.origin}/install`
} else { }
else {
if (location.pathname === '/signin') { if (location.pathname === '/signin') {
bodyJson.then((data: any) => { bodyJson.then((data: any) => {
Toast.notify({ type: 'error', message: data.message }) Toast.notify({ type: 'error', message: data.message })
}) })
} else { }
else {
globalThis.location.href = loginUrl globalThis.location.href = loginUrl
} }
} }
...@@ -195,15 +207,13 @@ const baseFetch = ( ...@@ -195,15 +207,13 @@ const baseFetch = (
new Promise(() => { new Promise(() => {
bodyJson.then((data: any) => { bodyJson.then((data: any) => {
Toast.notify({ type: 'error', message: data.message }) Toast.notify({ type: 'error', message: data.message })
if (data.code === 'already_setup') { if (data.code === 'already_setup')
globalThis.location.href = `${globalThis.location.origin}/signin` globalThis.location.href = `${globalThis.location.origin}/signin`
}
}) })
}) })
break break
// fall through // fall through
default: default:
// eslint-disable-next-line no-new
new Promise(() => { new Promise(() => {
bodyJson.then((data: any) => { bodyJson.then((data: any) => {
Toast.notify({ type: 'error', message: data.message }) Toast.notify({ type: 'error', message: data.message })
...@@ -215,7 +225,7 @@ const baseFetch = ( ...@@ -215,7 +225,7 @@ const baseFetch = (
// handle delete api. Delete api not return content. // handle delete api. Delete api not return content.
if (res.status === 204) { if (res.status === 204) {
resolve({ result: "success" }) resolve({ result: 'success' })
return return
} }
...@@ -243,24 +253,23 @@ export const upload = (options: any): Promise<any> => { ...@@ -243,24 +253,23 @@ export const upload = (options: any): Promise<any> => {
...defaultOptions, ...defaultOptions,
...options, ...options,
headers: { ...defaultOptions.headers, ...options.headers }, headers: { ...defaultOptions.headers, ...options.headers },
};
return new Promise(function (resolve, reject) {
const xhr = options.xhr
xhr.open(options.method, options.url);
for (const key in options.headers) {
xhr.setRequestHeader(key, options.headers[key]);
} }
return new Promise((resolve, reject) => {
const xhr = options.xhr
xhr.open(options.method, options.url)
for (const key in options.headers)
xhr.setRequestHeader(key, options.headers[key])
xhr.withCredentials = true xhr.withCredentials = true
xhr.responseType = 'json' xhr.responseType = 'json'
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { if (xhr.readyState === 4) {
if (xhr.status === 201) { if (xhr.status === 201)
resolve(xhr.response) resolve(xhr.response)
} else { else
reject(xhr) reject(xhr)
} }
} }
}
xhr.upload.onprogress = options.onprogress xhr.upload.onprogress = options.onprogress
xhr.send(options.data) xhr.send(options.data)
}) })
...@@ -287,7 +296,6 @@ export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, o ...@@ -287,7 +296,6 @@ export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, o
.then((res: any) => { .then((res: any) => {
// debugger // debugger
if (!/^(2|3)\d{2}$/.test(res.status)) { if (!/^(2|3)\d{2}$/.test(res.status)) {
// eslint-disable-next-line no-new
new Promise(() => { new Promise(() => {
res.json().then((data: any) => { res.json().then((data: any) => {
Toast.notify({ type: 'error', message: data.message || 'Server Error' }) Toast.notify({ type: 'error', message: data.message || 'Server Error' })
......
import { ssePost, get, IOnData, IOnCompleted, IOnError } from './base' import type { IOnCompleted, IOnData, IOnError } from './base'
import { get, post, ssePost } from './base'
export const sendChatMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onError, getAbortController }: { export const sendChatMessage = async (appId: string, body: Record<string, any>, { onData, onCompleted, onError, getAbortController }: {
onData: IOnData onData: IOnData
onCompleted: IOnCompleted onCompleted: IOnCompleted
onError: IOnError, onError: IOnError
getAbortController?: (abortController: AbortController) => void getAbortController?: (abortController: AbortController) => void
}) => { }) => {
return ssePost(`apps/${appId}/chat-messages`, { return ssePost(`apps/${appId}/chat-messages`, {
body: { body: {
...body, ...body,
response_mode: 'streaming' response_mode: 'streaming',
} },
}, { onData, onCompleted, onError, getAbortController }) }, { onData, onCompleted, onError, getAbortController })
} }
...@@ -22,8 +23,8 @@ export const sendCompletionMessage = async (appId: string, body: Record<string, ...@@ -22,8 +23,8 @@ export const sendCompletionMessage = async (appId: string, body: Record<string,
return ssePost(`apps/${appId}/completion-messages`, { return ssePost(`apps/${appId}/completion-messages`, {
body: { body: {
...body, ...body,
response_mode: 'streaming' response_mode: 'streaming',
} },
}, { onData, onCompleted, onError }) }, { onData, onCompleted, onError })
} }
...@@ -34,7 +35,13 @@ export const fetchSuggestedQuestions = (appId: string, messageId: string) => { ...@@ -34,7 +35,13 @@ export const fetchSuggestedQuestions = (appId: string, messageId: string) => {
export const fetchConvesationMessages = (appId: string, conversation_id: string) => { export const fetchConvesationMessages = (appId: string, conversation_id: string) => {
return get(`apps/${appId}/chat-messages`, { return get(`apps/${appId}/chat-messages`, {
params: { params: {
conversation_id conversation_id,
} },
})
}
export const generateRule = (body: Record<string, any>) => {
return post('/rule-generate', {
body,
}) })
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment