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
.idea/'
.DS_Store
web/.vscode/settings.json
# Intellij IDEA Files
.idea/
......
......@@ -42,7 +42,7 @@ The easiest way to start the Dify server is to run our [docker-compose.yml](dock
```bash
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.
......
......@@ -44,7 +44,7 @@ Dify 兼容 Langchain,这意味着我们将逐步支持多种 LLMs ,目前
```bash
cd docker
docker-compose up -d
docker compose up -d
```
运行后,可以在浏览器上访问 [http://localhost/install](http://localhost/install) 进入 Dify 控制台并开始初始化安装操作。
......
......@@ -43,7 +43,7 @@ Dify サーバーを起動する最も簡単な方法は、[docker-compose.yml](
```bash
cd docker
docker-compose up -d
docker compose up -d
```
実行後、ブラウザで [http://localhost/install](http://localhost/install) にアクセスし、初期化インストール作業を開始することができます。
......
......@@ -50,8 +50,8 @@ def _validate_name(name):
def _validate_description_length(description):
if len(description) > 200:
raise ValueError('Description cannot exceed 200 characters.')
if len(description) > 400:
raise ValueError('Description cannot exceed 400 characters.')
return description
......
......@@ -78,12 +78,14 @@ class DatasetDocumentSegmentListApi(Resource):
parser.add_argument('hit_count_gte', type=int,
default=None, 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()
last_id = args['last_id']
limit = min(args['limit'], 100)
status_list = args['status']
hit_count_gte = args['hit_count_gte']
keyword = args['keyword']
query = DocumentSegment.query.filter(
DocumentSegment.document_id == str(document_id),
......@@ -104,6 +106,9 @@ class DatasetDocumentSegmentListApi(Resource):
if hit_count_gte is not None:
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() == 'true':
query = query.filter(DocumentSegment.enabled == True)
......
import logging
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.llm.llm_builder import LLMBuilder
......@@ -153,6 +153,8 @@ class LLMGenerator:
try:
output = llm(query)
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:
logging.exception("Error generating prompt")
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
from core.docstore.dataset_docstore import DatesetDocumentStore
from core.index.keyword_table_index import KeywordTableIndex
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.spiltter.fixed_text_splitter import FixedRecursiveCharacterTextSplitter
from core.index.vector_index import VectorIndex
......@@ -327,6 +328,7 @@ class IndexingRunner:
file_extractor = DEFAULT_FILE_EXTRACTOR.copy()
file_extractor[".markdown"] = MarkdownParser()
file_extractor[".md"] = MarkdownParser()
file_extractor[".html"] = HTMLParser()
file_extractor[".htm"] = HTMLParser()
file_extractor[".pdf"] = PDFParser({'upload_file': upload_file})
......
......@@ -13,6 +13,12 @@ def parse_json_markdown(json_string: str) -> dict:
if start_index != -1 and end_index != -1:
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
parsed = json.loads(extracted_content)
elif json_string.startswith("{"):
......
......@@ -43,7 +43,7 @@ def clean_document_task(document_id: str, dataset_id: str):
for segment in segments:
db.session.delete(segment)
db.session.commit()
end_at = time.perf_counter()
logging.info(
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):
for segment in segments:
db.session.delete(segment)
db.session.commit()
end_at = time.perf_counter()
logging.info(
click.style('Cleaned document when document update data source or process rule: {} latency: {}'.format(document_id, end_at - start_at), fg='green'))
......
......@@ -9,4 +9,9 @@ NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api
# The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from
# console or api domain.
# example: http://udify.app/api
NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api
\ No newline at end of file
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
COPY package.json /app/web/package.json
RUN npm install
RUN npm install --only=prod
COPY . /app/web/
......
......@@ -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.
## Lint Code
If your ide is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting.
## Learn More
To learn more about Next.js, take a look at the following resources:
......
'use client'
import { FC, useRef } from 'react'
import React, { useEffect, useState } from 'react'
import type { FC } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { usePathname, useRouter, useSelectedLayoutSegments } from 'next/navigation'
import useSWR, { SWRConfig } from 'swr'
import Header from '../components/header'
......@@ -50,7 +50,7 @@ const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => {
if (!appList || !userProfile || !langeniusVersionInfo)
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 currentDataset = datasetList?.data?.find(opt => opt.id === currentDatasetId)
......@@ -70,12 +70,18 @@ const CommonLayout: FC<ICommonLayoutProps> = ({ children }) => {
return (
<SWRConfig value={{
shouldRetryOnError: false
shouldRetryOnError: false,
}}>
<AppContextProvider value={{ apps: appList.data, mutateApps, userProfile, mutateUserProfile, pageContainerRef }}>
<DatasetsContext.Provider value={{ datasets: datasetList?.data || [], mutateDatasets, currentDataset }}>
<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}
</div>
</DatasetsContext.Provider>
......
......@@ -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 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)
......
'use client'
import type { FC } 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 type { PromptVariable } from '@/models/debug'
import Tooltip from '@/app/components/base/tooltip'
import { AppType } from '@/types/app'
import { getNewVar } from '@/utils/var'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import ConfirmAddVar from './confirm-add-var'
export type IPromptProps = {
mode: AppType
promptTemplate: string
promptVariables: PromptVariable[]
onChange: (promp: string, promptVariables: PromptVariable[]) => void
readonly?: boolean
onChange?: (promp: string, promptVariables: PromptVariable[]) => void
}
const Prompt: FC<IPromptProps> = ({
mode,
promptTemplate,
promptVariables,
readonly = false,
onChange,
}) => {
const { t } = useTranslation()
......@@ -45,35 +49,39 @@ const Prompt: FC<IPromptProps> = ({
showConfirmAddVar()
return
}
onChange(newTemplates, [])
onChange?.(newTemplates, [])
}
const handleAutoAdd = (isAdd: boolean) => {
return () => {
onChange(newTemplates, isAdd ? newPromptVariables : [])
onChange?.(newTemplates, isAdd ? newPromptVariables : [])
hideConfirmAddVar()
}
}
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">
<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" />
</svg>
<div className="h2">{mode === AppType.chat ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}</div>
<Tooltip
htmlContent={<div className='w-[180px]'>
{t('appDebug.promptTip')}
</div>}
selector='config-prompt-tooltip'>
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</Tooltip>
{!readonly && (
<Tooltip
htmlContent={<div className='w-[180px]'>
{t('appDebug.promptTip')}
</div>}
selector='config-prompt-tooltip'>
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>
</Tooltip>
)}
</div>
<BlockInput
readonly={readonly}
value={promptTemplate}
onConfirm={(value: string, vars: string[]) => {
handleChange(value, vars)
......@@ -82,7 +90,7 @@ const Prompt: FC<IPromptProps> = ({
{isShowConfirmAddVar && (
<ConfirmAddVar
varNameArr={newPromptVariables.map((v) => v.name)}
varNameArr={newPromptVariables.map(v => v.name)}
onConfrim={handleAutoAdd(true)}
onCancel={handleAutoAdd(false)}
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
'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)
......@@ -3,19 +3,22 @@ import type { FC } from 'react'
import React from 'react'
import { useContext } from 'use-context-selector'
import produce from 'immer'
import AddFeatureBtn from './feature/add-feature-btn'
import ChooseFeature from './feature/choose-feature'
import useFeature from './feature/use-feature'
import ConfigContext from '@/context/debug-configuration'
import { useBoolean } from 'ahooks'
import DatasetConfig from '../dataset-config'
import ChatGroup from '../features/chat-group'
import ExperienceEnchanceGroup from '../features/experience-enchance-group'
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 ConfigVar from '@/app/components/app/configuration/config-var'
import type { PromptVariable } from '@/models/debug'
import { AppType } from '@/types/app'
import { useBoolean } from 'ahooks'
const Config: FC = () => {
const {
......@@ -26,10 +29,10 @@ const Config: FC = () => {
setModelConfig,
setPrevPromptConfig,
setFormattingChanged,
moreLikeThisConifg,
setMoreLikeThisConifg,
moreLikeThisConfig,
setMoreLikeThisConfig,
suggestedQuestionsAfterAnswerConfig,
setSuggestedQuestionsAfterAnswerConfig
setSuggestedQuestionsAfterAnswerConfig,
} = useContext(ConfigContext)
const isChatApp = mode === AppType.chat
......@@ -41,9 +44,8 @@ const Config: FC = () => {
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...newVariables]
})
if (modelConfig.configs.prompt_template !== newTemplate) {
if (modelConfig.configs.prompt_template !== newTemplate)
setFormattingChanged(true)
}
setPrevPromptConfig(modelConfig.configs)
setModelConfig(newModelConfig)
......@@ -59,14 +61,14 @@ const Config: FC = () => {
const [showChooseFeature, {
setTrue: showChooseFeatureTrue,
setFalse: showChooseFeatureFalse
setFalse: showChooseFeatureFalse,
}] = useBoolean(false)
const { featureConfig, handleFeatureChange } = useFeature({
introduction,
setIntroduction,
moreLikeThis: moreLikeThisConifg.enabled,
moreLikeThis: moreLikeThisConfig.enabled,
setMoreLikeThis: (value) => {
setMoreLikeThisConifg(produce(moreLikeThisConifg, (draft) => {
setMoreLikeThisConfig(produce(moreLikeThisConfig, (draft) => {
draft.enabled = value
}))
},
......@@ -81,14 +83,24 @@ const Config: FC = () => {
const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer)
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 (
<>
<div className="pb-[20px]">
<div className='flex justify-between items-center mb-4'>
<AddFeatureBtn onClick={showChooseFeatureTrue} />
<div>
{/* AutoMatic */}
</div>
<AutomaticBtn onClick={showAutomaticTrue}/>
</div>
{showChooseFeature && (
......@@ -100,6 +112,14 @@ const Config: FC = () => {
onChange={handleFeatureChange}
/>
)}
{showAutomatic && (
<GetAutomaticResModal
mode={mode as AppType}
isShow={showAutomatic}
onClose={showAutomaticFalse}
onFinished={handleAutomaticRes}
/>
)}
{/* Template */}
<ConfigPrompt
mode={mode as AppType}
......@@ -124,9 +144,8 @@ const Config: FC = () => {
isShowOpeningStatement={featureConfig.openingStatement}
openingStatementConfig={
{
promptTemplate,
value: introduction,
onChange: setIntroduction
onChange: setIntroduction,
}
}
isShowSuggestedQuestionsAfterAnswer={featureConfig.suggestedQuestionsAfterAnswer}
......@@ -135,11 +154,10 @@ const Config: FC = () => {
}
{/* TextnGeneration config */}
{moreLikeThisConifg.enabled && (
{moreLikeThisConfig.enabled && (
<ExperienceEnchanceGroup />
)}
{/* Toolbox */}
{
hasToolbox && (
......
......@@ -38,7 +38,7 @@ const Debug: FC<IDebug> = ({
mode,
introduction,
suggestedQuestionsAfterAnswerConfig,
moreLikeThisConifg,
moreLikeThisConfig,
inputs,
// setInputs,
formattingChanged,
......@@ -304,7 +304,7 @@ const Debug: FC<IDebug> = ({
user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables),
opening_statement: introduction,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
more_like_this: moreLikeThisConifg,
more_like_this: moreLikeThisConfig,
agent_mode: {
enabled: true,
tools: [...postDatasets],
......
......@@ -17,9 +17,9 @@ import { getNewVar } from '@/utils/var'
import { varHighlightHTML } from '@/app/components/app/configuration/base/var-highlight'
export type IOpeningStatementProps = {
promptTemplate: string
value: string
onChange: (value: string) => void
readonly?: boolean
onChange?: (value: string) => void
}
// regex to match the {{}} and replace it with a span
......@@ -27,6 +27,7 @@ const regex = /\{\{([^}]+)\}\}/g
const OpeningStatement: FC<IOpeningStatementProps> = ({
value = '',
readonly,
onChange,
}) => {
const { t } = useTranslation()
......@@ -64,6 +65,8 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
.replace(/\n/g, '<br />')
const handleEdit = () => {
if (readonly)
return
setFocus()
}
......@@ -93,11 +96,11 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
return
}
setBlur()
onChange(tempValue)
onChange?.(tempValue)
}
const cancelAutoAddVar = () => {
onChange(tempValue)
onChange?.(tempValue)
hideConfirmAddVar()
setBlur()
}
......@@ -106,15 +109,15 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
const newModelConfig = produce(modelConfig, (draft) => {
draft.configs.prompt_variables = [...draft.configs.prompt_variables, ...notIncludeKeys.map(key => getNewVar(key))]
})
onChange(tempValue)
onChange?.(tempValue)
setModelConfig(newModelConfig)
hideConfirmAddVar()
setBlur()
}
const headerRight = (
const headerRight = !readonly ? (
<OperationBtn type='edit' actionName={hasValue ? '' : t('appDebug.openingStatement.writeOpner') as string} onClick={handleEdit} />
)
) : null
return (
<Panel
......@@ -130,30 +133,28 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
isFocus={isFocus}
>
<div className='text-gray-700 text-sm'>
{(hasValue || (!hasValue && isFocus))
? (
<>
{isFocus
? (
<textarea
ref={inputRef}
value={tempValue}
rows={3}
onChange={e => setTempValue(e.target.value)}
className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none "
placeholder={t('appDebug.openingStatement.placeholder') as string}
>
</textarea>
)
: (
<div dangerouslySetInnerHTML={{
__html: coloredContent,
}}></div>
)}
{/* Operation Bar */}
{isFocus
&& (
{(hasValue || (!hasValue && isFocus)) ? (
<>
{isFocus
? (
<textarea
ref={inputRef}
value={tempValue}
rows={3}
onChange={e => setTempValue(e.target.value)}
className="w-full px-0 text-sm border-0 bg-transparent focus:outline-none "
placeholder={t('appDebug.openingStatement.placeholder') as string}
>
</textarea>
)
: (
<div dangerouslySetInnerHTML={{
__html: coloredContent,
}}></div>
)}
{/* Operation Bar */}
{isFocus && (
<div className='mt-2 flex items-center justify-between'>
<div className='text-xs text-gray-500'>{t('appDebug.openingStatement.varTip')}</div>
......@@ -164,9 +165,9 @@ const OpeningStatement: FC<IOpeningStatementProps> = ({
</div>
)}
</>) : (
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div>
)}
</>) : (
<div className='pt-2 pb-1 text-xs text-gray-500'>{t('appDebug.openingStatement.noDataPlaceHolder')}</div>
)}
{isShowConfirmAddVar && (
<ConfirmAddVar
......
......@@ -5,7 +5,10 @@ import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { usePathname } from 'next/navigation'
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 { ModelConfig as BackendModelConfig } from '@/types/app'
import ConfigContext from '@/context/debug-configuration'
......@@ -17,12 +20,9 @@ import type { AppDetailResponse } from '@/models/app'
import { ToastContext } from '@/app/components/base/toast'
import { fetchTenantInfo } from '@/service/common'
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 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 { t } = useTranslation()
......@@ -35,8 +35,8 @@ const Configuration: FC = () => {
const matched = pathname.match(/\/app\/([^/]+)/)
const appId = (matched?.length && matched[1]) ? matched[1] : ''
const [mode, setMode] = useState('')
const [pusblisedConfig, setPusblisedConfig] = useState<{
modelConfig: ModelConfig,
const [publishedConfig, setPublishedConfig] = useState<{
modelConfig: ModelConfig
completionParams: CompletionParams
} | null>(null)
......@@ -47,7 +47,7 @@ const Configuration: FC = () => {
prompt_template: '',
prompt_variables: [],
})
const [moreLikeThisConifg, setMoreLikeThisConifg] = useState<MoreLikeThisConfig>({
const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig>({
enabled: false,
})
const [suggestedQuestionsAfterAnswerConfig, setSuggestedQuestionsAfterAnswerConfig] = useState<MoreLikeThisConfig>({
......@@ -70,6 +70,10 @@ const Configuration: FC = () => {
prompt_template: '',
prompt_variables: [] as PromptVariable[],
},
opening_statement: '',
more_like_this: null,
suggested_questions_after_answer: null,
dataSets: [],
})
const setModelConfig = (newModelConfig: ModelConfig) => {
......@@ -77,19 +81,29 @@ const Configuration: FC = () => {
}
const setModelId = (modelId: string) => {
const newModelConfig = produce(modelConfig, (draft) => {
const newModelConfig = produce(modelConfig, (draft: any) => {
draft.model_id = modelId
})
setModelConfig(newModelConfig)
}
const syncToPublishedConfig = (_pusblisedConfig: any) => {
setModelConfig(_pusblisedConfig.modelConfig)
setCompletionParams(_pusblisedConfig.completionParams)
}
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 [isTrailFinished, setIsTrailFinished] = useState(false)
const hasSetAPIKEY = hasSetCustomAPIKEY || !isTrailFinished
......@@ -116,35 +130,40 @@ const Configuration: FC = () => {
const model = res.model_config.model
let datasets: any = null
if (modelConfig.agent_mode?.enabled) {
if (modelConfig.agent_mode?.enabled)
datasets = modelConfig.agent_mode?.tools.filter(({ dataset }: any) => dataset?.enabled)
}
if (dataSets && datasets?.length && datasets?.length > 0) {
const { data: dataSetsWithDetail } = await fetchDatasets({ url: '/datasets', params: { page: 1, ids: datasets.map(({ dataset }: any) => dataset.id) } })
datasets = dataSetsWithDetail
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 = {
modelConfig: {
provider: model.provider,
model_id: model.name,
configs: {
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,
}
syncToPublishedConfig(config)
setPusblisedConfig(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)
}
setPublishedConfig(config)
setHasFetchedDetail(true)
})
}, [appId])
......@@ -154,18 +173,11 @@ const Configuration: FC = () => {
const promptTemplate = modelConfig.configs.prompt_template
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 }) => ({
dataset: {
enabled: true,
id,
}
},
}))
// new model config data struct
......@@ -173,11 +185,11 @@ const Configuration: FC = () => {
pre_prompt: promptTemplate,
user_input_form: promptVariablesToUserInputsForm(promptVariables),
opening_statement: introduction || '',
more_like_this: moreLikeThisConifg,
more_like_this: moreLikeThisConfig,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
agent_mode: {
enabled: true,
tools: [...postDatasets]
tools: [...postDatasets],
},
model: {
provider: modelConfig.provider,
......@@ -187,8 +199,14 @@ const Configuration: FC = () => {
}
await updateAppModelConfig({ url: `/apps/${appId}/model-config`, body: data })
setPusblisedConfig({
modelConfig,
const newModelConfig = produce(modelConfig, (draft: any) => {
draft.opening_statement = introduction
draft.more_like_this = moreLikeThisConfig
draft.suggested_questions_after_answer = suggestedQuestionsAfterAnswerConfig
draft.dataSets = dataSets
})
setPublishedConfig({
modelConfig: newModelConfig,
completionParams,
})
notify({ type: 'success', message: t('common.api.success'), duration: 3000 })
......@@ -196,8 +214,7 @@ const Configuration: FC = () => {
const [showConfirm, setShowConfirm] = useState(false)
const resetAppConfig = () => {
// debugger
syncToPublishedConfig(pusblisedConfig)
syncToPublishedConfig(publishedConfig)
setShowConfirm(false)
}
......@@ -224,8 +241,8 @@ const Configuration: FC = () => {
setControlClearChatMessage,
prevPromptConfig,
setPrevPromptConfig,
moreLikeThisConifg,
setMoreLikeThisConifg,
moreLikeThisConfig,
setMoreLikeThisConfig,
suggestedQuestionsAfterAnswerConfig,
setSuggestedQuestionsAfterAnswerConfig,
formattingChanged,
......@@ -239,7 +256,7 @@ const Configuration: FC = () => {
modelConfig,
setModelConfig,
dataSets,
setDataSets
setDataSets,
}}
>
<>
......
......@@ -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 = {
name: string
query: {
query?: {
start: string
end: string
}
......@@ -257,7 +262,7 @@ export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
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'
{...(noDataFlag && { yMax: 500 })}
/>
......@@ -272,7 +277,7 @@ export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
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'
{...(noDataFlag && { yMax: 500 })}
/>
......@@ -286,7 +291,7 @@ export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
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'
valueKey='interactions'
isAvg
......@@ -302,7 +307,7 @@ export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
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'
chartType='conversations'
isAvg
......@@ -319,7 +324,7 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
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'
chartType='endUsers'
isAvg
......@@ -336,7 +341,7 @@ export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
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'
{...(noDataFlag && { yMax: 100 })}
/>
......
......@@ -33,12 +33,14 @@ export type IBlockInputProps = {
value: string
className?: string // wrapper class
highLightClassName?: string // class for the highlighted text default is text-blue-500
readonly?: boolean
onConfirm?: (value: string, keys: string[]) => void
}
const BlockInput: FC<IBlockInputProps> = ({
value = '',
className,
readonly = false,
onConfirm,
}) => {
const { t } = useTranslation()
......@@ -113,7 +115,7 @@ const BlockInput: FC<IBlockInputProps> = ({
const editAreaClassName = 'focus:outline-none bg-transparent text-sm'
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
? <div className='h-full px-4 py-1'>
<textarea
......@@ -141,35 +143,37 @@ const BlockInput: FC<IBlockInputProps> = ({
<div className={classNames('block-input w-full overflow-y-auto border-none rounded-lg')}>
{textAreaContent}
{/* footer */}
<div className='flex item-center h-14 px-4'>
{isContentChanged
? (
<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='flex space-x-2'>
<Button
onClick={handleCancel}
className='w-20 !h-8 !text-[13px]'
>
{t('common.operation.cancel')}
</Button>
<Button
onClick={handleSubmit}
type="primary"
className='w-20 !h-8 !text-[13px]'
>
{t('common.operation.confirm')}
</Button>
</div>
{!readonly && (
<div className='flex item-center h-14 px-4'>
{isContentChanged
? (
<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='flex space-x-2'>
<Button
onClick={handleCancel}
className='w-20 !h-8 !text-[13px]'
>
{t('common.operation.cancel')}
</Button>
<Button
onClick={handleSubmit}
type="primary"
className='w-20 !h-8 !text-[13px]'
>
{t('common.operation.confirm')}
</Button>
</div>
</div>
)
: (
<p className="leading-5 text-xs text-gray-500">
{t('appDebug.promptTip')}
</p>
)}
</div>
</div>
)
: (
<p className="leading-5 text-xs text-gray-500">
{t('appDebug.promptTip')}
</p>
)}
</div>
)}
</div>
)
......
'use client'
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import classNames from 'classnames'
import { Switch as OriginalSwitch } from '@headlessui/react'
......@@ -12,25 +12,29 @@ type SwitchProps = {
const Switch = ({ onChange, size = 'lg', defaultValue = false, disabled = false }: SwitchProps) => {
const [enabled, setEnabled] = useState(defaultValue)
useEffect(() => {
setEnabled(defaultValue)
}, [defaultValue])
const wrapStyle = {
lg: 'h-6 w-11',
md: 'h-4 w-7'
md: 'h-4 w-7',
}
const circleStyle = {
lg: 'h-5 w-5',
md: 'h-3 w-3'
md: 'h-3 w-3',
}
const translateLeft = {
lg: 'translate-x-5',
md: 'translate-x-3'
md: 'translate-x-3',
}
return (
<OriginalSwitch
checked={enabled}
onChange={(checked: boolean) => {
if (disabled) return;
if (disabled)
return
setEnabled(checked)
onChange(checked)
}}
......
......@@ -35,7 +35,7 @@ const Tooltip: FC<TooltipProps> = ({
<ReactTooltip
id={selector}
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}
clickable={clickable}
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 @@
background-image: url(../assets/unknow.svg);
background-size: 32px;
}
.fileIcon.csv {
background-image: url(../assets/csv.svg);
}
.fileIcon.xlsx,
.fileIcon.xls {
background-image: url(../assets/xlsx.svg);
}
.fileIcon.pdf {
background-image: url(../assets/pdf.svg);
}
......@@ -168,4 +176,4 @@
.actionWrapper .buttonWrapper {
@apply flex items-center;
display: none;
}
\ No newline at end of file
}
'use client'
import React, { useState, useRef, useEffect, useCallback } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { File as FileEntity } from '@/models/datasets'
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 Button from '@/app/components/base/button'
import { upload } from '@/service/base'
import cn from 'classnames'
import s from './index.module.css'
type IFileUploaderProps = {
file?: FileEntity;
onFileUpdate: (file?: FileEntity) => void;
file?: FileEntity
onFileUpdate: (file?: FileEntity) => void
}
const ACCEPTS = [
......@@ -23,9 +22,12 @@ const ACCEPTS = [
'.md',
'.markdown',
'.txt',
'.xls',
'.xlsx',
'.csv',
]
const MAX_SIZE = 15 * 1024 *1024
const MAX_SIZE = 15 * 1024 * 1024
const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
const { t } = useTranslation()
......@@ -39,60 +41,55 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
const [uploading, setUploading] = useState(false)
const [percent, setPercent] = useState(0)
const handleDragEnter = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
// utils
const getFileType = (currentFile: File) => {
if (!currentFile)
return ''
const arr = currentFile.name.split('.')
return arr[arr.length - 1]
}
const handleDragLeave = (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
e.target === dragRef.current && setDragging(false)
const getFileName = (name: string) => {
const arr = name.split('.')
return arr.slice(0, -1).join()
}
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()
fileUpload(files[0])
const getFileSize = (size: number) => {
if (size / 1024 < 10)
return `${(size / 1024).toFixed(2)}KB`
return `${(size / 1024 / 1024).toFixed(2)}MB`
}
const selectHandle = () => {
if (fileUploader.current) {
fileUploader.current.click();
}
const isValid = (file: File) => {
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
}
const removeFile = () => {
if (fileUploader.current) {
fileUploader.current.value = ''
const onProgress = useCallback((e: ProgressEvent) => {
if (e.lengthComputable) {
const percent = Math.floor(e.loaded / e.total * 100)
setPercent(percent)
}
setCurrentFile(undefined)
onFileUpdate()
}
const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => {
const currentFile = e.target.files?.[0]
onFileUpdate()
fileUpload(currentFile)
}, [setPercent])
const abort = () => {
const currentXHR = uploadPromise.current
currentXHR.abort()
}
const fileUpload = async (file?: File) => {
if (!file) {
if (!file)
return
}
if (!isValid(file)) {
if (!isValid(file))
return
}
setCurrentFile(file)
setUploading(true)
const formData = new FormData()
......@@ -105,7 +102,7 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
xhr: currentXHR,
data: formData,
onprogress: onProgress,
}) as FileEntity;
}) as FileEntity
onFileUpdate(result)
setUploading(false)
}
......@@ -113,69 +110,72 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
setUploading(false)
// abort handle
if (xhr.readyState === 0 && xhr.status === 0) {
if (fileUploader.current) {
if (fileUploader.current)
fileUploader.current.value = ''
}
setCurrentFile(undefined)
return
return
}
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) => {
e.preventDefault()
e.stopPropagation()
e.target !== dragRef.current && setDragging(true)
}
const handleDragOver = (e: DragEvent) => {
e.preventDefault()
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
// utils
const getFileType = (currentFile: File) => {
if (!currentFile) {
return ''
const files = [...e.dataTransfer.files]
if (files.length > 1) {
notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.count') })
return
}
const arr = currentFile.name.split('.')
return arr[arr.length-1]
onFileUpdate()
fileUpload(files[0])
}
const getFileName = (name: string) => {
const arr = name.split('.')
return arr.slice(0, -1).join()
const selectHandle = () => {
if (fileUploader.current)
fileUploader.current.click()
}
const getFileSize = (size: number) => {
if (size / 1024 < 10) {
return `${(size / 1024).toFixed(2)}KB`
}
return `${(size / 1024 / 1024).toFixed(2)}MB`
const removeFile = () => {
if (fileUploader.current)
fileUploader.current.value = ''
setCurrentFile(undefined)
onFileUpdate()
}
const isValid = (file: File) => {
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;
const fileChangeHandle = (e: React.ChangeEvent<HTMLInputElement>) => {
const currentFile = e.target.files?.[0]
onFileUpdate()
fileUpload(currentFile)
}
useEffect(() => {
dropRef.current?.addEventListener('dragenter', handleDragEnter);
dropRef.current?.addEventListener('dragover', handleDragOver);
dropRef.current?.addEventListener('dragleave', handleDragLeave);
dropRef.current?.addEventListener('drop', handleDrop);
dropRef.current?.addEventListener('dragenter', handleDragEnter)
dropRef.current?.addEventListener('dragover', handleDragOver)
dropRef.current?.addEventListener('dragleave', handleDragLeave)
dropRef.current?.addEventListener('drop', handleDrop)
return () => {
dropRef.current?.removeEventListener('dragenter', handleDragEnter);
dropRef.current?.removeEventListener('dragover', handleDragOver);
dropRef.current?.removeEventListener('dragleave', handleDragLeave);
dropRef.current?.removeEventListener('drop', handleDrop);
dropRef.current?.removeEventListener('dragenter', handleDragEnter)
dropRef.current?.removeEventListener('dragover', handleDragOver)
dropRef.current?.removeEventListener('dragleave', handleDragLeave)
dropRef.current?.removeEventListener('drop', handleDrop)
}
}, [])
......@@ -202,7 +202,7 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
{currentFile && (
<div className={cn(s.file, uploading && s.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={s.fileInfo}>
......@@ -264,4 +264,4 @@ const FileUploader = ({ file, onFileUpdate }: IFileUploaderProps) => {
)
}
export default FileUploader;
export default FileUploader
......@@ -292,6 +292,15 @@
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.htm {
background-image: url(../assets/html.svg);
......@@ -379,4 +388,4 @@
line-height: 28px;
color: #101828;
z-index: 10;
}
\ No newline at end of file
}
<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 React, { useCallback, useMemo, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import useSWR from 'swr'
import { useRouter } from 'next/navigation'
import { useContext } from 'use-context-selector'
import { useTranslation } from 'react-i18next'
import { omit } from 'lodash-es'
import cn from 'classnames'
import { ArrowRightIcon } from '@heroicons/react/24/solid'
import { useGetState } from 'ahooks'
import cn from 'classnames'
import SegmentCard from '../completed/SegmentCard'
import { FieldInfo } from '../metadata'
import style from '../completed/style.module.css'
......@@ -19,7 +20,7 @@ import type { FullDocumentDetail, ProcessRuleResponse } from '@/models/datasets'
import type { CommonResponse } from '@/models/common'
import { asyncRunSafe } from '@/utils'
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 StopEmbeddingModal from '@/app/components/datasets/create/stop-embedding-modal'
......@@ -134,11 +135,15 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d
}
const [runId, setRunId, getRunId] = useGetState<any>(null)
const stopQueryStatus = () => {
clearInterval(getRunId())
}
const startQueryStatus = () => {
const runId = setInterval(() => {
const indexingStatusDetail = getIndexingStatusDetail()
if (indexingStatusDetail?.indexing_status === 'completed') {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
stopQueryStatus()
return
}
......@@ -146,9 +151,6 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d
}, 2500)
setRunId(runId)
}
const stopQueryStatus = () => {
clearInterval(getRunId())
}
useEffect(() => {
fetchIndexingStatus()
......@@ -235,14 +237,15 @@ const EmbeddingDetail: FC<Props> = ({ detail, stopPosition = 'top', datasetId: d
key={idx}
className={cn(s.progressBgItem, isEmbedding ? 'bg-primary-50' : 'bg-gray-100')}
/>)}
<div className={
cn('rounded-l-md',
<div
className={cn(
'rounded-l-md',
s.progressBar,
(isEmbedding || isEmbeddingCompleted) && s.barProcessing,
(isEmbeddingPaused || isEmbeddingError) && s.barPaused,
indexingStatusDetail?.indexing_status === 'completed' && 'rounded-r-md')
}
style={{ width: `${percent}%` }}
indexingStatusDetail?.indexing_status === 'completed' && 'rounded-r-md',
)}
style={{ width: `${percent}%` }}
/>
</div>
<div className={s.progressData}>
......
'use client'
import React, { useState, useCallback, useEffect } from 'react'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'
import { useContext } from 'use-context-selector'
......@@ -7,7 +7,8 @@ import { useRouter } from 'next/navigation'
import DatasetDetailContext from '@/context/dataset-detail'
import type { FullDocumentDetail } from '@/models/datasets'
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 StepTwo from '@/app/components/datasets/create/step-two'
......@@ -15,8 +16,8 @@ import AccountSetting from '@/app/components/header/account-setting'
import AppUnavailable from '@/app/components/base/app-unavailable'
type DocumentSettingsProps = {
datasetId: string;
documentId: string;
datasetId: string
documentId: string
}
const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
......@@ -48,18 +49,18 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
const detail = await fetchDocumentDetail({
datasetId,
documentId,
params: { metadata: 'without' as MetadataType }
params: { metadata: 'without' as MetadataType },
})
setDocumentDetail(detail)
} catch (e) {
}
catch (e) {
setHasError(true)
}
})()
}, [datasetId, documentId])
if (hasError) {
if (hasError)
return <AppUnavailable code={500} unknownReason={t('datasetCreation.error.unavailable') as string} />
}
return (
<div className='flex' style={{ height: 'calc(100vh - 56px)' }}>
......
......@@ -75,6 +75,15 @@
.markdownIcon {
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 {
@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 = () => {
}
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='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>
<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) {
.appList {
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 @@
}
.github-icon {
width: 16px;
height: 16px;
background: url(./assets/github.svg) center center no-repeat;
width: 18px;
height: 18px;
background: url(./assets/github-icon.svg) center center no-repeat;
background-size: contain;
}
.alpha {
......
'use client'
import { useCallback, useEffect, useState } from 'react'
import type { FC } from 'react'
import { useState } from 'react'
import useSWRInfinite from 'swr/infinite'
import { useTranslation } from 'react-i18next'
import { flatten } from 'lodash-es'
import { useRouter, useSelectedLayoutSegment } from 'next/navigation'
import classNames from 'classnames'
import { CircleStackIcon, PuzzlePieceIcon } from '@heroicons/react/24/outline'
......@@ -9,11 +12,12 @@ import Link from 'next/link'
import AccountDropdown from './account-dropdown'
import Nav from './nav'
import s from './index.module.css'
import type { AppDetailResponse } from '@/models/app'
import type { LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import type { GithubRepo, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import type { AppListResponse } from '@/models/app'
import NewAppDialog from '@/app/(commonLayout)/apps/NewAppDialog'
import { WorkspaceProvider } from '@/context/workspace-context'
import { useDatasetsContext } from '@/context/datasets-context'
import { fetchAppList } from '@/service/apps'
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">
......@@ -22,8 +26,7 @@ const BuildAppsIcon = ({ isSelected }: { isSelected: boolean }) => (
)
export type IHeaderProps = {
appItems: AppDetailResponse[]
curApp: AppDetailResponse
curAppId?: string
userProfile: UserProfileResponse
onLogout: () => void
langeniusVersionInfo: LangGeniusVersionResponse
......@@ -38,15 +41,42 @@ const headerEnvClassName: { [k: string]: string } = {
DEVELOPMENT: 'bg-[#FEC84B] border-[#FDB022] text-[#93370D]',
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 [showNewAppDialog, setShowNewAppDialog] = useState(false)
const { data: appsData, isLoading, setSize } = useSWRInfinite(curAppId ? getKey : () => null, fetchAppList, { revalidateFirstPage: false })
const { datasets, currentDataset } = useDatasetsContext()
const router = useRouter()
const showEnvTag = langeniusVersionInfo.current_env === 'TESTING' || langeniusVersionInfo.current_env === 'DEVELOPMENT'
const selectedSegment = useSelectedLayoutSegment()
const isPluginsComingSoon = selectedSegment === 'plugins-coming-soon'
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 (
<div className={classNames(
......@@ -59,17 +89,23 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
'flex flex-1 items-center justify-between px-4',
)}>
<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} />
</Link>
{/* Add it when has many stars */}
<div className='
flex items-center h-[26px] px-2 bg-white
border border-solid border-[#E5E7EB] rounded-l-[6px] rounded-r-[6px]
'>
<div className={s.alpha} />
<div className='ml-1 text-xs font-semibold text-gray-700'>{t('common.menus.status')}</div>
</div>
{
starCount > 0 && (
<Link
href='https://github.com/langgenius/dify'
target='_blank'
className='flex items-center leading-[18px] border border-gray-200 rounded-md text-xs text-gray-700 font-semibold overflow-hidden'>
<div className='flex items-center px-2 py-1 bg-gray-100'>
<div className={`${s['github-icon']} mr-1 rounded-full`} />
Star
</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 className='flex items-center'>
<Link href="/explore/apps" className={classNames(
......@@ -85,7 +121,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
text={t('common.menus.apps')}
activeSegment={['apps', 'app']}
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 => ({
id: item.id,
name: item.name,
......@@ -95,6 +131,7 @@ const Header: FC<IHeaderProps> = ({ appItems, curApp, userProfile, onLogout, lan
}))}
createText={t('common.menus.newApp')}
onCreate={() => setShowNewAppDialog(true)}
onLoadmore={handleLoadmore}
/>
<Link href="/plugins-coming-soon" className={classNames(
navClassName, 'group',
......
......@@ -24,6 +24,7 @@ const Nav = ({
navs,
createText,
onCreate,
onLoadmore,
}: INavProps) => {
const [hovered, setHovered] = useState(false)
const segment = useSelectedLayoutSegment()
......@@ -62,6 +63,7 @@ const Nav = ({
navs={navs}
createText={createText}
onCreate={onCreate}
onLoadmore={onLoadmore}
/>
</>
)
......
'use client'
import { Fragment } from 'react'
import { useCallback } from 'react'
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 { debounce } from 'lodash-es'
import Indicator from '../../indicator'
import AppIcon from '@/app/components/base/app-icon'
......@@ -13,11 +14,12 @@ type NavItem = {
icon: string
icon_background: string
}
export interface INavSelectorProps {
export type INavSelectorProps = {
navs: NavItem[]
curNav?: Omit<NavItem, 'link'>
createText: string
onCreate: () => void
onLoadmore?: () => void
}
const itemClassName = `
......@@ -25,9 +27,18 @@ const itemClassName = `
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 handleScroll = useCallback(debounce((e) => {
if (typeof onLoadmore === 'function') {
const { clientHeight, scrollHeight, scrollTop } = e.target
if (clientHeight + scrollTop > scrollHeight - 50)
onLoadmore()
}
}, 50), [])
return (
<div className="">
<Menu as="div" className="relative inline-block text-left">
......@@ -46,59 +57,49 @@ const NavSelector = ({ curNav, navs, createText, onCreate }: INavSelectorProps)
/>
</Menu.Button>
</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
className="
absolute -left-11 right-0 mt-1.5 w-60 max-w-80
divide-y divide-gray-100 origin-top-right rounded-lg bg-white
shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_rgba(0,0,0,0.05)]
"
>
<Menu.Items
className="
absolute -left-11 right-0 mt-1.5 w-60 max-w-80
divide-y divide-gray-100 origin-top-right rounded-lg bg-white
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' }}>
{
navs.map((nav) => (
<Menu.Item key={nav.id}>
<div className={itemClassName} onClick={() => router.push(nav.link)}>
<div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'>
<AppIcon size='tiny' icon={nav.icon} background={nav.icon_background}/>
<div className='flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded'>
<Indicator />
</div>
<div className="px-1 py-1 overflow-auto" style={{ maxHeight: '50vh' }} onScroll={handleScroll}>
{
navs.map(nav => (
<Menu.Item key={nav.id}>
<div className={itemClassName} onClick={() => router.push(nav.link)}>
<div className='relative w-6 h-6 mr-2 bg-[#D5F5F6] rounded-[6px]'>
<AppIcon size='tiny' icon={nav.icon} background={nav.icon_background}/>
<div className='flex justify-center items-center absolute -right-0.5 -bottom-0.5 w-2.5 h-2.5 bg-white rounded'>
<Indicator />
</div>
{nav.name}
</div>
</Menu.Item>
))
}
</div>
<Menu.Item>
<div className='p-1' onClick={onCreate}>
{nav.name}
</div>
</Menu.Item>
))
}
</div>
<Menu.Item>
<div className='p-1' onClick={onCreate}>
<div
className='flex items-center h-12 rounded-lg cursor-pointer hover:bg-gray-100'
>
<div
className='flex items-center h-12 rounded-lg cursor-pointer hover:bg-gray-100'
className='
flex justify-center items-center
ml-4 mr-2 w-6 h-6 bg-gray-100 rounded-[6px]
border-[0.5px] border-gray-200 border-dashed
'
>
<div
className='
flex justify-center items-center
ml-4 mr-2 w-6 h-6 bg-gray-100 rounded-[6px]
border-[0.5px] border-gray-200 border-dashed
'
>
<PlusIcon className='w-4 h-4 text-gray-500' />
</div>
<div className='font-normal text-[14px] text-gray-700'>{createText}</div>
<PlusIcon className='w-4 h-4 text-gray-500' />
</div>
<div className='font-normal text-[14px] text-gray-700'>{createText}</div>
</div>
</Menu.Item>
</Menu.Items>
</Transition>
</div>
</Menu.Item>
</Menu.Items>
</Menu>
</div>
)
......
......@@ -47,7 +47,7 @@ const TextGeneration: FC<IMainProps> = ({
const [appId, setAppId] = useState<string>('')
const [siteInfo, setSiteInfo] = useState<SiteInfo | 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 [query, setQuery] = useState('')
const [completionRes, setCompletionRes] = useState('')
......@@ -193,7 +193,7 @@ const TextGeneration: FC<IMainProps> = ({
prompt_template: '', // placeholder for feture
prompt_variables,
} as PromptConfig)
setMoreLikeThisConifg(more_like_this)
setMoreLikeThisConfig(more_like_this)
})()
}, [])
......@@ -251,7 +251,7 @@ const TextGeneration: FC<IMainProps> = ({
content={completionRes}
messageId={messageId}
isInWebApp
moreLikeThis={moreLikeThisConifg?.enabled}
moreLikeThis={moreLikeThisConfig?.enabled}
onFeedback={handleFeedback}
feedback={feedback}
onSave={handleSaveMessage}
......
......@@ -137,4 +137,12 @@ button:focus-within {
[class*=style_paginatio] li .text-primary-600 {
color: rgb(28 100 242);
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 @@
.markdown-body pre code {
font-size: 100%;
white-space: pre-wrap !important;
}
.markdown-body pre>code {
padding: 0;
margin: 0;
word-break: normal;
white-space: pre;
white-space: pre-wrap;
background: transparent;
border: 0;
}
......
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'
type IDebugConfiguration = {
......@@ -15,9 +15,9 @@ type IDebugConfiguration = {
setControlClearChatMessage: (controlClearChatMessage: number) => void
prevPromptConfig: PromptConfig
setPrevPromptConfig: (prevPromptConfig: PromptConfig) => void
moreLikeThisConifg: MoreLikeThisConfig,
setMoreLikeThisConifg: (moreLikeThisConfig: MoreLikeThisConfig) => void
suggestedQuestionsAfterAnswerConfig: SuggestedQuestionsAfterAnswerConfig,
moreLikeThisConfig: MoreLikeThisConfig
setMoreLikeThisConfig: (moreLikeThisConfig: MoreLikeThisConfig) => void
suggestedQuestionsAfterAnswerConfig: SuggestedQuestionsAfterAnswerConfig
setSuggestedQuestionsAfterAnswerConfig: (suggestedQuestionsAfterAnswerConfig: SuggestedQuestionsAfterAnswerConfig) => void
formattingChanged: boolean
setFormattingChanged: (formattingChanged: boolean) => void
......@@ -51,10 +51,10 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({
prompt_variables: [],
},
setPrevPromptConfig: () => { },
moreLikeThisConifg: {
moreLikeThisConfig: {
enabled: false,
},
setMoreLikeThisConifg: () => { },
setMoreLikeThisConfig: () => { },
suggestedQuestionsAfterAnswerConfig: {
enabled: false,
},
......
This diff is collapsed.
const translation = {
pageTitle: "提示词编排",
pageTitle: '提示词编排',
operation: {
applyConfig: "发布",
resetConfig: "重置",
addFeature: "添加功能",
stopResponding: "停止响应",
applyConfig: '发布',
resetConfig: '重置',
addFeature: '添加功能',
automatic: '自动编排',
stopResponding: '停止响应',
},
notSetAPIKey: {
title: "LLM 提供者的密钥未设置",
trailFinished: "试用已结束",
description: "在调试之前需要设置 LLM 提供者的密钥。",
settingBtn: "去设置",
title: 'LLM 提供者的密钥未设置',
trailFinished: '试用已结束',
description: '在调试之前需要设置 LLM 提供者的密钥。',
settingBtn: '去设置',
},
trailUseGPT4Info: {
title: '当前不支持使用 gpt-4',
......@@ -19,14 +20,14 @@ const translation = {
feature: {
groupChat: {
title: '聊天增强',
description: '为聊天型应用添加预对话设置,可以提升用户体验。'
description: '为聊天型应用添加预对话设置,可以提升用户体验。',
},
groupExperience: {
title: '体验增强',
},
conversationOpener: {
title: "对话开场白",
description: "在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。"
title: '对话开场白',
description: '在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。',
},
suggestedQuestionsAfterAnswer: {
title: '下一步问题建议',
......@@ -35,100 +36,117 @@ const translation = {
tryToAsk: '试着问问',
},
moreLikeThis: {
title: "更多类似的",
title: '更多类似的',
description: '一次生成多条文本,可在此基础上编辑并继续生成',
generateNumTip: "每次生成数",
tip: "使用此功能将会额外消耗 tokens"
generateNumTip: '每次生成数',
tip: '使用此功能将会额外消耗 tokens',
},
dataSet: {
title: "上下文",
noData: "您可以导入数据集作为上下文",
words: "词",
textBlocks: "文本块",
selectTitle: "选择引用数据集",
selected: "个数据集被选中",
noDataSet: "未找到数据集",
toCreate: "去创建",
notSupportSelectMulti: '目前只支持引用一个数据集'
}
title: '上下文',
noData: '您可以导入数据集作为上下文',
words: '词',
textBlocks: '文本块',
selectTitle: '选择引用数据集',
selected: '个数据集被选中',
noDataSet: '未找到数据集',
toCreate: '去创建',
notSupportSelectMulti: '目前只支持引用一个数据集',
},
},
automatic: {
title: '自动编排',
description: '描述您的场景,Dify 将为您编排一个应用。',
intendedAudience: '目标用户是谁?',
intendedAudiencePlaceHolder: '例如:学生',
solveProblem: '希望 AI 为他们解决什么问题?',
solveProblemPlaceHolder: '例如:评估学业水平',
generate: '生成',
audiencesRequired: '目标用户必填',
problemRequired: '解决问题必填',
resTitle: '我们为您编排了以下应用程序',
apply: '应用',
noData: '在左侧描述您的用例,编排预览将在此处显示。',
loading: '为您编排应用程序中…',
overwriteTitle: '覆盖现有配置?',
overwriteMessage: '应用此编排将覆盖现有配置。',
},
resetConfig: {
title: "确认重置?",
message: "重置将丢失当前页面所有修改,恢复至上次发布时的配置",
title: '确认重置?',
message: '重置将丢失当前页面所有修改,恢复至上次发布时的配置',
},
errorMessage: {
nameOfKeyRequired: "变量 {{key}} 对应的名称必填",
valueOfVarRequired: "变量值必填",
queryRequired: "主要文本必填",
waitForResponse: "请等待上条信息响应完成",
nameOfKeyRequired: '变量 {{key}} 对应的名称必填',
valueOfVarRequired: '变量值必填',
queryRequired: '主要文本必填',
waitForResponse: '请等待上条信息响应完成',
},
chatSubTitle: "对话前提示词",
completionSubTitle: "前缀提示词",
chatSubTitle: '对话前提示词',
completionSubTitle: '前缀提示词',
promptTip:
"提示词用于对 AI 的回复做出一系列指令和约束。可插入表单变量,例如 {{input}}。这段提示词不会被最终用户所看到。",
formattingChangedTitle: "编排已改变",
formattingChangedText: "修改编排将重置调试区域,确定吗?",
variableTitle: "变量",
notSetVar: "变量能使用户输入表单引入提示词或开场白,你可以试试在提示词中输入输入 {{input}}",
'提示词用于对 AI 的回复做出一系列指令和约束。可插入表单变量,例如 {{input}}。这段提示词不会被最终用户所看到。',
formattingChangedTitle: '编排已改变',
formattingChangedText: '修改编排将重置调试区域,确定吗?',
variableTitle: '变量',
notSetVar: '变量能使用户输入表单引入提示词或开场白,你可以试试在提示词中输入输入 {{input}}',
variableTip:
"变量将以表单形式让用户在对话前填写,用户填写的表单内容将自动替换提示词中的变量。",
autoAddVar: "提示词中引用了未定义的变量,是否自动添加到用户输入表单中?",
'变量将以表单形式让用户在对话前填写,用户填写的表单内容将自动替换提示词中的变量。',
autoAddVar: '提示词中引用了未定义的变量,是否自动添加到用户输入表单中?',
variableTable: {
key: "变量 Key",
name: "字段名称",
optional: "可选",
type: "类型",
action: "操作",
typeString: "文本",
typeSelect: "下拉选项",
key: '变量 Key',
name: '字段名称',
optional: '可选',
type: '类型',
action: '操作',
typeString: '文本',
typeSelect: '下拉选项',
},
varKeyError: {
canNoBeEmpty: "变量不能为空",
tooLong: "变量: {{key}} 长度太长。不能超过 16 个字符",
notValid: "变量: {{key}} 非法。只能包含英文字符,数字和下划线",
notStartWithNumber: "变量: {{key}} 不能以数字开头",
canNoBeEmpty: '变量不能为空',
tooLong: '变量: {{key}} 长度太长。不能超过 16 个字符',
notValid: '变量: {{key}} 非法。只能包含英文字符,数字和下划线',
notStartWithNumber: '变量: {{key}} 不能以数字开头',
},
variableConig: {
modalTitle: "变量设置",
description: "设置变量 {{varName}}",
modalTitle: '变量设置',
description: '设置变量 {{varName}}',
fieldType: '字段类型',
string: '文本',
select: '下拉选项',
notSet: '未设置,在 Prompt 中输入 {{input}} 试试',
stringTitle: "文本框设置",
maxLength: "最大长度",
options: "选项",
addOption: "添加选项",
stringTitle: '文本框设置',
maxLength: '最大长度',
options: '选项',
addOption: '添加选项',
},
openingStatement: {
title: "对话开场白",
add: "添加开场白",
writeOpner: "编写开场白",
placeholder: "请在这里输入开场白",
title: '对话开场白',
add: '添加开场白',
writeOpner: '编写开场白',
placeholder: '请在这里输入开场白',
noDataPlaceHolder:
"在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。",
'在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。',
varTip: '你可以使用变量, 试试输入 {{variable}}',
tooShort: "对话前提示词至少 20 字才能生成开场白",
notIncludeKey: "前缀提示词中不包含变量 {{key}}。请在前缀提示词中添加该变量",
tooShort: '对话前提示词至少 20 字才能生成开场白',
notIncludeKey: '前缀提示词中不包含变量 {{key}}。请在前缀提示词中添加该变量',
},
modelConfig: {
model: "语言模型",
setTone: "模型设置",
title: "模型及参数",
model: '语言模型',
setTone: '模型设置',
title: '模型及参数',
},
inputs: {
title: "调试与预览",
noPrompt: "尝试在对话前提示框中编写一些提示词",
userInputField: "用户输入",
noVar: "填入变量的值,每次启动新会话时该变量将自动替换提示词中的变量。",
chatVarTip: "填入变量的值,该值将在每次开启一个新会话时自动替换到提示词中",
completionVarTip: "填入变量的值,该值将在每次提交问题时自动替换到提示词中",
previewTitle: "提示词预览",
queryTitle: "查询内容",
queryPlaceholder: "请输入文本内容",
run: "运行",
title: '调试与预览',
noPrompt: '尝试在对话前提示框中编写一些提示词',
userInputField: '用户输入',
noVar: '填入变量的值,每次启动新会话时该变量将自动替换提示词中的变量。',
chatVarTip: '填入变量的值,该值将在每次开启一个新会话时自动替换到提示词中',
completionVarTip: '填入变量的值,该值将在每次提交问题时自动替换到提示词中',
previewTitle: '提示词预览',
queryTitle: '查询内容',
queryPlaceholder: '请输入文本内容',
run: '运行',
},
result: "结果",
};
result: '结果',
}
export default translation;
export default translation
......@@ -22,7 +22,7 @@ const translation = {
title: 'Upload text file',
button: 'Drag and drop file, or',
browse: 'Browse',
tip: 'Supports txt, html, markdown, and pdf.',
tip: 'Supports txt, html, markdown, xlsx, xls, and pdf.',
validation: {
typeError: 'File type not supported',
size: 'File too large. Maximum is 15MB',
......@@ -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.',
modelTitle: 'Are you sure to stop embedding?',
modelContent: 'If you need to resume processing later, you will continue from where you left off.',
modelButtonConfirm: "Confirm",
modelButtonCancel: 'Cancel'
modelButtonConfirm: 'Confirm',
modelButtonCancel: 'Cancel',
},
}
......
......@@ -22,7 +22,7 @@ const translation = {
title: '上传文本文件',
button: '拖拽文件至此,或者',
browse: '选择文件',
tip: '已支持 TXT, HTML, Markdown, PDF',
tip: '已支持 TXT, HTML, Markdown, PDF, XLSX, XLS',
validation: {
typeError: '文件类型不支持',
size: '文件太大了,不能超过 15MB',
......@@ -101,9 +101,9 @@ const translation = {
sideTipTitle: '接下来做什么',
sideTipContent: '当文档完成索引处理后,数据集即可集成至应用内作为上下文使用,你可以在提示词编排页找到上下文设置。你也可以创建成可独立使用的 ChatGPT 索引插件发布。',
modelTitle: '确认停止索引过程吗?',
modelContent:'如果您需要稍后恢复处理,则从停止处继续。',
modelButtonConfirm: "确认停止",
modelButtonCancel: '取消'
modelContent: '如果您需要稍后恢复处理,则从停止处继续。',
modelButtonConfirm: '确认停止',
modelButtonCancel: '取消',
},
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -120,3 +120,7 @@ export type DataSourceNotion = {
is_bound: boolean
source_info: DataSourceNotionWorkspace
}
export type GithubRepo = {
stargazers_count: number
}
export type Inputs = Record<string, string | number | object>
export type PromptVariable = {
key: string,
name: string,
type: string, // "string" | "number" | "select",
default?: string | number,
required: boolean,
key: string
name: string
type: string // "string" | "number" | "select",
default?: string | number
required: boolean
options?: string[]
max_length?: number
}
export type CompletionParams = {
max_tokens: number,
temperature: number,
top_p: number,
presence_penalty: number,
frequency_penalty: number,
max_tokens: number
temperature: number
top_p: number
presence_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 = {
prompt_template: string,
prompt_variables: PromptVariable[],
prompt_template: string
prompt_variables: PromptVariable[]
}
export type MoreLikeThisConfig = {
......@@ -33,83 +33,88 @@ export type SuggestedQuestionsAfterAnswerConfig = MoreLikeThisConfig
// frontend use. Not the same as backend
export type ModelConfig = {
provider: string, // LLM Provider: for example "OPENAI"
model_id: string,
provider: string // LLM Provider: for example "OPENAI"
model_id: string
configs: PromptConfig
opening_statement: string | null
more_like_this: {
enabled: boolean
} | null
suggested_questions_after_answer: {
enabled: boolean
} | null
dataSets: any[]
}
export type DebugRequestBody = {
inputs: Inputs,
query: string,
completion_params: CompletionParams,
inputs: Inputs
query: string
completion_params: CompletionParams
model_config: ModelConfig
}
export type DebugResponse = {
id: string,
answer: string,
created_at: string,
id: string
answer: string
created_at: string
}
export type DebugResponseStream = {
id: string,
data: string,
created_at: string,
id: string
data: string
created_at: string
}
export type FeedBackRequestBody = {
message_id: string,
rating: 'like' | 'dislike',
content?: string,
message_id: string
rating: 'like' | 'dislike'
content?: string
from_source: 'api' | 'log'
}
export type FeedBackResponse = {
message_id: string,
message_id: string
rating: 'like' | 'dislike'
}
// Log session list
export type LogSessionListQuery = {
keyword?: string,
start?: string, // format datetime(YYYY-mm-dd HH:ii)
end?: string, // format datetime(YYYY-mm-dd HH:ii)
page: number,
limit: number, // default 20. 1-100
keyword?: string
start?: string // format datetime(YYYY-mm-dd HH:ii)
end?: string // format datetime(YYYY-mm-dd HH:ii)
page: number
limit: number // default 20. 1-100
}
export type LogSessionListResponse = {
data: {
id: string,
conversation_id: string,
query: string, // user's query question
message: string, // prompt send to LLM
answer: string,
creat_at: string,
}[],
total: number,
page: number,
id: string
conversation_id: string
query: string // user's query question
message: string // prompt send to LLM
answer: string
creat_at: string
}[]
total: number
page: number
}
// log session detail and debug
export type LogSessionDetailResponse = {
id: string,
cnversation_id: string,
model_provider: string,
query: string,
inputs: Record<string, string | number | object>[],
message: string,
message_tokens: number, // number of tokens in message
answer: string,
answer_tokens: number, // number of tokens in answer
provider_response_latency: number, // used time in ms
from_source: 'api' | 'log',
id: string
cnversation_id: string
model_provider: string
query: string
inputs: Record<string, string | number | object>[]
message: string
message_tokens: number // number of tokens in message
answer: string
answer_tokens: number // number of tokens in answer
provider_response_latency: number // used time in ms
from_source: 'api' | 'log'
}
export type SavedMessage = {
id: string,
id: string
answer: string
}
\ No newline at end of file
}
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 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')({
extension: /\.mdx?$/,
......@@ -56,8 +57,8 @@ const nextConfig = {
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup
const sentryWebpackPluginOptions = {
org: 'perfectworld',
project: 'javascript-nextjs',
org: SENTRY_ORG,
project: SENTRY_PROJECT,
silent: true, // Suppresses all logs
sourcemaps: {
assets: './**',
......
......@@ -12,6 +12,7 @@
"prepare": "cd ../ && husky install ./web/.husky"
},
"dependencies": {
"@babel/runtime": "^7.22.3",
"@emoji-mart/data": "^1.1.2",
"@formatjs/intl-localematcher": "^0.2.32",
"@headlessui/react": "^1.7.13",
......@@ -20,6 +21,8 @@
"@mdx-js/react": "^2.3.0",
"@next/mdx": "^13.2.4",
"@sentry/nextjs": "^7.53.1",
"@sentry/utils": "^7.54.0",
"@tailwindcss/typography": "^0.5.9",
"@tailwindcss/line-clamp": "^0.4.2",
"@types/crypto-js": "^4.1.1",
"@types/lodash-es": "^4.17.7",
......@@ -30,6 +33,7 @@
"@types/react-syntax-highlighter": "^15.5.6",
"@types/react-window": "^1.8.5",
"@types/react-window-infinite-loader": "^1.0.6",
"autoprefixer": "^10.4.14",
"ahooks": "^3.7.5",
"classnames": "^2.3.2",
"copy-to-clipboard": "^3.3.3",
......@@ -40,10 +44,12 @@
"emoji-mart": "^5.5.2",
"eslint": "8.36.0",
"eslint-config-next": "13.2.4",
"husky": "^8.0.3",
"i18next": "^22.4.13",
"i18next-resources-to-backend": "^1.1.3",
"immer": "^9.0.19",
"js-cookie": "^3.0.1",
"katex": "^0.16.7",
"lodash-es": "^4.17.21",
"negotiator": "^0.6.3",
"next": "13.2.4",
......@@ -68,23 +74,20 @@
"scheduler": "^0.23.0",
"server-only": "^0.0.1",
"swr": "^2.1.0",
"tailwindcss": "^3.2.7",
"typescript": "4.9.5",
"use-context-selector": "^1.4.1"
},
"devDependencies": {
"@antfu/eslint-config": "^0.36.0",
"@faker-js/faker": "^7.6.0",
"@tailwindcss/typography": "^0.5.9",
"@types/js-cookie": "^3.0.3",
"@types/negotiator": "^0.6.1",
"@types/qs": "^6.9.7",
"autoprefixer": "^10.4.14",
"eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.3",
"lint-staged": "^13.2.2",
"miragejs": "^0.1.47",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.7"
"postcss": "^8.4.21"
},
"lint-staged": {
"**/*.js?(x)": [
......
import * as Sentry from '@sentry/nextjs'
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
integrations: [new Sentry.Replay()],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
tracesSampleRate: 0.1,
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
......
import * as Sentry from '@sentry/nextjs'
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%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
tracesSampleRate: 0.1,
// ...
......
import * as Sentry from '@sentry/nextjs'
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%
// of transactions for performance monitoring.
// 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'
const TIME_OUT = 100000
......@@ -46,12 +47,11 @@ function unicodeToChar(text: string) {
})
}
export function format(text: string) {
let res = text.trim()
if (res.startsWith('\n')) {
if (res.startsWith('\n'))
res = res.replace('\n', '')
}
return res.replaceAll('\n', '<br/>').replaceAll('```', '')
}
......@@ -77,12 +77,22 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted
lines.forEach((message) => {
if (message.startsWith('data: ')) { // check if it starts with data:
// console.log(message);
bufferObj = JSON.parse(message.substring(6)) // remove data: and parse as json
try {
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) {
onData('', false, {
conversationId: undefined,
messageId: '',
errorMessage: bufferObj.message
errorMessage: bufferObj.message,
})
hasError = true
onCompleted && onCompleted(true)
......@@ -97,19 +107,19 @@ const handleStream = (response: any, onData: IOnData, onCompleted?: IOnCompleted
}
})
buffer = lines[lines.length - 1]
} catch (e) {
}
catch (e) {
onData('', false, {
conversationId: undefined,
messageId: '',
errorMessage: e + ''
errorMessage: `${e}`,
})
hasError = true
onCompleted && onCompleted(true)
return
}
if (!hasError) {
if (!hasError)
read()
}
})
}
read()
......@@ -120,8 +130,8 @@ const baseFetch = (
fetchOptions: any,
{
isPublicAPI = false,
needAllResponseContent
}: IOtherOptions
needAllResponseContent,
}: IOtherOptions,
) => {
const options = Object.assign({}, baseOptions, fetchOptions)
if (isPublicAPI) {
......@@ -129,7 +139,7 @@ const baseFetch = (
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}`}`
const { method, params, body } = options
......@@ -176,12 +186,14 @@ const baseFetch = (
bodyJson.then((data: any) => {
if (data.code === 'not_setup') {
globalThis.location.href = `${globalThis.location.origin}/install`
} else {
}
else {
if (location.pathname === '/signin') {
bodyJson.then((data: any) => {
Toast.notify({ type: 'error', message: data.message })
})
} else {
}
else {
globalThis.location.href = loginUrl
}
}
......@@ -195,15 +207,13 @@ const baseFetch = (
new Promise(() => {
bodyJson.then((data: any) => {
Toast.notify({ type: 'error', message: data.message })
if (data.code === 'already_setup') {
if (data.code === 'already_setup')
globalThis.location.href = `${globalThis.location.origin}/signin`
}
})
})
break
// fall through
default:
// eslint-disable-next-line no-new
new Promise(() => {
bodyJson.then((data: any) => {
Toast.notify({ type: 'error', message: data.message })
......@@ -215,7 +225,7 @@ const baseFetch = (
// handle delete api. Delete api not return content.
if (res.status === 204) {
resolve({ result: "success" })
resolve({ result: 'success' })
return
}
......@@ -243,22 +253,21 @@ export const upload = (options: any): Promise<any> => {
...defaultOptions,
...options,
headers: { ...defaultOptions.headers, ...options.headers },
};
return new Promise(function (resolve, reject) {
}
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.open(options.method, options.url)
for (const key in options.headers)
xhr.setRequestHeader(key, options.headers[key])
xhr.withCredentials = true
xhr.responseType = 'json'
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 201) {
if (xhr.status === 201)
resolve(xhr.response)
} else {
else
reject(xhr)
}
}
}
xhr.upload.onprogress = options.onprogress
......@@ -287,7 +296,6 @@ export const ssePost = (url: string, fetchOptions: any, { isPublicAPI = false, o
.then((res: any) => {
// debugger
if (!/^(2|3)\d{2}$/.test(res.status)) {
// eslint-disable-next-line no-new
new Promise(() => {
res.json().then((data: any) => {
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 }: {
onData: IOnData
onCompleted: IOnCompleted
onError: IOnError,
onError: IOnError
getAbortController?: (abortController: AbortController) => void
}) => {
return ssePost(`apps/${appId}/chat-messages`, {
body: {
...body,
response_mode: 'streaming'
}
response_mode: 'streaming',
},
}, { onData, onCompleted, onError, getAbortController })
}
......@@ -22,8 +23,8 @@ export const sendCompletionMessage = async (appId: string, body: Record<string,
return ssePost(`apps/${appId}/completion-messages`, {
body: {
...body,
response_mode: 'streaming'
}
response_mode: 'streaming',
},
}, { onData, onCompleted, onError })
}
......@@ -34,7 +35,13 @@ export const fetchSuggestedQuestions = (appId: string, messageId: string) => {
export const fetchConvesationMessages = (appId: string, conversation_id: string) => {
return get(`apps/${appId}/chat-messages`, {
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