mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-04 03:25:30 +08:00
Compare commits
4 Commits
ad03ede7cd
...
5b5f19cbc1
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b5f19cbc1 | |||
| ea38e12d42 | |||
| 885eb2eab9 | |||
| 6587acef88 |
8
.github/workflows/tests.yml
vendored
8
.github/workflows/tests.yml
vendored
@ -127,6 +127,14 @@ jobs:
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Run unit test
|
||||
run: |
|
||||
uv sync --python 3.10 --group test --frozen
|
||||
source .venv/bin/activate
|
||||
which pytest || echo "pytest not in PATH"
|
||||
echo "Start to run unit test"
|
||||
python3 run_tests.py
|
||||
|
||||
- name: Build ragflow:nightly
|
||||
run: |
|
||||
RUNNER_WORKSPACE_PREFIX=${RUNNER_WORKSPACE_PREFIX:-${HOME}}
|
||||
|
||||
@ -190,6 +190,11 @@ class WebDAVConnector(LoadConnector, PollConnector):
|
||||
files = self._list_files_recursive(self.remote_path, start, end)
|
||||
logging.info(f"Found {len(files)} files matching time criteria")
|
||||
|
||||
filename_counts: dict[str, int] = {}
|
||||
for file_path, _ in files:
|
||||
file_name = os.path.basename(file_path)
|
||||
filename_counts[file_name] = filename_counts.get(file_name, 0) + 1
|
||||
|
||||
batch: list[Document] = []
|
||||
for file_path, file_info in files:
|
||||
file_name = os.path.basename(file_path)
|
||||
@ -237,12 +242,22 @@ class WebDAVConnector(LoadConnector, PollConnector):
|
||||
else:
|
||||
modified = datetime.now(timezone.utc)
|
||||
|
||||
if filename_counts.get(file_name, 0) > 1:
|
||||
relative_path = file_path
|
||||
if file_path.startswith(self.remote_path):
|
||||
relative_path = file_path[len(self.remote_path):]
|
||||
if relative_path.startswith('/'):
|
||||
relative_path = relative_path[1:]
|
||||
semantic_id = relative_path.replace('/', ' / ') if relative_path else file_name
|
||||
else:
|
||||
semantic_id = file_name
|
||||
|
||||
batch.append(
|
||||
Document(
|
||||
id=f"webdav:{self.base_url}:{file_path}",
|
||||
blob=blob,
|
||||
source=DocumentSource.WEBDAV,
|
||||
semantic_identifier=file_name,
|
||||
semantic_identifier=semantic_id,
|
||||
extension=get_file_ext(file_name),
|
||||
doc_updated_at=modified,
|
||||
size_bytes=size_bytes if size_bytes else 0
|
||||
|
||||
@ -131,7 +131,6 @@ dependencies = [
|
||||
"graspologic @ git+https://github.com/yuzhichang/graspologic.git@38e680cab72bc9fb68a7992c3bcc2d53b24e42fd",
|
||||
"mini-racer>=0.12.4,<0.13.0",
|
||||
"pyodbc>=5.2.0,<6.0.0",
|
||||
"pyicu>=2.15.3,<3.0.0",
|
||||
"flasgger>=0.9.7.1,<0.10.0",
|
||||
"xxhash>=3.5.0,<4.0.0",
|
||||
"trio>=0.17.0,<0.29.0",
|
||||
@ -163,6 +162,9 @@ test = [
|
||||
"openpyxl>=3.1.5",
|
||||
"pillow>=10.4.0",
|
||||
"pytest>=8.3.5",
|
||||
"pytest-asyncio>=1.3.0",
|
||||
"pytest-xdist>=3.8.0",
|
||||
"pytest-cov>=7.0.0",
|
||||
"python-docx>=1.1.2",
|
||||
"python-pptx>=1.0.2",
|
||||
"reportlab>=4.4.1",
|
||||
@ -195,8 +197,83 @@ extend-select = ["ASYNC", "ASYNC1"]
|
||||
ignore = ["E402"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = [
|
||||
"."
|
||||
]
|
||||
|
||||
testpaths = ["test"]
|
||||
python_files = ["test_*.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
|
||||
markers = [
|
||||
"p1: high priority test cases",
|
||||
"p2: medium priority test cases",
|
||||
"p3: low priority test cases",
|
||||
]
|
||||
|
||||
# Test collection and runtime configuration
|
||||
filterwarnings = [
|
||||
"error", # Treat warnings as errors
|
||||
"ignore::DeprecationWarning", # Ignore specific warnings
|
||||
]
|
||||
|
||||
# Command line options
|
||||
addopts = [
|
||||
"-v", # Verbose output
|
||||
"--strict-markers", # Enforce marker definitions
|
||||
"--tb=short", # Simplified traceback
|
||||
"--disable-warnings", # Disable warnings
|
||||
"--color=yes" # Colored output
|
||||
]
|
||||
|
||||
|
||||
# Coverage configuration
|
||||
[tool.coverage.run]
|
||||
# Source paths - adjust according to your project structure
|
||||
source = [
|
||||
# "../../api/db/services",
|
||||
# Add more directories if needed:
|
||||
"../../common",
|
||||
# "../../utils",
|
||||
]
|
||||
|
||||
# Files/directories to exclude
|
||||
omit = [
|
||||
"*/tests/*",
|
||||
"*/test_*",
|
||||
"*/__pycache__/*",
|
||||
"*/.pytest_cache/*",
|
||||
"*/venv/*",
|
||||
"*/.venv/*",
|
||||
"*/env/*",
|
||||
"*/site-packages/*",
|
||||
"*/dist/*",
|
||||
"*/build/*",
|
||||
"*/migrations/*",
|
||||
"setup.py"
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
# Report configuration
|
||||
precision = 2
|
||||
show_missing = true
|
||||
skip_covered = false
|
||||
fail_under = 0 # Minimum coverage requirement (0-100)
|
||||
|
||||
# Lines to exclude (optional)
|
||||
exclude_lines = [
|
||||
# "pragma: no cover",
|
||||
# "def __repr__",
|
||||
# "raise AssertionError",
|
||||
# "raise NotImplementedError",
|
||||
# "if __name__ == .__main__.:",
|
||||
# "if TYPE_CHECKING:",
|
||||
"pass"
|
||||
]
|
||||
|
||||
[tool.coverage.html]
|
||||
# HTML report configuration
|
||||
directory = "htmlcov"
|
||||
title = "Test Coverage Report"
|
||||
# extra_css = "custom.css" # Optional custom CSS
|
||||
275
run_tests.py
Executable file
275
run_tests.py
Executable file
@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
||||
class Colors:
|
||||
"""ANSI color codes for terminal output"""
|
||||
RED = '\033[0;31m'
|
||||
GREEN = '\033[0;32m'
|
||||
YELLOW = '\033[1;33m'
|
||||
BLUE = '\033[0;34m'
|
||||
NC = '\033[0m' # No Color
|
||||
|
||||
|
||||
class TestRunner:
|
||||
"""RAGFlow Unit Test Runner"""
|
||||
|
||||
def __init__(self):
|
||||
self.project_root = Path(__file__).parent.resolve()
|
||||
self.ut_dir = Path(self.project_root / 'test' / 'unit_test')
|
||||
# Default options
|
||||
self.coverage = False
|
||||
self.parallel = False
|
||||
self.verbose = False
|
||||
self.markers = ""
|
||||
|
||||
# Python interpreter path
|
||||
self.python = sys.executable
|
||||
|
||||
@staticmethod
|
||||
def print_info(message: str) -> None:
|
||||
"""Print informational message"""
|
||||
print(f"{Colors.BLUE}[INFO]{Colors.NC} {message}")
|
||||
|
||||
@staticmethod
|
||||
def print_error(message: str) -> None:
|
||||
"""Print error message"""
|
||||
print(f"{Colors.RED}[ERROR]{Colors.NC} {message}")
|
||||
|
||||
@staticmethod
|
||||
def show_usage() -> None:
|
||||
"""Display usage information"""
|
||||
usage = """
|
||||
RAGFlow Unit Test Runner
|
||||
Usage: python run_tests.py [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
-c, --coverage Run tests with coverage report
|
||||
-p, --parallel Run tests in parallel (requires pytest-xdist)
|
||||
-v, --verbose Verbose output
|
||||
-t, --test FILE Run specific test file or directory
|
||||
-m, --markers MARKERS Run tests with specific markers (e.g., "unit", "integration")
|
||||
|
||||
EXAMPLES:
|
||||
# Run all tests
|
||||
python run_tests.py
|
||||
|
||||
# Run with coverage
|
||||
python run_tests.py --coverage
|
||||
|
||||
# Run in parallel
|
||||
python run_tests.py --parallel
|
||||
|
||||
# Run specific test file
|
||||
python run_tests.py --test services/test_dialog_service.py
|
||||
|
||||
# Run only unit tests
|
||||
python run_tests.py --markers "unit"
|
||||
|
||||
# Run tests with coverage and parallel execution
|
||||
python run_tests.py --coverage --parallel
|
||||
|
||||
"""
|
||||
print(usage)
|
||||
|
||||
def build_pytest_command(self) -> List[str]:
|
||||
"""Build the pytest command arguments"""
|
||||
cmd = ["pytest", str(self.ut_dir)]
|
||||
|
||||
# Add test path
|
||||
|
||||
# Add markers
|
||||
if self.markers:
|
||||
cmd.extend(["-m", self.markers])
|
||||
|
||||
# Add verbose flag
|
||||
if self.verbose:
|
||||
cmd.extend(["-vv"])
|
||||
else:
|
||||
cmd.append("-v")
|
||||
|
||||
# Add coverage
|
||||
if self.coverage:
|
||||
# Relative path from test directory to source code
|
||||
source_path = str(self.project_root / "common")
|
||||
cmd.extend([
|
||||
"--cov", source_path,
|
||||
"--cov-report", "html",
|
||||
"--cov-report", "term"
|
||||
])
|
||||
|
||||
# Add parallel execution
|
||||
if self.parallel:
|
||||
# Try to get number of CPU cores
|
||||
try:
|
||||
import multiprocessing
|
||||
cpu_count = multiprocessing.cpu_count()
|
||||
cmd.extend(["-n", str(cpu_count)])
|
||||
except ImportError:
|
||||
# Fallback to auto if multiprocessing not available
|
||||
cmd.extend(["-n", "auto"])
|
||||
|
||||
# Add default options from pyproject.toml if it exists
|
||||
pyproject_path = self.project_root / "pyproject.toml"
|
||||
if pyproject_path.exists():
|
||||
cmd.extend(["--config-file", str(pyproject_path)])
|
||||
|
||||
return cmd
|
||||
|
||||
def run_tests(self) -> bool:
|
||||
"""Execute the pytest command"""
|
||||
# Change to test directory
|
||||
os.chdir(self.project_root)
|
||||
|
||||
# Build command
|
||||
cmd = self.build_pytest_command()
|
||||
|
||||
# Print test configuration
|
||||
self.print_info("Running RAGFlow Unit Tests")
|
||||
self.print_info("=" * 40)
|
||||
self.print_info(f"Test Directory: {self.ut_dir}")
|
||||
self.print_info(f"Coverage: {self.coverage}")
|
||||
self.print_info(f"Parallel: {self.parallel}")
|
||||
self.print_info(f"Verbose: {self.verbose}")
|
||||
|
||||
if self.markers:
|
||||
self.print_info(f"Markers: {self.markers}")
|
||||
|
||||
print(f"\n{Colors.BLUE}[EXECUTING]{Colors.NC} {' '.join(cmd)}\n")
|
||||
|
||||
# Run pytest
|
||||
try:
|
||||
result = subprocess.run(cmd, check=False)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"\n{Colors.GREEN}[SUCCESS]{Colors.NC} All tests passed!")
|
||||
|
||||
if self.coverage:
|
||||
coverage_dir = self.ut_dir / "htmlcov"
|
||||
if coverage_dir.exists():
|
||||
index_file = coverage_dir / "index.html"
|
||||
print(f"\n{Colors.BLUE}[INFO]{Colors.NC} Coverage report generated:")
|
||||
print(f" {index_file}")
|
||||
print("\nOpen with:")
|
||||
print(f" - Windows: start {index_file}")
|
||||
print(f" - macOS: open {index_file}")
|
||||
print(f" - Linux: xdg-open {index_file}")
|
||||
|
||||
return True
|
||||
else:
|
||||
print(f"\n{Colors.RED}[FAILURE]{Colors.NC} Some tests failed!")
|
||||
return False
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{Colors.YELLOW}[INTERRUPTED]{Colors.NC} Test execution interrupted by user")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.print_error(f"Failed to execute tests: {e}")
|
||||
return False
|
||||
|
||||
def parse_arguments(self) -> bool:
|
||||
"""Parse command line arguments"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="RAGFlow Unit Test Runner",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
python run_tests.py # Run all tests
|
||||
python run_tests.py --coverage # Run with coverage
|
||||
python run_tests.py --parallel # Run in parallel
|
||||
python run_tests.py --test services/test_dialog_service.py # Run specific test
|
||||
python run_tests.py --markers "unit" # Run only unit tests
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-c", "--coverage",
|
||||
action="store_true",
|
||||
help="Run tests with coverage report"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-p", "--parallel",
|
||||
action="store_true",
|
||||
help="Run tests in parallel (requires pytest-xdist)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-v", "--verbose",
|
||||
action="store_true",
|
||||
help="Verbose output"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-t", "--test",
|
||||
type=str,
|
||||
default="",
|
||||
help="Run specific test file or directory"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-m", "--markers",
|
||||
type=str,
|
||||
default="",
|
||||
help="Run tests with specific markers (e.g., 'unit', 'integration')"
|
||||
)
|
||||
|
||||
try:
|
||||
args = parser.parse_args()
|
||||
|
||||
# Set options
|
||||
self.coverage = args.coverage
|
||||
self.parallel = args.parallel
|
||||
self.verbose = args.verbose
|
||||
self.markers = args.markers
|
||||
|
||||
return True
|
||||
|
||||
except SystemExit:
|
||||
# argparse already printed help, just exit
|
||||
return False
|
||||
except Exception as e:
|
||||
self.print_error(f"Error parsing arguments: {e}")
|
||||
return False
|
||||
|
||||
def run(self) -> int:
|
||||
"""Main execution method"""
|
||||
# Parse command line arguments
|
||||
if not self.parse_arguments():
|
||||
return 1
|
||||
|
||||
# Run tests
|
||||
success = self.run_tests()
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
def main():
|
||||
"""Entry point"""
|
||||
runner = TestRunner()
|
||||
return runner.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
137
uv.lock
generated
137
uv.lock
generated
@ -443,6 +443,15 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backports-asyncio-runner"
|
||||
version = "1.2.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bce-python-sdk"
|
||||
version = "0.9.55"
|
||||
@ -1025,6 +1034,58 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "7.12.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/4a/0dc3de1c172d35abe512332cfdcc43211b6ebce629e4cc42e6cd25ed8f4d/coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b", size = 217409, upload-time = "2025-11-18T13:31:53.122Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/c3/086198b98db0109ad4f84241e8e9ea7e5fb2db8c8ffb787162d40c26cc76/coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c", size = 217927, upload-time = "2025-11-18T13:31:54.458Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/5f/34614dbf5ce0420828fc6c6f915126a0fcb01e25d16cf141bf5361e6aea6/coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832", size = 244678, upload-time = "2025-11-18T13:31:55.805Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/7b/6b26fb32e8e4a6989ac1d40c4e132b14556131493b1d06bc0f2be169c357/coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa", size = 246507, upload-time = "2025-11-18T13:31:57.05Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/42/7d70e6603d3260199b90fb48b537ca29ac183d524a65cc31366b2e905fad/coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73", size = 248366, upload-time = "2025-11-18T13:31:58.362Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/4a/d86b837923878424c72458c5b25e899a3c5ca73e663082a915f5b3c4d749/coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb", size = 245366, upload-time = "2025-11-18T13:31:59.572Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/c2/2adec557e0aa9721875f06ced19730fdb7fc58e31b02b5aa56f2ebe4944d/coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e", size = 246408, upload-time = "2025-11-18T13:32:00.784Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/4b/8bd1f1148260df11c618e535fdccd1e5aaf646e55b50759006a4f41d8a26/coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777", size = 244416, upload-time = "2025-11-18T13:32:01.963Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/13/3a248dd6a83df90414c54a4e121fd081fb20602ca43955fbe1d60e2312a9/coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553", size = 244681, upload-time = "2025-11-18T13:32:03.408Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/30/aa833827465a5e8c938935f5d91ba055f70516941078a703740aaf1aa41f/coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d", size = 245300, upload-time = "2025-11-18T13:32:04.686Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/24/f85b3843af1370fb3739fa7571819b71243daa311289b31214fe3e8c9d68/coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef", size = 220008, upload-time = "2025-11-18T13:32:05.806Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/a2/c7da5b9566f7164db9eefa133d17761ecb2c2fde9385d754e5b5c80f710d/coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022", size = 220943, upload-time = "2025-11-18T13:32:07.166Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
toml = [
|
||||
{ name = "tomli", marker = "python_full_version <= '3.11'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cramjam"
|
||||
version = "2.11.0"
|
||||
@ -1512,6 +1573,15 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "execnet"
|
||||
version = "2.1.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "extract-msg"
|
||||
version = "0.41.5"
|
||||
@ -3925,12 +3995,12 @@ name = "onnxruntime-gpu"
|
||||
version = "1.19.2"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "coloredlogs" },
|
||||
{ name = "flatbuffers" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "sympy" },
|
||||
{ name = "coloredlogs", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "flatbuffers", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "packaging", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "protobuf", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
{ name = "sympy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/9c/3fa310e0730643051eb88e884f19813a6c8b67d0fbafcda610d960e589db/onnxruntime_gpu-1.19.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a49740e079e7c5215830d30cde3df792e903df007aa0b0fd7aa797937061b27a", size = 226178508, upload-time = "2024-09-04T06:43:40.83Z" },
|
||||
@ -4994,12 +5064,6 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyicu"
|
||||
version = "2.16"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/c3/8d558b30deb33eb583c0bcae3e64d6db8316b69461a04bb9db5ff63d3f6e/pyicu-2.16.tar.gz", hash = "sha256:42b3a8062e3b23e927ca727e6b5e1730d86c70279834e4887152895d2eb012d9", size = 268126, upload-time = "2025-11-04T23:33:00.006Z" }
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.8.0"
|
||||
@ -5200,6 +5264,47 @@ wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
|
||||
{ name = "pytest" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "7.0.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "coverage", extra = ["toml"] },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.8.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "execnet" },
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-calamine"
|
||||
version = "0.6.1"
|
||||
@ -5563,7 +5668,6 @@ dependencies = [
|
||||
{ name = "psycopg2-binary" },
|
||||
{ name = "pyclipper" },
|
||||
{ name = "pycryptodomex" },
|
||||
{ name = "pyicu" },
|
||||
{ name = "pymysql" },
|
||||
{ name = "pyobvector" },
|
||||
{ name = "pyodbc" },
|
||||
@ -5625,6 +5729,9 @@ test = [
|
||||
{ name = "openpyxl" },
|
||||
{ name = "pillow" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-xdist" },
|
||||
{ name = "python-docx" },
|
||||
{ name = "python-pptx" },
|
||||
{ name = "reportlab" },
|
||||
@ -5728,7 +5835,6 @@ requires-dist = [
|
||||
{ name = "psycopg2-binary", specifier = "==2.9.9" },
|
||||
{ name = "pyclipper", specifier = "==1.3.0.post5" },
|
||||
{ name = "pycryptodomex", specifier = "==3.20.0" },
|
||||
{ name = "pyicu", specifier = ">=2.15.3,<3.0.0" },
|
||||
{ name = "pymysql", specifier = ">=1.1.1,<2.0.0" },
|
||||
{ name = "pyobvector", specifier = "==0.2.18" },
|
||||
{ name = "pyodbc", specifier = ">=5.2.0,<6.0.0" },
|
||||
@ -5790,6 +5896,9 @@ test = [
|
||||
{ name = "openpyxl", specifier = ">=3.1.5" },
|
||||
{ name = "pillow", specifier = ">=10.4.0" },
|
||||
{ name = "pytest", specifier = ">=8.3.5" },
|
||||
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
|
||||
{ name = "pytest-cov", specifier = ">=7.0.0" },
|
||||
{ name = "pytest-xdist", specifier = ">=3.8.0" },
|
||||
{ name = "python-docx", specifier = ">=1.1.2" },
|
||||
{ name = "python-pptx", specifier = ">=1.0.2" },
|
||||
{ name = "reportlab", specifier = ">=4.4.1" },
|
||||
|
||||
@ -433,7 +433,7 @@ export const useSelectDerivedMessages = () => {
|
||||
);
|
||||
|
||||
const addNewestQuestion = useCallback(
|
||||
(message: Message, answer: string = '') => {
|
||||
(message: IMessage, answer: string = '') => {
|
||||
setDerivedMessages((pre) => {
|
||||
return [
|
||||
...pre,
|
||||
@ -446,6 +446,7 @@ export const useSelectDerivedMessages = () => {
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
content: answer,
|
||||
conversationId: message.conversationId,
|
||||
id: buildMessageUuid({ ...message, role: MessageType.Assistant }),
|
||||
},
|
||||
];
|
||||
|
||||
@ -16,11 +16,11 @@ import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-
|
||||
import { isConversationIdExist } from '@/pages/next-chats/utils';
|
||||
import chatService from '@/services/next-chat-service';
|
||||
import api from '@/utils/api';
|
||||
import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
|
||||
import { buildMessageListWithUuid, generateConversationId } from '@/utils/chat';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
import { has } from 'lodash';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import {
|
||||
@ -36,6 +36,7 @@ export const enum ChatApiAction {
|
||||
FetchDialog = 'fetchDialog',
|
||||
FetchConversationList = 'fetchConversationList',
|
||||
FetchConversation = 'fetchConversation',
|
||||
FetchConversationManually = 'fetchConversationManually',
|
||||
UpdateConversation = 'updateConversation',
|
||||
RemoveConversation = 'removeConversation',
|
||||
DeleteMessage = 'deleteMessage',
|
||||
@ -59,29 +60,6 @@ export const useGetChatSearchParams = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useClickDialogCard = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
|
||||
const newQueryParameters: URLSearchParams = useMemo(() => {
|
||||
return new URLSearchParams();
|
||||
}, []);
|
||||
|
||||
const handleClickDialog = useCallback(
|
||||
(dialogId: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.DialogId, dialogId);
|
||||
// newQueryParameters.set(
|
||||
// ChatSearchParams.ConversationId,
|
||||
// EmptyConversationId,
|
||||
// );
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[newQueryParameters, setSearchParams],
|
||||
);
|
||||
|
||||
return { handleClickDialog };
|
||||
};
|
||||
|
||||
export const useFetchDialogList = () => {
|
||||
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||
@ -222,28 +200,8 @@ export const useFetchDialog = () => {
|
||||
|
||||
//#region Conversation
|
||||
|
||||
export const useClickConversationCard = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const handleClickConversation = useCallback(
|
||||
(conversationId: string, isNew: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||
newQueryParameters.set(ChatSearchParams.isNew, isNew);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[setSearchParams, newQueryParameters],
|
||||
);
|
||||
|
||||
return { handleClickConversation };
|
||||
};
|
||||
|
||||
export const useFetchConversationList = () => {
|
||||
const { id } = useParams();
|
||||
const { handleClickConversation } = useClickConversationCard();
|
||||
|
||||
const { searchString, handleInputChange } = useHandleSearchStrChange();
|
||||
|
||||
@ -267,13 +225,6 @@ export const useFetchConversationList = () => {
|
||||
{ params: { dialog_id: id } },
|
||||
true,
|
||||
);
|
||||
if (data.code === 0) {
|
||||
if (data.data.length > 0) {
|
||||
handleClickConversation(data.data[0].id, '');
|
||||
} else {
|
||||
handleClickConversation('', '');
|
||||
}
|
||||
}
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
@ -281,45 +232,33 @@ export const useFetchConversationList = () => {
|
||||
return { data, loading, refetch, searchString, handleInputChange };
|
||||
};
|
||||
|
||||
export const useFetchConversation = () => {
|
||||
const { isNew, conversationId } = useGetChatSearchParams();
|
||||
const { sharedId } = useGetSharedChatSearchParams();
|
||||
export function useFetchConversationManually() {
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IClientConversation>({
|
||||
queryKey: [ChatApiAction.FetchConversation, conversationId],
|
||||
initialData: {} as IClientConversation,
|
||||
// enabled: isConversationIdExist(conversationId),
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
if (
|
||||
isNew !== 'true' &&
|
||||
isConversationIdExist(sharedId || conversationId)
|
||||
) {
|
||||
const { data } = await chatService.getConversation(
|
||||
{
|
||||
params: {
|
||||
conversationId: conversationId || sharedId,
|
||||
},
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation<IClientConversation, unknown, string>({
|
||||
mutationKey: [ChatApiAction.FetchConversationManually],
|
||||
mutationFn: async (conversationId) => {
|
||||
const { data } = await chatService.getConversation(
|
||||
{
|
||||
params: {
|
||||
conversationId,
|
||||
},
|
||||
true,
|
||||
);
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
const conversation = data?.data ?? {};
|
||||
const conversation = data?.data ?? {};
|
||||
|
||||
const messageList = buildMessageListWithUuid(conversation?.message);
|
||||
const messageList = buildMessageListWithUuid(conversation?.message);
|
||||
|
||||
return { ...conversation, message: messageList };
|
||||
}
|
||||
return { message: [] };
|
||||
return { ...conversation, message: messageList };
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
return { data, loading, fetchConversationManually: mutateAsync };
|
||||
}
|
||||
|
||||
export const useUpdateConversation = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -335,7 +274,7 @@ export const useUpdateConversation = () => {
|
||||
...params,
|
||||
conversation_id: params.conversation_id
|
||||
? params.conversation_id
|
||||
: getConversationId(),
|
||||
: generateConversationId(),
|
||||
});
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
|
||||
@ -24,6 +24,15 @@ import { buildLlmUuid } from '@/utils/llm-util';
|
||||
|
||||
export const enum LLMApiAction {
|
||||
LlmList = 'llmList',
|
||||
MyLlmList = 'myLlmList',
|
||||
MyLlmListDetailed = 'myLlmListDetailed',
|
||||
FactoryList = 'factoryList',
|
||||
SaveApiKey = 'saveApiKey',
|
||||
SaveTenantInfo = 'saveTenantInfo',
|
||||
AddLlm = 'addLlm',
|
||||
DeleteLlm = 'deleteLlm',
|
||||
EnableLlm = 'enableLlm',
|
||||
DeleteFactory = 'deleteFactory',
|
||||
}
|
||||
|
||||
export const useFetchLlmList = (modelType?: LlmModelType) => {
|
||||
@ -177,7 +186,7 @@ export const useComposeLlmOptionsByModelTypes = (
|
||||
|
||||
export const useFetchLlmFactoryList = (): ResponseGetType<IFactory[]> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['factoryList'],
|
||||
queryKey: [LLMApiAction.FactoryList],
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
@ -196,7 +205,7 @@ export const useFetchMyLlmList = (): ResponseGetType<
|
||||
Record<string, IMyLlmValue>
|
||||
> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['myLlmList'],
|
||||
queryKey: [LLMApiAction.MyLlmList],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
@ -213,7 +222,7 @@ export const useFetchMyLlmListDetailed = (): ResponseGetType<
|
||||
Record<string, any>
|
||||
> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: ['myLlmListDetailed'],
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
@ -271,14 +280,16 @@ export const useSaveApiKey = () => {
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['saveApiKey'],
|
||||
mutationKey: [LLMApiAction.SaveApiKey],
|
||||
mutationFn: async (params: IApiKeySavingParams) => {
|
||||
const { data } = await userService.set_api_key(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
@ -303,7 +314,7 @@ export const useSaveTenantInfo = () => {
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['saveTenantInfo'],
|
||||
mutationKey: [LLMApiAction.SaveTenantInfo],
|
||||
mutationFn: async (params: ISystemModelSettingSavingParams) => {
|
||||
const { data } = await userService.set_tenant_info(params);
|
||||
if (data.code === 0) {
|
||||
@ -324,13 +335,16 @@ export const useAddLlm = () => {
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['addLlm'],
|
||||
mutationKey: [LLMApiAction.AddLlm],
|
||||
mutationFn: async (params: IAddLlmRequestBody) => {
|
||||
const { data } = await userService.add_llm(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.LlmList] });
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data.code;
|
||||
@ -348,13 +362,15 @@ export const useDeleteLlm = () => {
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteLlm'],
|
||||
mutationKey: [LLMApiAction.DeleteLlm],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody) => {
|
||||
const { data } = await userService.delete_llm(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
@ -372,7 +388,7 @@ export const useEnableLlm = () => {
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['enableLlm'],
|
||||
mutationKey: [LLMApiAction.EnableLlm],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody & { enable: boolean }) => {
|
||||
const reqParam: IDeleteLlmRequestBody & {
|
||||
enable?: boolean;
|
||||
@ -381,9 +397,11 @@ export const useEnableLlm = () => {
|
||||
delete reqParam.enable;
|
||||
const { data } = await userService.enable_llm(reqParam);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data.code;
|
||||
@ -401,14 +419,16 @@ export const useDeleteFactory = () => {
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: ['deleteFactory'],
|
||||
mutationKey: [LLMApiAction.DeleteFactory],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody) => {
|
||||
const { data } = await userService.deleteFactory(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['llmList'] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.LlmList] });
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
|
||||
@ -187,6 +187,7 @@ export interface IExternalChatInfo {
|
||||
export interface IMessage extends Message {
|
||||
id: string;
|
||||
reference?: IReference; // the latest news has reference
|
||||
conversationId?: string; // To distinguish which conversation the message belongs to
|
||||
}
|
||||
|
||||
export interface IClientConversation extends IConversation {
|
||||
|
||||
@ -15,12 +15,12 @@ import {
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import { useScrollToBottom } from '@/hooks/logic-hooks';
|
||||
import {
|
||||
useFetchConversation,
|
||||
useFetchDialog,
|
||||
useGetChatSearchParams,
|
||||
useSetDialog,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
|
||||
import { IClientConversation, IMessage } from '@/interfaces/database/chat';
|
||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { t } from 'i18next';
|
||||
@ -38,13 +38,14 @@ import { useCreateConversationBeforeUploadDocument } from '../../hooks/use-creat
|
||||
import { useSendMessage } from '../../hooks/use-send-chat-message';
|
||||
import { useSendMultipleChatMessage } from '../../hooks/use-send-multiple-message';
|
||||
import { buildMessageItemReference } from '../../utils';
|
||||
import { IMessage } from '../interface';
|
||||
import { useAddChatBox } from '../use-add-box';
|
||||
import { useSetDefaultModel } from './use-set-default-model';
|
||||
|
||||
type MultipleChatBoxProps = {
|
||||
controller: AbortController;
|
||||
chatBoxIds: string[];
|
||||
stopOutputMessage(): void;
|
||||
conversation: IClientConversation;
|
||||
} & Pick<
|
||||
ReturnType<typeof useAddChatBox>,
|
||||
'removeChatBox' | 'addChatBox' | 'chatBoxIds'
|
||||
@ -55,6 +56,7 @@ type ChatCardProps = {
|
||||
idx: number;
|
||||
derivedMessages: IMessage[];
|
||||
sendLoading: boolean;
|
||||
conversation: IClientConversation;
|
||||
} & Pick<
|
||||
MultipleChatBoxProps,
|
||||
'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds'
|
||||
@ -72,6 +74,7 @@ const ChatCard = forwardRef(function ChatCard(
|
||||
derivedMessages,
|
||||
sendLoading,
|
||||
clickDocumentButton,
|
||||
conversation,
|
||||
}: ChatCardProps,
|
||||
ref,
|
||||
) {
|
||||
@ -97,7 +100,8 @@ const ChatCard = forwardRef(function ChatCard(
|
||||
|
||||
const { data: userInfo } = useFetchUserInfo();
|
||||
const { data: currentDialog } = useFetchDialog();
|
||||
const { data: conversation } = useFetchConversation();
|
||||
|
||||
useSetDefaultModel(form);
|
||||
|
||||
const isLatestChat = idx === chatBoxIds.length - 1;
|
||||
|
||||
@ -202,6 +206,7 @@ export function MultipleChatBox({
|
||||
removeChatBox,
|
||||
addChatBox,
|
||||
stopOutputMessage,
|
||||
conversation,
|
||||
}: MultipleChatBoxProps) {
|
||||
const {
|
||||
value,
|
||||
@ -237,6 +242,7 @@ export function MultipleChatBox({
|
||||
ref={setFormRef(id)}
|
||||
sendLoading={sendLoading}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
conversation={conversation}
|
||||
></ChatCard>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -4,12 +4,13 @@ import PdfSheet from '@/components/pdf-drawer';
|
||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import {
|
||||
useFetchConversation,
|
||||
useFetchDialog,
|
||||
useGetChatSearchParams,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
|
||||
import { IClientConversation } from '@/interfaces/database/chat';
|
||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
useGetSendButtonDisabled,
|
||||
useSendButtonDisabled,
|
||||
@ -21,9 +22,14 @@ import { buildMessageItemReference } from '../../utils';
|
||||
interface IProps {
|
||||
controller: AbortController;
|
||||
stopOutputMessage(): void;
|
||||
conversation: IClientConversation;
|
||||
}
|
||||
|
||||
export function SingleChatBox({ controller, stopOutputMessage }: IProps) {
|
||||
export function SingleChatBox({
|
||||
controller,
|
||||
stopOutputMessage,
|
||||
conversation,
|
||||
}: IProps) {
|
||||
const {
|
||||
value,
|
||||
scrollRef,
|
||||
@ -37,18 +43,32 @@ export function SingleChatBox({ controller, stopOutputMessage }: IProps) {
|
||||
removeMessageById,
|
||||
handleUploadFile,
|
||||
removeFile,
|
||||
setDerivedMessages,
|
||||
} = useSendMessage(controller);
|
||||
const { data: userInfo } = useFetchUserInfo();
|
||||
const { data: currentDialog } = useFetchDialog();
|
||||
const { createConversationBeforeUploadDocument } =
|
||||
useCreateConversationBeforeUploadDocument();
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
const { data: conversation } = useFetchConversation();
|
||||
const disabled = useGetSendButtonDisabled();
|
||||
const sendDisabled = useSendButtonDisabled(value);
|
||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||
useClickDrawer();
|
||||
|
||||
useEffect(() => {
|
||||
const messages = conversation?.message;
|
||||
if (Array.isArray(messages)) {
|
||||
setDerivedMessages(messages);
|
||||
}
|
||||
}, [conversation?.message, setDerivedMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
// Clear the message list after deleting the conversation.
|
||||
if (conversationId === '') {
|
||||
setDerivedMessages([]);
|
||||
}
|
||||
}, [conversationId, setDerivedMessages]);
|
||||
|
||||
return (
|
||||
<section className="flex flex-col p-5 h-full">
|
||||
<div ref={messageContainerRef} className="flex-1 overflow-auto min-h-0">
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import { useMount } from 'ahooks';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
|
||||
export function useSetDefaultModel(form: UseFormReturn<any>) {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Chat,
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
|
||||
useMount(() => {
|
||||
const firstModel = modelOptions.at(0)?.options.at(0)?.value;
|
||||
if (firstModel) {
|
||||
form.setValue('llm_id', firstModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -5,11 +5,15 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useRemoveConversation } from '@/hooks/use-chat-request';
|
||||
import {
|
||||
useGetChatSearchParams,
|
||||
useRemoveConversation,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { IConversation } from '@/interfaces/database/chat';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useChatUrlParams } from '../hooks/use-chat-url';
|
||||
|
||||
export function ConversationDropdown({
|
||||
children,
|
||||
@ -20,22 +24,27 @@ export function ConversationDropdown({
|
||||
removeTemporaryConversation?: (conversationId: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { setConversationBoth } = useChatUrlParams();
|
||||
const { removeConversation } = useRemoveConversation();
|
||||
const { isNew } = useGetChatSearchParams();
|
||||
|
||||
const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
|
||||
if (conversation.is_new && removeTemporaryConversation) {
|
||||
removeTemporaryConversation(conversation.id);
|
||||
removeConversation([]);
|
||||
} else {
|
||||
removeConversation([conversation.id]);
|
||||
}
|
||||
}, [
|
||||
conversation.id,
|
||||
conversation.is_new,
|
||||
removeConversation,
|
||||
removeTemporaryConversation,
|
||||
]);
|
||||
const handleDelete: MouseEventHandler<HTMLDivElement> =
|
||||
useCallback(async () => {
|
||||
if (isNew === 'true' && removeTemporaryConversation) {
|
||||
removeTemporaryConversation(conversation.id);
|
||||
} else {
|
||||
const code = await removeConversation([conversation.id]);
|
||||
if (code === 0) {
|
||||
setConversationBoth('', '');
|
||||
}
|
||||
}
|
||||
}, [
|
||||
conversation.id,
|
||||
isNew,
|
||||
removeConversation,
|
||||
removeTemporaryConversation,
|
||||
setConversationBoth,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
|
||||
@ -15,13 +15,17 @@ import { SharedFrom } from '@/constants/chat';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import {
|
||||
useFetchConversation,
|
||||
useFetchConversationList,
|
||||
useFetchConversationManually,
|
||||
useFetchDialog,
|
||||
useGetChatSearchParams,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { IClientConversation } from '@/interfaces/database/chat';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useMount } from 'ahooks';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { ArrowUpRight, LogOut, Send } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
||||
@ -37,26 +41,54 @@ export default function Chat() {
|
||||
const { navigateToChatList } = useNavigatePage();
|
||||
const { data } = useFetchDialog();
|
||||
const { t } = useTranslation();
|
||||
const { data: conversation } = useFetchConversation();
|
||||
const [currentConversation, setCurrentConversation] =
|
||||
useState<IClientConversation>({} as IClientConversation);
|
||||
|
||||
const { fetchConversationManually } = useFetchConversationManually();
|
||||
|
||||
const { handleConversationCardClick, controller, stopOutputMessage } =
|
||||
useHandleClickConversationCard();
|
||||
const { visible: settingVisible, switchVisible: switchSettingVisible } =
|
||||
useSetModalState(true);
|
||||
const {
|
||||
removeChatBox,
|
||||
addChatBox,
|
||||
chatBoxIds,
|
||||
hasSingleChatBox,
|
||||
hasThreeChatBox,
|
||||
} = useAddChatBox();
|
||||
|
||||
const { isDebugMode, switchDebugMode } = useSwitchDebugMode();
|
||||
const { removeChatBox, addChatBox, chatBoxIds, hasSingleChatBox } =
|
||||
useAddChatBox(isDebugMode);
|
||||
|
||||
const { showEmbedModal, hideEmbedModal, embedVisible, beta } =
|
||||
useShowEmbedModal();
|
||||
|
||||
const { conversationId, isNew } = useGetChatSearchParams();
|
||||
|
||||
const { isDebugMode, switchDebugMode } = useSwitchDebugMode();
|
||||
const { data: dialogList } = useFetchConversationList();
|
||||
|
||||
const currentConversationName = useMemo(() => {
|
||||
return dialogList.find((x) => x.id === conversationId)?.name;
|
||||
}, [conversationId, dialogList]);
|
||||
|
||||
const fetchConversation: typeof handleConversationCardClick = useCallback(
|
||||
async (conversationId, isNew) => {
|
||||
if (conversationId && !isNew) {
|
||||
const conversation = await fetchConversationManually(conversationId);
|
||||
if (!isEmpty(conversation)) {
|
||||
setCurrentConversation(conversation);
|
||||
}
|
||||
}
|
||||
},
|
||||
[fetchConversationManually],
|
||||
);
|
||||
|
||||
const handleSessionClick: typeof handleConversationCardClick = useCallback(
|
||||
(conversationId, isNew) => {
|
||||
handleConversationCardClick(conversationId, isNew);
|
||||
fetchConversation(conversationId, isNew);
|
||||
},
|
||||
[fetchConversation, handleConversationCardClick],
|
||||
);
|
||||
|
||||
useMount(() => {
|
||||
fetchConversation(conversationId, isNew === 'true');
|
||||
});
|
||||
|
||||
if (isDebugMode) {
|
||||
return (
|
||||
@ -75,6 +107,7 @@ export default function Chat() {
|
||||
removeChatBox={removeChatBox}
|
||||
addChatBox={addChatBox}
|
||||
stopOutputMessage={stopOutputMessage}
|
||||
conversation={currentConversation}
|
||||
></MultipleChatBox>
|
||||
</section>
|
||||
);
|
||||
@ -104,7 +137,7 @@ export default function Chat() {
|
||||
<div className="flex flex-1 min-h-0 pb-9">
|
||||
<Sessions
|
||||
hasSingleChatBox={hasSingleChatBox}
|
||||
handleConversationCardClick={handleConversationCardClick}
|
||||
handleConversationCardClick={handleSessionClick}
|
||||
switchSettingVisible={switchSettingVisible}
|
||||
></Sessions>
|
||||
|
||||
@ -115,16 +148,8 @@ export default function Chat() {
|
||||
className={cn('p-5', { 'border-b': hasSingleChatBox })}
|
||||
>
|
||||
<CardTitle className="flex justify-between items-center text-base">
|
||||
<div className="truncate">{conversation.name}</div>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
onClick={switchDebugMode}
|
||||
disabled={
|
||||
hasThreeChatBox ||
|
||||
isEmpty(conversationId) ||
|
||||
isNew === 'true'
|
||||
}
|
||||
>
|
||||
<div className="truncate">{currentConversationName}</div>
|
||||
<Button variant={'ghost'} onClick={switchDebugMode}>
|
||||
<ArrowUpRight /> {t('chat.multipleModels')}
|
||||
</Button>
|
||||
</CardTitle>
|
||||
@ -133,6 +158,7 @@ export default function Chat() {
|
||||
<SingleChatBox
|
||||
controller={controller}
|
||||
stopOutputMessage={stopOutputMessage}
|
||||
conversation={currentConversation}
|
||||
></SingleChatBox>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export function useAddChatBox() {
|
||||
export function useAddChatBox(isDebugMode: boolean) {
|
||||
const [ids, setIds] = useState<string[]>([uuid()]);
|
||||
|
||||
const hasSingleChatBox = ids.length === 1;
|
||||
@ -16,6 +16,12 @@ export function useAddChatBox() {
|
||||
setIds((prev) => prev.filter((x) => x !== id));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDebugMode) {
|
||||
setIds((pre) => pre.slice(0, 1));
|
||||
}
|
||||
}, [isDebugMode]);
|
||||
|
||||
return {
|
||||
chatBoxIds: ids,
|
||||
hasSingleChatBox,
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { useGetChatSearchParams } from '@/hooks/use-chat-request';
|
||||
import { trim } from 'lodash';
|
||||
import { useParams } from 'umi';
|
||||
|
||||
export const useGetSendButtonDisabled = () => {
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
const { id: dialogId } = useParams();
|
||||
|
||||
return dialogId === '' || conversationId === '';
|
||||
return dialogId === '';
|
||||
};
|
||||
|
||||
export const useSendButtonDisabled = (value: string) => {
|
||||
|
||||
97
web/src/pages/next-chats/hooks/use-chat-url.ts
Normal file
97
web/src/pages/next-chats/hooks/use-chat-url.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import { ChatSearchParams } from '@/constants/chat';
|
||||
import { useGetChatSearchParams } from '@/hooks/use-chat-request';
|
||||
import { IMessage } from '@/interfaces/database/chat';
|
||||
import { generateConversationId } from '@/utils/chat';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useSearchParams } from 'umi';
|
||||
import { useSetConversation } from './use-set-conversation';
|
||||
|
||||
/**
|
||||
* Consolidated hook for managing chat URL parameters (conversationId and isNew)
|
||||
* Replaces: useClickConversationCard from use-chat-request.ts and useSetChatRouteParams from use-set-chat-route.ts
|
||||
*/
|
||||
export const useChatUrlParams = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const setConversationId = useCallback(
|
||||
(conversationId: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[setSearchParams, newQueryParameters],
|
||||
);
|
||||
|
||||
const setIsNew = useCallback(
|
||||
(isNew: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.isNew, isNew);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[setSearchParams, newQueryParameters],
|
||||
);
|
||||
|
||||
const getIsNew = useCallback(() => {
|
||||
return newQueryParameters.get(ChatSearchParams.isNew);
|
||||
}, [newQueryParameters]);
|
||||
|
||||
const setConversationBoth = useCallback(
|
||||
(conversationId: string, isNew: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||
newQueryParameters.set(ChatSearchParams.isNew, isNew);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[setSearchParams, newQueryParameters],
|
||||
);
|
||||
|
||||
return {
|
||||
setConversationId,
|
||||
setIsNew,
|
||||
getIsNew,
|
||||
setConversationBoth,
|
||||
};
|
||||
};
|
||||
|
||||
export function useCreateConversationBeforeSendMessage() {
|
||||
const { conversationId, isNew } = useGetChatSearchParams();
|
||||
const { setConversation } = useSetConversation();
|
||||
const { setIsNew, setConversationBoth } = useChatUrlParams();
|
||||
|
||||
// Create conversation if it doesn't exist
|
||||
const createConversationBeforeSendMessage = useCallback(
|
||||
async (value: string) => {
|
||||
let currentMessages: Array<IMessage> = [];
|
||||
const currentConversationId = generateConversationId();
|
||||
if (conversationId === '' || isNew === 'true') {
|
||||
if (conversationId === '') {
|
||||
setConversationBoth(currentConversationId, 'true');
|
||||
}
|
||||
const data = await setConversation(
|
||||
value,
|
||||
true,
|
||||
conversationId || currentConversationId,
|
||||
);
|
||||
if (data.code !== 0) {
|
||||
return;
|
||||
} else {
|
||||
setIsNew('');
|
||||
currentMessages = data.data.message;
|
||||
}
|
||||
}
|
||||
|
||||
const targetConversationId = conversationId || currentConversationId;
|
||||
|
||||
return {
|
||||
targetConversationId,
|
||||
currentMessages,
|
||||
};
|
||||
},
|
||||
[conversationId, isNew, setConversation, setConversationBoth, setIsNew],
|
||||
);
|
||||
|
||||
return {
|
||||
createConversationBeforeSendMessage,
|
||||
};
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import { useClickConversationCard } from '@/hooks/use-chat-request';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useChatUrlParams } from './use-chat-url';
|
||||
|
||||
export function useHandleClickConversationCard() {
|
||||
const [controller, setController] = useState(new AbortController());
|
||||
const { handleClickConversation } = useClickConversationCard();
|
||||
const { setConversationBoth } = useChatUrlParams();
|
||||
|
||||
const stopOutputMessage = useCallback(() => {
|
||||
setController((pre) => {
|
||||
@ -14,10 +14,10 @@ export function useHandleClickConversationCard() {
|
||||
|
||||
const handleConversationCardClick = useCallback(
|
||||
(conversationId: string, isNew: boolean) => {
|
||||
handleClickConversation(conversationId, isNew ? 'true' : '');
|
||||
setConversationBoth(conversationId, isNew ? 'true' : '');
|
||||
stopOutputMessage();
|
||||
},
|
||||
[handleClickConversation, stopOutputMessage],
|
||||
[setConversationBoth, stopOutputMessage],
|
||||
);
|
||||
|
||||
return { controller, handleConversationCardClick, stopOutputMessage };
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import { useSetChatRouteParams } from './use-set-chat-route';
|
||||
import { useChatUrlParams } from './use-chat-url';
|
||||
import { useSetConversation } from './use-set-conversation';
|
||||
|
||||
export const useCreateConversationBeforeUploadDocument = () => {
|
||||
const { setConversation } = useSetConversation();
|
||||
const { id: dialogId } = useParams();
|
||||
const { getConversationIsNew } = useSetChatRouteParams();
|
||||
const { getIsNew } = useChatUrlParams();
|
||||
|
||||
const createConversationBeforeUploadDocument = useCallback(
|
||||
async (message: string) => {
|
||||
const isNew = getConversationIsNew();
|
||||
const isNew = getIsNew();
|
||||
if (isNew === 'true') {
|
||||
const data = await setConversation(message, true);
|
||||
|
||||
return data;
|
||||
}
|
||||
},
|
||||
[setConversation, getConversationIsNew],
|
||||
[setConversation, getIsNew],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { ChatSearchParams, MessageType } from '@/constants/chat';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import {
|
||||
useFetchConversationList,
|
||||
useFetchDialogList,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { IConversation } from '@/interfaces/database/chat';
|
||||
import { getConversationId } from '@/utils/chat';
|
||||
import { generateConversationId } from '@/utils/chat';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'umi';
|
||||
import { useParams } from 'umi';
|
||||
import { useChatUrlParams } from './use-chat-url';
|
||||
|
||||
export const useFindPrologueFromDialogList = () => {
|
||||
const { id: dialogId } = useParams();
|
||||
@ -20,25 +21,6 @@ export const useFindPrologueFromDialogList = () => {
|
||||
return prologue;
|
||||
};
|
||||
|
||||
export const useSetNewConversationRouteParams = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const setNewConversationRouteParams = useCallback(
|
||||
(conversationId: string, isNew: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
|
||||
newQueryParameters.set(ChatSearchParams.isNew, isNew);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[newQueryParameters, setSearchParams],
|
||||
);
|
||||
|
||||
return { setNewConversationRouteParams };
|
||||
};
|
||||
|
||||
export const useSelectDerivedConversationList = () => {
|
||||
const { t } = useTranslate('chat');
|
||||
|
||||
@ -49,15 +31,16 @@ export const useSelectDerivedConversationList = () => {
|
||||
handleInputChange,
|
||||
searchString,
|
||||
} = useFetchConversationList();
|
||||
|
||||
const { id: dialogId } = useParams();
|
||||
const { setNewConversationRouteParams } = useSetNewConversationRouteParams();
|
||||
const prologue = useFindPrologueFromDialogList();
|
||||
const { setConversationBoth } = useChatUrlParams();
|
||||
|
||||
const addTemporaryConversation = useCallback(() => {
|
||||
const conversationId = getConversationId();
|
||||
const conversationId = generateConversationId();
|
||||
setList((pre) => {
|
||||
if (dialogId) {
|
||||
setNewConversationRouteParams(conversationId, 'true');
|
||||
setConversationBoth(conversationId, 'true');
|
||||
const nextList = [
|
||||
{
|
||||
id: conversationId,
|
||||
@ -78,7 +61,7 @@ export const useSelectDerivedConversationList = () => {
|
||||
|
||||
return pre;
|
||||
});
|
||||
}, [conversationList, dialogId, prologue, t, setNewConversationRouteParams]);
|
||||
}, [dialogId, setConversationBoth, t, prologue, conversationList]);
|
||||
|
||||
const removeTemporaryConversation = useCallback((conversationId: string) => {
|
||||
setList((prevList) => {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { FileUploadProps } from '@/components/file-upload';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import {
|
||||
useHandleMessageInputChange,
|
||||
@ -6,19 +5,15 @@ import {
|
||||
useSelectDerivedMessages,
|
||||
useSendMessageWithSse,
|
||||
} from '@/hooks/logic-hooks';
|
||||
import {
|
||||
useFetchConversation,
|
||||
useGetChatSearchParams,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { IMessage, Message } from '@/interfaces/database/chat';
|
||||
import { useGetChatSearchParams } from '@/hooks/use-chat-request';
|
||||
import { IMessage } from '@/interfaces/database/chat';
|
||||
import api from '@/utils/api';
|
||||
import { trim } from 'lodash';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useCreateConversationBeforeSendMessage } from './use-chat-url';
|
||||
import { useFindPrologueFromDialogList } from './use-select-conversation-list';
|
||||
import { useSetChatRouteParams } from './use-set-chat-route';
|
||||
import { useSetConversation } from './use-set-conversation';
|
||||
import { useUploadFile } from './use-upload-file';
|
||||
|
||||
export const useSelectNextMessages = () => {
|
||||
@ -33,8 +28,7 @@ export const useSelectNextMessages = () => {
|
||||
removeMessageById,
|
||||
removeMessagesAfterCurrentMessage,
|
||||
} = useSelectDerivedMessages();
|
||||
const { data: conversation, loading } = useFetchConversation();
|
||||
const { conversationId, isNew } = useGetChatSearchParams();
|
||||
const { isNew, conversationId } = useGetChatSearchParams();
|
||||
const { id: dialogId } = useParams();
|
||||
const prologue = useFindPrologueFromDialogList();
|
||||
|
||||
@ -44,45 +38,31 @@ export const useSelectNextMessages = () => {
|
||||
role: MessageType.Assistant,
|
||||
content: prologue,
|
||||
id: uuid(),
|
||||
conversationId: conversationId,
|
||||
} as IMessage;
|
||||
|
||||
setDerivedMessages([nextMessage]);
|
||||
}
|
||||
}, [dialogId, isNew, prologue, setDerivedMessages]);
|
||||
}, [conversationId, dialogId, isNew, prologue, setDerivedMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
addPrologue();
|
||||
}, [addPrologue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
conversationId &&
|
||||
isNew !== 'true' &&
|
||||
conversation.message?.length > 0
|
||||
) {
|
||||
setDerivedMessages(conversation.message);
|
||||
}
|
||||
|
||||
if (!conversationId) {
|
||||
setDerivedMessages([]);
|
||||
}
|
||||
}, [conversation.message, conversationId, setDerivedMessages, isNew]);
|
||||
|
||||
return {
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
derivedMessages,
|
||||
loading,
|
||||
addNewestAnswer,
|
||||
addNewestQuestion,
|
||||
removeLatestMessage,
|
||||
removeMessageById,
|
||||
removeMessagesAfterCurrentMessage,
|
||||
setDerivedMessages,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSendMessage = (controller: AbortController) => {
|
||||
const { setConversation } = useSetConversation();
|
||||
const { conversationId, isNew } = useGetChatSearchParams();
|
||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||
|
||||
@ -96,31 +76,13 @@ export const useSendMessage = (controller: AbortController) => {
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
derivedMessages,
|
||||
loading,
|
||||
addNewestAnswer,
|
||||
addNewestQuestion,
|
||||
removeLatestMessage,
|
||||
removeMessageById,
|
||||
removeMessagesAfterCurrentMessage,
|
||||
setDerivedMessages,
|
||||
} = useSelectNextMessages();
|
||||
const { setConversationIsNew, getConversationIsNew } =
|
||||
useSetChatRouteParams();
|
||||
|
||||
const onUploadFile: NonNullable<FileUploadProps['onUpload']> = useCallback(
|
||||
async (files, options) => {
|
||||
const isNew = getConversationIsNew();
|
||||
|
||||
if (isNew === 'true' && Array.isArray(files) && files.length) {
|
||||
const data = await setConversation(files[0].name, true);
|
||||
if (data.code === 0) {
|
||||
handleUploadFile(files, options, data.data?.id);
|
||||
}
|
||||
} else {
|
||||
handleUploadFile(files, options);
|
||||
}
|
||||
},
|
||||
[getConversationIsNew, handleUploadFile, setConversation],
|
||||
);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async ({
|
||||
@ -128,14 +90,19 @@ export const useSendMessage = (controller: AbortController) => {
|
||||
currentConversationId,
|
||||
messages,
|
||||
}: {
|
||||
message: Message;
|
||||
message: IMessage;
|
||||
currentConversationId?: string;
|
||||
messages?: Message[];
|
||||
messages?: IMessage[];
|
||||
}) => {
|
||||
const res = await send(
|
||||
{
|
||||
conversation_id: currentConversationId ?? conversationId,
|
||||
messages: [...(messages ?? derivedMessages ?? []), message],
|
||||
messages: [
|
||||
...(Array.isArray(messages) && messages?.length > 0
|
||||
? messages
|
||||
: derivedMessages ?? []),
|
||||
message,
|
||||
],
|
||||
},
|
||||
controller,
|
||||
);
|
||||
@ -157,44 +124,62 @@ export const useSendMessage = (controller: AbortController) => {
|
||||
],
|
||||
);
|
||||
|
||||
const handleSendMessage = useCallback(
|
||||
async (message: Message) => {
|
||||
const isNew = getConversationIsNew();
|
||||
if (isNew !== 'true') {
|
||||
sendMessage({ message });
|
||||
} else {
|
||||
const data = await setConversation(
|
||||
message.content,
|
||||
true,
|
||||
conversationId,
|
||||
);
|
||||
if (data.code === 0) {
|
||||
setConversationIsNew('');
|
||||
const id = data.data.id;
|
||||
// currentConversationIdRef.current = id;
|
||||
sendMessage({
|
||||
message,
|
||||
currentConversationId: id,
|
||||
messages: data.data.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
setConversation,
|
||||
sendMessage,
|
||||
setConversationIsNew,
|
||||
getConversationIsNew,
|
||||
conversationId,
|
||||
],
|
||||
);
|
||||
|
||||
const { regenerateMessage } = useRegenerateMessage({
|
||||
removeMessagesAfterCurrentMessage,
|
||||
sendMessage,
|
||||
messages: derivedMessages,
|
||||
});
|
||||
|
||||
const { createConversationBeforeSendMessage } =
|
||||
useCreateConversationBeforeSendMessage();
|
||||
|
||||
const handlePressEnter = useCallback(async () => {
|
||||
if (trim(value) === '') return;
|
||||
|
||||
const data = await createConversationBeforeSendMessage(value);
|
||||
|
||||
if (data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { targetConversationId, currentMessages } = data;
|
||||
|
||||
const id = uuid();
|
||||
|
||||
addNewestQuestion({
|
||||
content: value,
|
||||
files: files,
|
||||
id,
|
||||
role: MessageType.User,
|
||||
conversationId: targetConversationId,
|
||||
});
|
||||
|
||||
if (done) {
|
||||
setValue('');
|
||||
sendMessage({
|
||||
currentConversationId: targetConversationId,
|
||||
messages: currentMessages,
|
||||
message: {
|
||||
id,
|
||||
content: value.trim(),
|
||||
role: MessageType.User,
|
||||
files: files,
|
||||
conversationId: targetConversationId,
|
||||
},
|
||||
});
|
||||
}
|
||||
clearFiles();
|
||||
}, [
|
||||
value,
|
||||
createConversationBeforeSendMessage,
|
||||
addNewestQuestion,
|
||||
files,
|
||||
done,
|
||||
clearFiles,
|
||||
setValue,
|
||||
sendMessage,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// #1289
|
||||
if (answer.answer && conversationId && isNew !== 'true') {
|
||||
@ -202,36 +187,6 @@ export const useSendMessage = (controller: AbortController) => {
|
||||
}
|
||||
}, [answer, addNewestAnswer, conversationId, isNew]);
|
||||
|
||||
const handlePressEnter = useCallback(() => {
|
||||
if (trim(value) === '') return;
|
||||
const id = uuid();
|
||||
|
||||
addNewestQuestion({
|
||||
content: value,
|
||||
files: files,
|
||||
id,
|
||||
role: MessageType.User,
|
||||
});
|
||||
if (done) {
|
||||
setValue('');
|
||||
handleSendMessage({
|
||||
id,
|
||||
content: value.trim(),
|
||||
role: MessageType.User,
|
||||
files: files,
|
||||
});
|
||||
}
|
||||
clearFiles();
|
||||
}, [
|
||||
value,
|
||||
addNewestQuestion,
|
||||
files,
|
||||
done,
|
||||
clearFiles,
|
||||
setValue,
|
||||
handleSendMessage,
|
||||
]);
|
||||
|
||||
return {
|
||||
handlePressEnter,
|
||||
handleInputChange,
|
||||
@ -239,13 +194,13 @@ export const useSendMessage = (controller: AbortController) => {
|
||||
setValue,
|
||||
regenerateMessage,
|
||||
sendLoading: !done,
|
||||
loading,
|
||||
scrollRef,
|
||||
messageContainerRef,
|
||||
derivedMessages,
|
||||
removeMessageById,
|
||||
handleUploadFile: onUploadFile,
|
||||
handleUploadFile,
|
||||
isUploading,
|
||||
removeFile,
|
||||
setDerivedMessages,
|
||||
};
|
||||
};
|
||||
|
||||
@ -12,6 +12,7 @@ import { trim } from 'lodash';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useBuildFormRefs } from './use-build-form-refs';
|
||||
import { useCreateConversationBeforeSendMessage } from './use-chat-url';
|
||||
import { useUploadFile } from './use-upload-file';
|
||||
|
||||
export function useSendMultipleChatMessage(
|
||||
@ -29,7 +30,11 @@ export function useSendMultipleChatMessage(
|
||||
api.completeConversation,
|
||||
);
|
||||
|
||||
const { handleUploadFile, files, clearFiles } = useUploadFile();
|
||||
const { handleUploadFile, isUploading, files, clearFiles, removeFile } =
|
||||
useUploadFile();
|
||||
|
||||
const { createConversationBeforeSendMessage } =
|
||||
useCreateConversationBeforeSendMessage();
|
||||
|
||||
const { setFormRef, getLLMConfigById, isLLMConfigEmpty } =
|
||||
useBuildFormRefs(chatBoxIds);
|
||||
@ -170,10 +175,18 @@ export function useSendMultipleChatMessage(
|
||||
],
|
||||
);
|
||||
|
||||
const handlePressEnter = useCallback(() => {
|
||||
const handlePressEnter = useCallback(async () => {
|
||||
if (trim(value) === '') return;
|
||||
const id = uuid();
|
||||
|
||||
const data = await createConversationBeforeSendMessage(value);
|
||||
|
||||
if (data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { targetConversationId, currentMessages } = data;
|
||||
|
||||
chatBoxIds.forEach((chatBoxId) => {
|
||||
if (!isLLMConfigEmpty(chatBoxId)) {
|
||||
addNewestQuestion({
|
||||
@ -182,6 +195,7 @@ export function useSendMultipleChatMessage(
|
||||
role: MessageType.User,
|
||||
chatBoxId,
|
||||
files,
|
||||
conversationId: targetConversationId,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -196,8 +210,11 @@ export function useSendMultipleChatMessage(
|
||||
content: value.trim(),
|
||||
role: MessageType.User,
|
||||
files,
|
||||
conversationId: targetConversationId,
|
||||
},
|
||||
chatBoxId,
|
||||
currentConversationId: targetConversationId,
|
||||
messages: currentMessages,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -205,6 +222,7 @@ export function useSendMultipleChatMessage(
|
||||
clearFiles();
|
||||
}, [
|
||||
value,
|
||||
createConversationBeforeSendMessage,
|
||||
chatBoxIds,
|
||||
allDone,
|
||||
clearFiles,
|
||||
@ -234,5 +252,7 @@ export function useSendMultipleChatMessage(
|
||||
sendLoading: !allDone,
|
||||
setFormRef,
|
||||
handleUploadFile,
|
||||
isUploading,
|
||||
removeFile,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
import { ChatSearchParams } from '@/constants/chat';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useSearchParams } from 'umi';
|
||||
|
||||
export const useSetChatRouteParams = () => {
|
||||
const [currentQueryParameters, setSearchParams] = useSearchParams();
|
||||
const newQueryParameters: URLSearchParams = useMemo(
|
||||
() => new URLSearchParams(currentQueryParameters.toString()),
|
||||
[currentQueryParameters],
|
||||
);
|
||||
|
||||
const setConversationIsNew = useCallback(
|
||||
(value: string) => {
|
||||
newQueryParameters.set(ChatSearchParams.isNew, value);
|
||||
setSearchParams(newQueryParameters);
|
||||
},
|
||||
[newQueryParameters, setSearchParams],
|
||||
);
|
||||
|
||||
const getConversationIsNew = useCallback(() => {
|
||||
return newQueryParameters.get(ChatSearchParams.isNew);
|
||||
}, [newQueryParameters]);
|
||||
|
||||
return { setConversationIsNew, getConversationIsNew };
|
||||
};
|
||||
@ -22,6 +22,7 @@ export const useSetConversation = () => {
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
content: message,
|
||||
conversationId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
import { FileUploadProps } from '@/components/file-upload';
|
||||
import { useUploadAndParseFile } from '@/hooks/use-chat-request';
|
||||
import {
|
||||
useGetChatSearchParams,
|
||||
useUploadAndParseFile,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { generateConversationId } from '@/utils/chat';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useChatUrlParams } from './use-chat-url';
|
||||
import { useSetConversation } from './use-set-conversation';
|
||||
|
||||
export function useUploadFile() {
|
||||
const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile();
|
||||
@ -8,6 +14,9 @@ export function useUploadFile() {
|
||||
const [fileMap, setFileMap] = useState<Map<File, Record<string, any>>>(
|
||||
new Map(),
|
||||
);
|
||||
const { setConversation } = useSetConversation();
|
||||
const { conversationId, isNew } = useGetChatSearchParams();
|
||||
const { setIsNew, setConversationBoth } = useChatUrlParams();
|
||||
|
||||
type FileUploadParameters = Parameters<
|
||||
NonNullable<FileUploadProps['onUpload']>
|
||||
@ -35,6 +44,44 @@ export function useUploadFile() {
|
||||
[uploadAndParseFile],
|
||||
);
|
||||
|
||||
const createConversationBeforeUploadFile: NonNullable<
|
||||
FileUploadProps['onUpload']
|
||||
> = useCallback(
|
||||
async (files, options) => {
|
||||
if (
|
||||
(conversationId === '' || isNew === 'true') &&
|
||||
Array.isArray(files) &&
|
||||
files.length
|
||||
) {
|
||||
const currentConversationId = generateConversationId();
|
||||
|
||||
if (conversationId === '') {
|
||||
setConversationBoth(currentConversationId, 'true');
|
||||
}
|
||||
|
||||
const data = await setConversation(
|
||||
files[0].name,
|
||||
true,
|
||||
conversationId || currentConversationId,
|
||||
);
|
||||
if (data.code === 0) {
|
||||
setIsNew('');
|
||||
handleUploadFile(files, options, data.data?.id);
|
||||
}
|
||||
} else {
|
||||
handleUploadFile(files, options);
|
||||
}
|
||||
},
|
||||
[
|
||||
conversationId,
|
||||
handleUploadFile,
|
||||
isNew,
|
||||
setConversation,
|
||||
setConversationBoth,
|
||||
setIsNew,
|
||||
],
|
||||
);
|
||||
|
||||
const clearFiles = useCallback(() => {
|
||||
setCurrentFiles([]);
|
||||
setFileMap(new Map());
|
||||
@ -55,7 +102,7 @@ export function useUploadFile() {
|
||||
);
|
||||
|
||||
return {
|
||||
handleUploadFile,
|
||||
handleUploadFile: createConversationBeforeUploadFile,
|
||||
files: currentFiles,
|
||||
isUploading: loading,
|
||||
removeFile,
|
||||
|
||||
@ -26,7 +26,7 @@ export const buildMessageListWithUuid = (messages?: Message[]) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const getConversationId = () => {
|
||||
export const generateConversationId = () => {
|
||||
return uuid().replace(/-/g, '');
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user