Spaces:
Sleeping
Sleeping
solitudeLin
commited on
Commit
•
c8210cf
1
Parent(s):
f3d2a3c
Add application file
Browse files- .gitignore +162 -0
- .pre-commit-config.yaml +46 -0
- .pylintrc +428 -0
- Dockerfile +26 -0
- LICENSE +201 -0
- README.md +122 -12
- README_zh-CN.md +119 -0
- assets/logo.svg +24 -0
- assets/mindsearch_openset.png +0 -0
- frontend/React/.gitignore +20 -0
- frontend/React/.prettierignore +7 -0
- frontend/React/.prettierrc.json +7 -0
- frontend/React/README.md +132 -0
- frontend/React/index.html +14 -0
- frontend/React/package-lock.json +0 -0
- frontend/React/package.json +49 -0
- frontend/React/src/App.module.less +54 -0
- frontend/React/src/App.tsx +23 -0
- frontend/React/src/assets/background.png +0 -0
- frontend/React/src/assets/fold-icon.svg +3 -0
- frontend/React/src/assets/logo.svg +24 -0
- frontend/React/src/assets/pack-up.svg +4 -0
- frontend/React/src/assets/sendIcon.svg +4 -0
- frontend/React/src/assets/show-right-icon.png +0 -0
- frontend/React/src/assets/unflod-icon.svg +3 -0
- frontend/React/src/components/iconfont/index.tsx +7 -0
- frontend/React/src/config/cgi.ts +2 -0
- frontend/React/src/global.d.ts +1 -0
- frontend/React/src/index.less +62 -0
- frontend/React/src/index.tsx +10 -0
- frontend/React/src/pages/render/index.module.less +848 -0
- frontend/React/src/pages/render/index.tsx +681 -0
- frontend/React/src/pages/render/mindMapItem.tsx +39 -0
- frontend/React/src/routes/routes.tsx +38 -0
- frontend/React/src/utils/tools.ts +24 -0
- frontend/React/src/vite-env.d.ts +9 -0
- frontend/React/tsconfig.json +24 -0
- frontend/React/vite.config.ts +62 -0
- frontend/React/windows-.png +0 -0
- frontend/mindsearch_gradio.py +142 -0
- frontend/mindsearch_streamlit.py +319 -0
- mindsearch/agent/__init__.py +54 -0
- mindsearch/agent/mindsearch_agent.py +408 -0
- mindsearch/agent/mindsearch_prompt.py +267 -0
- mindsearch/agent/models.py +44 -0
- mindsearch/app.py +119 -0
- mindsearch/terminal.py +50 -0
- requirements.txt +11 -0
.gitignore
ADDED
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
share/python-wheels/
|
24 |
+
*.egg-info/
|
25 |
+
.installed.cfg
|
26 |
+
*.egg
|
27 |
+
MANIFEST
|
28 |
+
|
29 |
+
# PyInstaller
|
30 |
+
# Usually these files are written by a python script from a template
|
31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32 |
+
*.manifest
|
33 |
+
*.spec
|
34 |
+
|
35 |
+
# Installer logs
|
36 |
+
pip-log.txt
|
37 |
+
pip-delete-this-directory.txt
|
38 |
+
|
39 |
+
# Unit test / coverage reports
|
40 |
+
htmlcov/
|
41 |
+
.tox/
|
42 |
+
.nox/
|
43 |
+
.coverage
|
44 |
+
.coverage.*
|
45 |
+
.cache
|
46 |
+
nosetests.xml
|
47 |
+
coverage.xml
|
48 |
+
*.cover
|
49 |
+
*.py,cover
|
50 |
+
.hypothesis/
|
51 |
+
.pytest_cache/
|
52 |
+
cover/
|
53 |
+
|
54 |
+
# Translations
|
55 |
+
*.mo
|
56 |
+
*.pot
|
57 |
+
|
58 |
+
# Django stuff:
|
59 |
+
*.log
|
60 |
+
local_settings.py
|
61 |
+
db.sqlite3
|
62 |
+
db.sqlite3-journal
|
63 |
+
|
64 |
+
# Flask stuff:
|
65 |
+
instance/
|
66 |
+
.webassets-cache
|
67 |
+
|
68 |
+
# Scrapy stuff:
|
69 |
+
.scrapy
|
70 |
+
|
71 |
+
# Sphinx documentation
|
72 |
+
docs/_build/
|
73 |
+
|
74 |
+
# PyBuilder
|
75 |
+
.pybuilder/
|
76 |
+
target/
|
77 |
+
|
78 |
+
# Jupyter Notebook
|
79 |
+
.ipynb_checkpoints
|
80 |
+
|
81 |
+
# IPython
|
82 |
+
profile_default/
|
83 |
+
ipython_config.py
|
84 |
+
|
85 |
+
# pyenv
|
86 |
+
# For a library or package, you might want to ignore these files since the code is
|
87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
88 |
+
# .python-version
|
89 |
+
|
90 |
+
# pipenv
|
91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94 |
+
# install all needed dependencies.
|
95 |
+
#Pipfile.lock
|
96 |
+
|
97 |
+
# poetry
|
98 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100 |
+
# commonly ignored for libraries.
|
101 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
102 |
+
#poetry.lock
|
103 |
+
|
104 |
+
# pdm
|
105 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
106 |
+
#pdm.lock
|
107 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
108 |
+
# in version control.
|
109 |
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
110 |
+
.pdm.toml
|
111 |
+
.pdm-python
|
112 |
+
.pdm-build/
|
113 |
+
|
114 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
115 |
+
__pypackages__/
|
116 |
+
|
117 |
+
# Celery stuff
|
118 |
+
celerybeat-schedule
|
119 |
+
celerybeat.pid
|
120 |
+
|
121 |
+
# SageMath parsed files
|
122 |
+
*.sage.py
|
123 |
+
|
124 |
+
# Environments
|
125 |
+
.env
|
126 |
+
.venv
|
127 |
+
env/
|
128 |
+
venv/
|
129 |
+
ENV/
|
130 |
+
env.bak/
|
131 |
+
venv.bak/
|
132 |
+
|
133 |
+
# Spyder project settings
|
134 |
+
.spyderproject
|
135 |
+
.spyproject
|
136 |
+
|
137 |
+
# Rope project settings
|
138 |
+
.ropeproject
|
139 |
+
|
140 |
+
# mkdocs documentation
|
141 |
+
/site
|
142 |
+
|
143 |
+
# mypy
|
144 |
+
.mypy_cache/
|
145 |
+
.dmypy.json
|
146 |
+
dmypy.json
|
147 |
+
|
148 |
+
# Pyre type checker
|
149 |
+
.pyre/
|
150 |
+
|
151 |
+
# pytype static type analyzer
|
152 |
+
.pytype/
|
153 |
+
|
154 |
+
# Cython debug symbols
|
155 |
+
cython_debug/
|
156 |
+
|
157 |
+
# PyCharm
|
158 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
159 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
160 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
161 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
162 |
+
#.idea/
|
.pre-commit-config.yaml
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
exclude: ^(tests/data|scripts|frontend/React)/
|
2 |
+
repos:
|
3 |
+
- repo: https://github.com/PyCQA/flake8
|
4 |
+
rev: 7.0.0
|
5 |
+
hooks:
|
6 |
+
- id: flake8
|
7 |
+
args: ["--max-line-length=120"]
|
8 |
+
- repo: https://github.com/PyCQA/isort
|
9 |
+
rev: 5.13.2
|
10 |
+
hooks:
|
11 |
+
- id: isort
|
12 |
+
- repo: https://github.com/pre-commit/mirrors-yapf
|
13 |
+
rev: v0.32.0
|
14 |
+
hooks:
|
15 |
+
- id: yapf
|
16 |
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
17 |
+
rev: v4.5.0
|
18 |
+
hooks:
|
19 |
+
- id: trailing-whitespace
|
20 |
+
- id: check-yaml
|
21 |
+
- id: end-of-file-fixer
|
22 |
+
- id: requirements-txt-fixer
|
23 |
+
- id: double-quote-string-fixer
|
24 |
+
- id: check-merge-conflict
|
25 |
+
- id: fix-encoding-pragma
|
26 |
+
args: ["--remove"]
|
27 |
+
- id: mixed-line-ending
|
28 |
+
args: ["--fix=lf"]
|
29 |
+
- repo: https://github.com/executablebooks/mdformat
|
30 |
+
rev: 0.7.17
|
31 |
+
hooks:
|
32 |
+
- id: mdformat
|
33 |
+
args: ["--number"]
|
34 |
+
additional_dependencies:
|
35 |
+
- mdformat-openmmlab
|
36 |
+
- mdformat_frontmatter
|
37 |
+
- linkify-it-py
|
38 |
+
- repo: https://github.com/codespell-project/codespell
|
39 |
+
rev: v2.2.6
|
40 |
+
hooks:
|
41 |
+
- id: codespell
|
42 |
+
- repo: https://github.com/asottile/pyupgrade
|
43 |
+
rev: v3.15.0
|
44 |
+
hooks:
|
45 |
+
- id: pyupgrade
|
46 |
+
args: ["--py36-plus"]
|
.pylintrc
ADDED
@@ -0,0 +1,428 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# This Pylint rcfile contains a best-effort configuration to uphold the
|
2 |
+
# best-practices and style described in the Google Python style guide:
|
3 |
+
# https://google.github.io/styleguide/pyguide.html
|
4 |
+
#
|
5 |
+
# Its canonical open-source location is:
|
6 |
+
# https://google.github.io/styleguide/pylintrc
|
7 |
+
|
8 |
+
[MASTER]
|
9 |
+
|
10 |
+
# Files or directories to be skipped. They should be base names, not paths.
|
11 |
+
ignore=third_party,storage
|
12 |
+
|
13 |
+
# Files or directories matching the regex patterns are skipped. The regex
|
14 |
+
# matches against base names, not paths.
|
15 |
+
ignore-patterns=
|
16 |
+
|
17 |
+
# Pickle collected data for later comparisons.
|
18 |
+
persistent=no
|
19 |
+
|
20 |
+
# List of plugins (as comma separated values of python modules names) to load,
|
21 |
+
# usually to register additional checkers.
|
22 |
+
load-plugins=
|
23 |
+
|
24 |
+
# Use multiple processes to speed up Pylint.
|
25 |
+
jobs=4
|
26 |
+
|
27 |
+
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
28 |
+
# active Python interpreter and may run arbitrary code.
|
29 |
+
unsafe-load-any-extension=no
|
30 |
+
|
31 |
+
|
32 |
+
[MESSAGES CONTROL]
|
33 |
+
|
34 |
+
# Only show warnings with the listed confidence levels. Leave empty to show
|
35 |
+
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
36 |
+
confidence=
|
37 |
+
|
38 |
+
# Enable the message, report, category or checker with the given id(s). You can
|
39 |
+
# either give multiple identifier separated by comma (,) or put this option
|
40 |
+
# multiple time (only on the command line, not in the configuration file where
|
41 |
+
# it should appear only once). See also the "--disable" option for examples.
|
42 |
+
#enable=
|
43 |
+
|
44 |
+
# Disable the message, report, category or checker with the given id(s). You
|
45 |
+
# can either give multiple identifiers separated by comma (,) or put this
|
46 |
+
# option multiple times (only on the command line, not in the configuration
|
47 |
+
# file where it should appear only once).You can also use "--disable=all" to
|
48 |
+
# disable everything first and then reenable specific checks. For example, if
|
49 |
+
# you want to run only the similarities checker, you can use "--disable=all
|
50 |
+
# --enable=similarities". If you want to run only the classes checker, but have
|
51 |
+
# no Warning level messages displayed, use"--disable=all --enable=classes
|
52 |
+
# --disable=W"
|
53 |
+
disable=abstract-method,
|
54 |
+
apply-builtin,
|
55 |
+
arguments-differ,
|
56 |
+
attribute-defined-outside-init,
|
57 |
+
backtick,
|
58 |
+
bad-option-value,
|
59 |
+
basestring-builtin,
|
60 |
+
buffer-builtin,
|
61 |
+
c-extension-no-member,
|
62 |
+
consider-using-enumerate,
|
63 |
+
cmp-builtin,
|
64 |
+
cmp-method,
|
65 |
+
coerce-builtin,
|
66 |
+
coerce-method,
|
67 |
+
delslice-method,
|
68 |
+
div-method,
|
69 |
+
duplicate-code,
|
70 |
+
eq-without-hash,
|
71 |
+
execfile-builtin,
|
72 |
+
file-builtin,
|
73 |
+
filter-builtin-not-iterating,
|
74 |
+
fixme,
|
75 |
+
getslice-method,
|
76 |
+
global-statement,
|
77 |
+
hex-method,
|
78 |
+
idiv-method,
|
79 |
+
implicit-str-concat,
|
80 |
+
import-error,
|
81 |
+
import-self,
|
82 |
+
import-star-module-level,
|
83 |
+
inconsistent-return-statements,
|
84 |
+
input-builtin,
|
85 |
+
intern-builtin,
|
86 |
+
invalid-str-codec,
|
87 |
+
locally-disabled,
|
88 |
+
long-builtin,
|
89 |
+
long-suffix,
|
90 |
+
map-builtin-not-iterating,
|
91 |
+
misplaced-comparison-constant,
|
92 |
+
missing-function-docstring,
|
93 |
+
metaclass-assignment,
|
94 |
+
next-method-called,
|
95 |
+
next-method-defined,
|
96 |
+
no-absolute-import,
|
97 |
+
no-else-break,
|
98 |
+
no-else-continue,
|
99 |
+
no-else-raise,
|
100 |
+
no-else-return,
|
101 |
+
no-init, # added
|
102 |
+
no-member,
|
103 |
+
no-name-in-module,
|
104 |
+
no-self-use,
|
105 |
+
nonzero-method,
|
106 |
+
oct-method,
|
107 |
+
old-division,
|
108 |
+
old-ne-operator,
|
109 |
+
old-octal-literal,
|
110 |
+
old-raise-syntax,
|
111 |
+
parameter-unpacking,
|
112 |
+
print-statement,
|
113 |
+
raising-string,
|
114 |
+
range-builtin-not-iterating,
|
115 |
+
raw_input-builtin,
|
116 |
+
rdiv-method,
|
117 |
+
reduce-builtin,
|
118 |
+
relative-import,
|
119 |
+
reload-builtin,
|
120 |
+
round-builtin,
|
121 |
+
setslice-method,
|
122 |
+
signature-differs,
|
123 |
+
standarderror-builtin,
|
124 |
+
suppressed-message,
|
125 |
+
sys-max-int,
|
126 |
+
too-few-public-methods,
|
127 |
+
too-many-ancestors,
|
128 |
+
too-many-arguments,
|
129 |
+
too-many-boolean-expressions,
|
130 |
+
too-many-branches,
|
131 |
+
too-many-instance-attributes,
|
132 |
+
too-many-locals,
|
133 |
+
too-many-nested-blocks,
|
134 |
+
too-many-public-methods,
|
135 |
+
too-many-return-statements,
|
136 |
+
too-many-statements,
|
137 |
+
trailing-newlines,
|
138 |
+
unichr-builtin,
|
139 |
+
unicode-builtin,
|
140 |
+
unnecessary-pass,
|
141 |
+
unpacking-in-except,
|
142 |
+
useless-else-on-loop,
|
143 |
+
useless-object-inheritance,
|
144 |
+
useless-suppression,
|
145 |
+
using-cmp-argument,
|
146 |
+
wrong-import-order,
|
147 |
+
xrange-builtin,
|
148 |
+
zip-builtin-not-iterating,
|
149 |
+
|
150 |
+
|
151 |
+
[REPORTS]
|
152 |
+
|
153 |
+
# Set the output format. Available formats are text, parseable, colorized, msvs
|
154 |
+
# (visual studio) and html. You can also give a reporter class, eg
|
155 |
+
# mypackage.mymodule.MyReporterClass.
|
156 |
+
output-format=colorized
|
157 |
+
|
158 |
+
# Tells whether to display a full report or only the messages
|
159 |
+
reports=no
|
160 |
+
|
161 |
+
# Python expression which should return a note less than 10 (10 is the highest
|
162 |
+
# note). You have access to the variables errors warning, statement which
|
163 |
+
# respectively contain the number of errors / warnings messages and the total
|
164 |
+
# number of statements analyzed. This is used by the global evaluation report
|
165 |
+
# (RP0004).
|
166 |
+
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
167 |
+
|
168 |
+
# Template used to display messages. This is a python new-style format string
|
169 |
+
# used to format the message information. See doc for all details
|
170 |
+
#msg-template=
|
171 |
+
|
172 |
+
|
173 |
+
[BASIC]
|
174 |
+
|
175 |
+
# Good variable names which should always be accepted, separated by a comma
|
176 |
+
good-names=main,_
|
177 |
+
|
178 |
+
# Bad variable names which should always be refused, separated by a comma
|
179 |
+
bad-names=
|
180 |
+
|
181 |
+
# Colon-delimited sets of names that determine each other's naming style when
|
182 |
+
# the name regexes allow several styles.
|
183 |
+
name-group=
|
184 |
+
|
185 |
+
# Include a hint for the correct naming format with invalid-name
|
186 |
+
include-naming-hint=no
|
187 |
+
|
188 |
+
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
189 |
+
# to this list to register other decorators that produce valid properties.
|
190 |
+
property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
|
191 |
+
|
192 |
+
# Regular expression matching correct function names
|
193 |
+
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
|
194 |
+
|
195 |
+
# Regular expression matching correct variable names
|
196 |
+
variable-rgx=^[a-z][a-z0-9_]*$
|
197 |
+
|
198 |
+
# Regular expression matching correct constant names
|
199 |
+
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
|
200 |
+
|
201 |
+
# Regular expression matching correct attribute names
|
202 |
+
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
|
203 |
+
|
204 |
+
# Regular expression matching correct argument names
|
205 |
+
argument-rgx=^[a-z][a-z0-9_]*$
|
206 |
+
|
207 |
+
# Regular expression matching correct class attribute names
|
208 |
+
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
|
209 |
+
|
210 |
+
# Regular expression matching correct inline iteration names
|
211 |
+
inlinevar-rgx=^[a-z][a-z0-9_]*$
|
212 |
+
|
213 |
+
# Regular expression matching correct class names
|
214 |
+
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
|
215 |
+
|
216 |
+
# Regular expression matching correct module names
|
217 |
+
module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
|
218 |
+
|
219 |
+
# Regular expression matching correct method names
|
220 |
+
method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
|
221 |
+
|
222 |
+
# Regular expression which should only match function or class names that do
|
223 |
+
# not require a docstring.
|
224 |
+
no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
|
225 |
+
|
226 |
+
# Minimum line length for functions/classes that require docstrings, shorter
|
227 |
+
# ones are exempt.
|
228 |
+
docstring-min-length=10
|
229 |
+
|
230 |
+
|
231 |
+
[TYPECHECK]
|
232 |
+
|
233 |
+
# List of decorators that produce context managers, such as
|
234 |
+
# contextlib.contextmanager. Add to this list to register other decorators that
|
235 |
+
# produce valid context managers.
|
236 |
+
contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
|
237 |
+
|
238 |
+
# Tells whether missing members accessed in mixin class should be ignored. A
|
239 |
+
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
240 |
+
ignore-mixin-members=yes
|
241 |
+
|
242 |
+
# List of module names for which member attributes should not be checked
|
243 |
+
# (useful for modules/projects where namespaces are manipulated during runtime
|
244 |
+
# and thus existing member attributes cannot be deduced by static analysis. It
|
245 |
+
# supports qualified module names, as well as Unix pattern matching.
|
246 |
+
ignored-modules=
|
247 |
+
|
248 |
+
# List of class names for which member attributes should not be checked (useful
|
249 |
+
# for classes with dynamically set attributes). This supports the use of
|
250 |
+
# qualified names.
|
251 |
+
ignored-classes=optparse.Values,thread._local,_thread._local
|
252 |
+
|
253 |
+
# List of members which are set dynamically and missed by pylint inference
|
254 |
+
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
255 |
+
# expressions are accepted.
|
256 |
+
generated-members=
|
257 |
+
|
258 |
+
|
259 |
+
[FORMAT]
|
260 |
+
|
261 |
+
# Maximum number of characters on a single line.
|
262 |
+
max-line-length=120
|
263 |
+
|
264 |
+
# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
|
265 |
+
# lines made too long by directives to pytype.
|
266 |
+
|
267 |
+
# Regexp for a line that is allowed to be longer than the limit.
|
268 |
+
ignore-long-lines=(?x)(
|
269 |
+
^\s*(\#\ )?<?https?://\S+>?$|
|
270 |
+
^\s*(from\s+\S+\s+)?import\s+.+$)
|
271 |
+
|
272 |
+
# Allow the body of an if to be on the same line as the test if there is no
|
273 |
+
# else.
|
274 |
+
single-line-if-stmt=yes
|
275 |
+
|
276 |
+
# Maximum number of lines in a module
|
277 |
+
max-module-lines=99999
|
278 |
+
|
279 |
+
# String used as indentation unit. The internal Google style guide mandates 2
|
280 |
+
# spaces. Google's externaly-published style guide says 4, consistent with
|
281 |
+
# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
|
282 |
+
# projects (like TensorFlow).
|
283 |
+
indent-string=' '
|
284 |
+
|
285 |
+
# Number of spaces of indent required inside a hanging or continued line.
|
286 |
+
indent-after-paren=4
|
287 |
+
|
288 |
+
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
289 |
+
expected-line-ending-format=
|
290 |
+
|
291 |
+
|
292 |
+
[MISCELLANEOUS]
|
293 |
+
|
294 |
+
# List of note tags to take in consideration, separated by a comma.
|
295 |
+
notes=TODO
|
296 |
+
|
297 |
+
|
298 |
+
[STRING]
|
299 |
+
|
300 |
+
# This flag controls whether inconsistent-quotes generates a warning when the
|
301 |
+
# character used as a quote delimiter is used inconsistently within a module.
|
302 |
+
check-quote-consistency=yes
|
303 |
+
|
304 |
+
|
305 |
+
[VARIABLES]
|
306 |
+
|
307 |
+
# Tells whether we should check for unused import in __init__ files.
|
308 |
+
init-import=no
|
309 |
+
|
310 |
+
# A regular expression matching the name of dummy variables (i.e. expectedly
|
311 |
+
# not used).
|
312 |
+
dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
|
313 |
+
|
314 |
+
# List of additional names supposed to be defined in builtins. Remember that
|
315 |
+
# you should avoid to define new builtins when possible.
|
316 |
+
additional-builtins=
|
317 |
+
|
318 |
+
# List of strings which can identify a callback function by name. A callback
|
319 |
+
# name must start or end with one of those strings.
|
320 |
+
callbacks=cb_,_cb
|
321 |
+
|
322 |
+
# List of qualified module names which can have objects that can redefine
|
323 |
+
# builtins.
|
324 |
+
redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
|
325 |
+
|
326 |
+
|
327 |
+
[LOGGING]
|
328 |
+
|
329 |
+
# Logging modules to check that the string format arguments are in logging
|
330 |
+
# function parameter format
|
331 |
+
logging-modules=logging,absl.logging,tensorflow.io.logging
|
332 |
+
|
333 |
+
|
334 |
+
[SIMILARITIES]
|
335 |
+
|
336 |
+
# Minimum lines number of a similarity.
|
337 |
+
min-similarity-lines=4
|
338 |
+
|
339 |
+
# Ignore comments when computing similarities.
|
340 |
+
ignore-comments=yes
|
341 |
+
|
342 |
+
# Ignore docstrings when computing similarities.
|
343 |
+
ignore-docstrings=yes
|
344 |
+
|
345 |
+
# Ignore imports when computing similarities.
|
346 |
+
ignore-imports=no
|
347 |
+
|
348 |
+
|
349 |
+
[SPELLING]
|
350 |
+
|
351 |
+
# Spelling dictionary name. Available dictionaries: none. To make it working
|
352 |
+
# install python-enchant package.
|
353 |
+
spelling-dict=
|
354 |
+
|
355 |
+
# List of comma separated words that should not be checked.
|
356 |
+
spelling-ignore-words=
|
357 |
+
|
358 |
+
# A path to a file that contains private dictionary; one word per line.
|
359 |
+
spelling-private-dict-file=
|
360 |
+
|
361 |
+
# Tells whether to store unknown words to indicated private dictionary in
|
362 |
+
# --spelling-private-dict-file option instead of raising a message.
|
363 |
+
spelling-store-unknown-words=no
|
364 |
+
|
365 |
+
|
366 |
+
[IMPORTS]
|
367 |
+
|
368 |
+
# Deprecated modules which should not be used, separated by a comma
|
369 |
+
deprecated-modules=regsub,
|
370 |
+
TERMIOS,
|
371 |
+
Bastion,
|
372 |
+
rexec,
|
373 |
+
sets
|
374 |
+
|
375 |
+
# Create a graph of every (i.e. internal and external) dependencies in the
|
376 |
+
# given file (report RP0402 must not be disabled)
|
377 |
+
import-graph=
|
378 |
+
|
379 |
+
# Create a graph of external dependencies in the given file (report RP0402 must
|
380 |
+
# not be disabled)
|
381 |
+
ext-import-graph=
|
382 |
+
|
383 |
+
# Create a graph of internal dependencies in the given file (report RP0402 must
|
384 |
+
# not be disabled)
|
385 |
+
int-import-graph=
|
386 |
+
|
387 |
+
# Force import order to recognize a module as part of the standard
|
388 |
+
# compatibility libraries.
|
389 |
+
known-standard-library=
|
390 |
+
|
391 |
+
# Force import order to recognize a module as part of a third party library.
|
392 |
+
known-third-party=enchant, absl
|
393 |
+
|
394 |
+
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
395 |
+
# 3 compatible code, which means that the block might have code that exists
|
396 |
+
# only in one or another interpreter, leading to false positives when analysed.
|
397 |
+
analyse-fallback-blocks=no
|
398 |
+
|
399 |
+
|
400 |
+
[CLASSES]
|
401 |
+
|
402 |
+
# List of method names used to declare (i.e. assign) instance attributes.
|
403 |
+
defining-attr-methods=__init__,
|
404 |
+
__new__,
|
405 |
+
setUp
|
406 |
+
|
407 |
+
# List of member names, which should be excluded from the protected access
|
408 |
+
# warning.
|
409 |
+
exclude-protected=_asdict,
|
410 |
+
_fields,
|
411 |
+
_replace,
|
412 |
+
_source,
|
413 |
+
_make
|
414 |
+
|
415 |
+
# List of valid names for the first argument in a class method.
|
416 |
+
valid-classmethod-first-arg=cls,
|
417 |
+
class_
|
418 |
+
|
419 |
+
# List of valid names for the first argument in a metaclass class method.
|
420 |
+
valid-metaclass-classmethod-first-arg=mcs
|
421 |
+
|
422 |
+
|
423 |
+
[EXCEPTIONS]
|
424 |
+
|
425 |
+
# Exceptions that will emit a warning when being caught. Defaults to
|
426 |
+
# "Exception"
|
427 |
+
overgeneral-exceptions=builtins.BaseException,
|
428 |
+
builtins.Exception
|
Dockerfile
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM continuumio/miniconda3
|
2 |
+
|
3 |
+
ARG OPENAI_API_KEY
|
4 |
+
ENV OPENAI_API_KEY=${OPENAI_API_KEY}
|
5 |
+
|
6 |
+
ARG BING_API_KEY
|
7 |
+
ENV BING_API_KEY=${BING_API_KEY}
|
8 |
+
|
9 |
+
# 设置环境变量
|
10 |
+
ENV PATH=/opt/conda/bin:$PATH
|
11 |
+
|
12 |
+
# 克隆git仓库
|
13 |
+
RUN git clone https://github.com/InternLM/MindSearch.git /app
|
14 |
+
|
15 |
+
WORKDIR /app
|
16 |
+
|
17 |
+
# 创建并激活 fastapi 环境,并安装依赖包
|
18 |
+
RUN conda create --name fastapi python=3.10 -y && \
|
19 |
+
conda run -n fastapi pip install -r requirements.txt && \
|
20 |
+
conda clean --all -f -y
|
21 |
+
|
22 |
+
# 暴露 FastAPI 默认端口
|
23 |
+
EXPOSE 8000
|
24 |
+
|
25 |
+
# 启动 FastAPI 服务
|
26 |
+
CMD ["conda", "run", "--no-capture-output", "-n", "fastapi", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
LICENSE
ADDED
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Apache License
|
2 |
+
Version 2.0, January 2004
|
3 |
+
http://www.apache.org/licenses/
|
4 |
+
|
5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
6 |
+
|
7 |
+
1. Definitions.
|
8 |
+
|
9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
11 |
+
|
12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
13 |
+
the copyright owner that is granting the License.
|
14 |
+
|
15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
16 |
+
other entities that control, are controlled by, or are under common
|
17 |
+
control with that entity. For the purposes of this definition,
|
18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
19 |
+
direction or management of such entity, whether by contract or
|
20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
22 |
+
|
23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
24 |
+
exercising permissions granted by this License.
|
25 |
+
|
26 |
+
"Source" form shall mean the preferred form for making modifications,
|
27 |
+
including but not limited to software source code, documentation
|
28 |
+
source, and configuration files.
|
29 |
+
|
30 |
+
"Object" form shall mean any form resulting from mechanical
|
31 |
+
transformation or translation of a Source form, including but
|
32 |
+
not limited to compiled object code, generated documentation,
|
33 |
+
and conversions to other media types.
|
34 |
+
|
35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
36 |
+
Object form, made available under the License, as indicated by a
|
37 |
+
copyright notice that is included in or attached to the work
|
38 |
+
(an example is provided in the Appendix below).
|
39 |
+
|
40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
41 |
+
form, that is based on (or derived from) the Work and for which the
|
42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
44 |
+
of this License, Derivative Works shall not include works that remain
|
45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
46 |
+
the Work and Derivative Works thereof.
|
47 |
+
|
48 |
+
"Contribution" shall mean any work of authorship, including
|
49 |
+
the original version of the Work and any modifications or additions
|
50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
54 |
+
means any form of electronic, verbal, or written communication sent
|
55 |
+
to the Licensor or its representatives, including but not limited to
|
56 |
+
communication on electronic mailing lists, source code control systems,
|
57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
59 |
+
excluding communication that is conspicuously marked or otherwise
|
60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
61 |
+
|
62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
64 |
+
subsequently incorporated within the Work.
|
65 |
+
|
66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
71 |
+
Work and such Derivative Works in Source or Object form.
|
72 |
+
|
73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
76 |
+
(except as stated in this section) patent license to make, have made,
|
77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
78 |
+
where such license applies only to those patent claims licensable
|
79 |
+
by such Contributor that are necessarily infringed by their
|
80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
82 |
+
institute patent litigation against any entity (including a
|
83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
84 |
+
or a Contribution incorporated within the Work constitutes direct
|
85 |
+
or contributory patent infringement, then any patent licenses
|
86 |
+
granted to You under this License for that Work shall terminate
|
87 |
+
as of the date such litigation is filed.
|
88 |
+
|
89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
90 |
+
Work or Derivative Works thereof in any medium, with or without
|
91 |
+
modifications, and in Source or Object form, provided that You
|
92 |
+
meet the following conditions:
|
93 |
+
|
94 |
+
(a) You must give any other recipients of the Work or
|
95 |
+
Derivative Works a copy of this License; and
|
96 |
+
|
97 |
+
(b) You must cause any modified files to carry prominent notices
|
98 |
+
stating that You changed the files; and
|
99 |
+
|
100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
101 |
+
that You distribute, all copyright, patent, trademark, and
|
102 |
+
attribution notices from the Source form of the Work,
|
103 |
+
excluding those notices that do not pertain to any part of
|
104 |
+
the Derivative Works; and
|
105 |
+
|
106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
107 |
+
distribution, then any Derivative Works that You distribute must
|
108 |
+
include a readable copy of the attribution notices contained
|
109 |
+
within such NOTICE file, excluding those notices that do not
|
110 |
+
pertain to any part of the Derivative Works, in at least one
|
111 |
+
of the following places: within a NOTICE text file distributed
|
112 |
+
as part of the Derivative Works; within the Source form or
|
113 |
+
documentation, if provided along with the Derivative Works; or,
|
114 |
+
within a display generated by the Derivative Works, if and
|
115 |
+
wherever such third-party notices normally appear. The contents
|
116 |
+
of the NOTICE file are for informational purposes only and
|
117 |
+
do not modify the License. You may add Your own attribution
|
118 |
+
notices within Derivative Works that You distribute, alongside
|
119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
120 |
+
that such additional attribution notices cannot be construed
|
121 |
+
as modifying the License.
|
122 |
+
|
123 |
+
You may add Your own copyright statement to Your modifications and
|
124 |
+
may provide additional or different license terms and conditions
|
125 |
+
for use, reproduction, or distribution of Your modifications, or
|
126 |
+
for any such Derivative Works as a whole, provided Your use,
|
127 |
+
reproduction, and distribution of the Work otherwise complies with
|
128 |
+
the conditions stated in this License.
|
129 |
+
|
130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
132 |
+
by You to the Licensor shall be under the terms and conditions of
|
133 |
+
this License, without any additional terms or conditions.
|
134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
135 |
+
the terms of any separate license agreement you may have executed
|
136 |
+
with Licensor regarding such Contributions.
|
137 |
+
|
138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
140 |
+
except as required for reasonable and customary use in describing the
|
141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
142 |
+
|
143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
144 |
+
agreed to in writing, Licensor provides the Work (and each
|
145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
147 |
+
implied, including, without limitation, any warranties or conditions
|
148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
150 |
+
appropriateness of using or redistributing the Work and assume any
|
151 |
+
risks associated with Your exercise of permissions under this License.
|
152 |
+
|
153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
154 |
+
whether in tort (including negligence), contract, or otherwise,
|
155 |
+
unless required by applicable law (such as deliberate and grossly
|
156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
157 |
+
liable to You for damages, including any direct, indirect, special,
|
158 |
+
incidental, or consequential damages of any character arising as a
|
159 |
+
result of this License or out of the use or inability to use the
|
160 |
+
Work (including but not limited to damages for loss of goodwill,
|
161 |
+
work stoppage, computer failure or malfunction, or any and all
|
162 |
+
other commercial damages or losses), even if such Contributor
|
163 |
+
has been advised of the possibility of such damages.
|
164 |
+
|
165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
168 |
+
or other liability obligations and/or rights consistent with this
|
169 |
+
License. However, in accepting such obligations, You may act only
|
170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
171 |
+
of any other Contributor, and only if You agree to indemnify,
|
172 |
+
defend, and hold each Contributor harmless for any liability
|
173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
174 |
+
of your accepting any such warranty or additional liability.
|
175 |
+
|
176 |
+
END OF TERMS AND CONDITIONS
|
177 |
+
|
178 |
+
APPENDIX: How to apply the Apache License to your work.
|
179 |
+
|
180 |
+
To apply the Apache License to your work, attach the following
|
181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
182 |
+
replaced with your own identifying information. (Don't include
|
183 |
+
the brackets!) The text should be enclosed in the appropriate
|
184 |
+
comment syntax for the file format. We also recommend that a
|
185 |
+
file or class name and description of purpose be included on the
|
186 |
+
same "printed page" as the copyright notice for easier
|
187 |
+
identification within third-party archives.
|
188 |
+
|
189 |
+
Copyright [yyyy] [name of copyright owner]
|
190 |
+
|
191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
192 |
+
you may not use this file except in compliance with the License.
|
193 |
+
You may obtain a copy of the License at
|
194 |
+
|
195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
196 |
+
|
197 |
+
Unless required by applicable law or agreed to in writing, software
|
198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200 |
+
See the License for the specific language governing permissions and
|
201 |
+
limitations under the License.
|
README.md
CHANGED
@@ -1,12 +1,122 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div id="top"></div>
|
2 |
+
|
3 |
+
<div align="center">
|
4 |
+
|
5 |
+
<img src="assets/logo.svg" style="width: 50%; height: auto;">
|
6 |
+
|
7 |
+
[🌐 Project Page](https://mindsearch.netlify.app/) | [📃 Paper](https://arxiv.org/abs/2407.20183) | [💻 Playground](https://mindsearch.openxlab.org.cn/)
|
8 |
+
|
9 |
+
English | [简体中文](README_zh-CN.md)
|
10 |
+
|
11 |
+
<https://github.com/user-attachments/assets/44ffe4b9-be26-4b93-a77b-02fed16e33fe>
|
12 |
+
|
13 |
+
</div>
|
14 |
+
</p>
|
15 |
+
|
16 |
+
## ✨ MindSearch: Mimicking Human Minds Elicits Deep AI Searcher
|
17 |
+
|
18 |
+
MindSearch is an open-source AI Search Engine Framework with Perplexity.ai Pro performance. You can simply deploy it with your own perplexity.ai style search engine with either close-source LLMs (GPT, Claude) or open-source LLMs (InternLM2.5-7b-chat). It owns following features:
|
19 |
+
|
20 |
+
- 🤔 **Ask everything you want to know**: MindSearch is designed to solve any question in your life and use web knowledge.
|
21 |
+
- 📚 **In-depth Knowledge Discovery**: MindSearch browses hundreds of web pages to answer your question, providing deeper and wider knowledge base answer.
|
22 |
+
- 🔍 **Detailed Solution Path**: MindSearch exposes all details, allowing users to check everything they want. This greatly improves the credibility of its final response as well as usability.
|
23 |
+
- 💻 **Optimized UI Experimence**: Providing all kinds of interfaces for users, including React, Gradio, Streamlit and Terminal. Choose any type based on your need.
|
24 |
+
- 🧠 **Dynamic Graph Construction Process**: MindSearch decomposes the user query into atomic sub-questions as nodes in the graph and progressively extends the graph based on the search result from WebSearcher.
|
25 |
+
|
26 |
+
<div align="center">
|
27 |
+
|
28 |
+
<img src="assets/teaser.gif">
|
29 |
+
|
30 |
+
</div>
|
31 |
+
|
32 |
+
## ⚡️ MindSearch vs other AI Search Engines
|
33 |
+
|
34 |
+
Comparison on human preference based on depth, breadth, factuality of the response generated by ChatGPT-Web, Perplexity.ai (Pro), and MindSearch. Results are obtained on 100 human-crafted real-world questions and evaluated by 5 human experts\*.
|
35 |
+
|
36 |
+
<div align="center">
|
37 |
+
<img src="assets/mindsearch_openset.png" width="90%">
|
38 |
+
</div>
|
39 |
+
* All experiments are done before July.7 2024.
|
40 |
+
|
41 |
+
## ⚽️ Build Your Own MindSearch
|
42 |
+
|
43 |
+
### Step1: Dependencies Installation
|
44 |
+
|
45 |
+
```bash
|
46 |
+
pip install -r requirements.txt
|
47 |
+
```
|
48 |
+
|
49 |
+
### Step2: Setup MindSearch API
|
50 |
+
|
51 |
+
Setup FastAPI Server.
|
52 |
+
|
53 |
+
```bash
|
54 |
+
python -m mindsearch.app --lang en --model_format internlm_server
|
55 |
+
```
|
56 |
+
|
57 |
+
- `--lang`: language of the model, `en` for English and `zh` for Chinese.
|
58 |
+
- `--model_format`: format of the model.
|
59 |
+
- `internlm_server` for InternLM2.5-7b-chat with local server. (InternLM2.5-7b-chat has been better optimized for Chinese.)
|
60 |
+
- `gpt4` for GPT4.
|
61 |
+
if you want to use other models, please modify [models](./mindsearch/agent/models.py)
|
62 |
+
|
63 |
+
### Step3: Setup MindSearch Frontend
|
64 |
+
|
65 |
+
Providing following frontend interfaces,
|
66 |
+
|
67 |
+
- React
|
68 |
+
|
69 |
+
```bash
|
70 |
+
# Install Node.js and npm
|
71 |
+
# for Ubuntu
|
72 |
+
sudo apt install nodejs npm
|
73 |
+
|
74 |
+
# for windows
|
75 |
+
# download from https://nodejs.org/zh-cn/download/prebuilt-installer
|
76 |
+
|
77 |
+
# Install dependencies
|
78 |
+
|
79 |
+
cd frontend/React
|
80 |
+
npm install
|
81 |
+
npm start
|
82 |
+
```
|
83 |
+
|
84 |
+
Details can be found in [React](./frontend/React/README.md)
|
85 |
+
|
86 |
+
- Gradio
|
87 |
+
|
88 |
+
```bash
|
89 |
+
python frontend/mindsearch_gradio.py
|
90 |
+
```
|
91 |
+
|
92 |
+
- Streamlit
|
93 |
+
|
94 |
+
```bash
|
95 |
+
streamlit run frontend/mindsearch_streamlit.py
|
96 |
+
```
|
97 |
+
|
98 |
+
## 🐞 Debug Locally
|
99 |
+
|
100 |
+
```bash
|
101 |
+
python -m mindsearch.terminal
|
102 |
+
```
|
103 |
+
|
104 |
+
## 📝 License
|
105 |
+
|
106 |
+
This project is released under the [Apache 2.0 license](LICENSE).
|
107 |
+
|
108 |
+
## Citation
|
109 |
+
|
110 |
+
If you find this project useful in your research, please consider cite:
|
111 |
+
|
112 |
+
```
|
113 |
+
@misc{chen2024mindsearchmimickinghumanminds,
|
114 |
+
title={MindSearch: Mimicking Human Minds Elicits Deep AI Searcher},
|
115 |
+
author={Zehui Chen and Kuikun Liu and Qiuchen Wang and Jiangning Liu and Wenwei Zhang and Kai Chen and Feng Zhao},
|
116 |
+
year={2024},
|
117 |
+
eprint={2407.20183},
|
118 |
+
archivePrefix={arXiv},
|
119 |
+
primaryClass={cs.CL},
|
120 |
+
url={https://arxiv.org/abs/2407.20183},
|
121 |
+
}
|
122 |
+
```
|
README_zh-CN.md
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div id="top"></div>
|
2 |
+
|
3 |
+
<div align="center">
|
4 |
+
|
5 |
+
<img src="assets/logo.svg" style="width: 50%; height: auto;">
|
6 |
+
|
7 |
+
[🌐 Project Page](https://mindsearch.netlify.app/) | [📃 Paper](https://arxiv.org/abs/2407.20183) | [💻 Playground](https://mindsearch.openxlab.org.cn/)
|
8 |
+
|
9 |
+
[English](README.md) | 简体中文
|
10 |
+
|
11 |
+
<https://github.com/user-attachments/assets/b4312e9c-5b40-43e5-8c69-929c373e4965>
|
12 |
+
|
13 |
+
</div>
|
14 |
+
</p>
|
15 |
+
|
16 |
+
## ✨ MindSearch: Mimicking Human Minds Elicits Deep AI Searcher
|
17 |
+
|
18 |
+
MindSearch 是一个开源的 AI 搜索引擎框架,具有与 Perplexity.ai Pro 相同的性能。您可以轻松部署它来构建您自己的搜索引擎,可以使用闭源 LLM(如 GPT、Claude)或开源 LLM(如 InternLM2.5-7b-chat)。其拥有以下特性:
|
19 |
+
|
20 |
+
- 🤔 **任何想知道的问题**:MindSearch 通过搜索解决你在生活中遇到的各种问题
|
21 |
+
- 📚 **深度知识探索**:MindSearch 通过数百网页的浏览,提供更广泛、深层次的答案
|
22 |
+
- 🔍 **透明的解决方案路径**:MindSearch 提供了思考路径、搜索关键词等完整的内容,提高回复的可信度和可用性。
|
23 |
+
- 💻 **多种用户界面**:为用户提供各种接口,包括 React、Gradio、Streamlit 和本地调试。根据需要选择任意类型。
|
24 |
+
- 🧠 **动态图构建过程**:MindSearch 将用户查询分解为图中的子问题节点,并根据 WebSearcher 的搜索结果逐步扩展图。
|
25 |
+
|
26 |
+
<div align="center">
|
27 |
+
|
28 |
+
<img src="assets/teaser.gif">
|
29 |
+
|
30 |
+
</div>
|
31 |
+
|
32 |
+
## ⚡️ MindSearch VS 其他 AI 搜索引擎
|
33 |
+
|
34 |
+
在深度、广度和生成响应的准确性三个方面,对 ChatGPT-Web、Perplexity.ai(Pro)和 MindSearch 的表现进行比较。评估结果基于 100 个由人类专家精心设计的现实问题,并由 5 位专家进行评分\*。
|
35 |
+
|
36 |
+
<div align="center">
|
37 |
+
<img src="assets/mindsearch_openset.png" width="90%">
|
38 |
+
</div>
|
39 |
+
* 所有实验均在 2024 年 7 月 7 日之前完成。
|
40 |
+
|
41 |
+
## ⚽️ 构建您自己的 MindSearch
|
42 |
+
|
43 |
+
### 步骤1: 依赖安装
|
44 |
+
|
45 |
+
```bash
|
46 |
+
pip install -r requirements.txt
|
47 |
+
```
|
48 |
+
|
49 |
+
### 步骤2: 启动 MindSearch API
|
50 |
+
|
51 |
+
启动 FastAPI 服务器
|
52 |
+
|
53 |
+
```bash
|
54 |
+
python -m mindsearch.app --lang en --model_format internlm_server
|
55 |
+
```
|
56 |
+
|
57 |
+
- `--lang`: 模型的语言,`en` 为英语,`zh` 为中文。
|
58 |
+
- `--model_format`: 模型的格式。
|
59 |
+
- `internlm_server` 为 InternLM2.5-7b-chat 本地服务器。
|
60 |
+
- `gpt4` 为 GPT4。
|
61 |
+
如果您想使用其他模型,请修改 [models](./mindsearch/agent/models.py)
|
62 |
+
|
63 |
+
### 步骤3: 启动 MindSearch 前端
|
64 |
+
|
65 |
+
提供以下几种前端界面:
|
66 |
+
|
67 |
+
- React
|
68 |
+
|
69 |
+
```bash
|
70 |
+
# 安装 Node.js 和 npm
|
71 |
+
# 对于 Ubuntu
|
72 |
+
sudo apt install nodejs npm
|
73 |
+
# 对于 Windows
|
74 |
+
# 从 https://nodejs.org/zh-cn/download/prebuilt-installer 下载
|
75 |
+
|
76 |
+
cd frontend/React
|
77 |
+
npm install
|
78 |
+
npm start
|
79 |
+
```
|
80 |
+
|
81 |
+
更多细节请参考 [React](./frontend/React/README.md)
|
82 |
+
|
83 |
+
- Gradio
|
84 |
+
|
85 |
+
```bash
|
86 |
+
python frontend/mindsearch_gradio.py
|
87 |
+
```
|
88 |
+
|
89 |
+
- Streamlit
|
90 |
+
|
91 |
+
```bash
|
92 |
+
streamlit run frontend/mindsearch_streamlit.py
|
93 |
+
```
|
94 |
+
|
95 |
+
## 🐞 本地调试
|
96 |
+
|
97 |
+
```bash
|
98 |
+
python mindsearch/terminal.py
|
99 |
+
```
|
100 |
+
|
101 |
+
## 📝 许可证
|
102 |
+
|
103 |
+
该项目按照 [Apache 2.0 许可证](LICENSE) 发行。
|
104 |
+
|
105 |
+
## 学术引用
|
106 |
+
|
107 |
+
如果此项目对您的研究有帮助,请参考如下方式进行引用:
|
108 |
+
|
109 |
+
```
|
110 |
+
@misc{chen2024mindsearchmimickinghumanminds,
|
111 |
+
title={MindSearch: Mimicking Human Minds Elicits Deep AI Searcher},
|
112 |
+
author={Zehui Chen and Kuikun Liu and Qiuchen Wang and Jiangning Liu and Wenwei Zhang and Kai Chen and Feng Zhao},
|
113 |
+
year={2024},
|
114 |
+
eprint={2407.20183},
|
115 |
+
archivePrefix={arXiv},
|
116 |
+
primaryClass={cs.CL},
|
117 |
+
url={https://arxiv.org/abs/2407.20183},
|
118 |
+
}
|
119 |
+
```
|
assets/logo.svg
ADDED
assets/mindsearch_openset.png
ADDED
frontend/React/.gitignore
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
3 |
+
|
4 |
+
# dependencies
|
5 |
+
/node_modules
|
6 |
+
/.pnp
|
7 |
+
.pnp.js
|
8 |
+
|
9 |
+
# testing
|
10 |
+
/coverage
|
11 |
+
|
12 |
+
# production
|
13 |
+
/build
|
14 |
+
|
15 |
+
# misc
|
16 |
+
.DS_Store
|
17 |
+
|
18 |
+
npm-debug.log*
|
19 |
+
yarn-debug.log*
|
20 |
+
yarn-error.log*
|
frontend/React/.prettierignore
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
dist
|
2 |
+
deploy
|
3 |
+
values
|
4 |
+
node_modules
|
5 |
+
.gitignore
|
6 |
+
.prettierignore
|
7 |
+
.husky
|
frontend/React/.prettierrc.json
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"printWidth": 120,
|
3 |
+
"tabWidth": 4,
|
4 |
+
"singleQuote": true,
|
5 |
+
"quoteProps": "as-needed",
|
6 |
+
"bracketSpacing": true
|
7 |
+
}
|
frontend/React/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 开始
|
2 |
+
## 准备node.js开发环境
|
3 |
+
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,允许你在服务器端运行 JavaScript。以下是在 Windows、Linux 和 macOS 上安装 Node.js 的详细步骤。
|
4 |
+
|
5 |
+
### 在 Windows 上安装 Node.js
|
6 |
+
- 步骤 1: 访问 Node.js 官网
|
7 |
+
|
8 |
+
打开浏览器,访问 [Node.js](https://nodejs.org/zh-cn/download/prebuilt-installer) 官方网站。
|
9 |
+
|
10 |
+
- 步骤 2: 下载 Node.js 安装包
|
11 |
+
|
12 |
+
选择你需要的nodejs版本,设备的类型,点击下载,示例如下图:
|
13 |
+
![windows install](./windows-.png)
|
14 |
+
|
15 |
+
- 步骤 3: 安装 Node.js
|
16 |
+
|
17 |
+
双击下载的安装包开始安装。
|
18 |
+
|
19 |
+
跟随安装向导的指示进行安装。在安装过程中,你可以选择安装位置、是否将 Node.js 添加到系统 PATH 环境变量等选项。推荐选择“添加到 PATH”以便在任何地方都能通过命令行访问 Node.js。
|
20 |
+
安装完成后,点击“Finish”结束安装。
|
21 |
+
|
22 |
+
- 步骤 4: 验证安装
|
23 |
+
|
24 |
+
打开命令提示符(CMD)或 PowerShell。
|
25 |
+
输入 node -v 并回车,如果系统返回了 Node.js 的版本号,说明安装成功。
|
26 |
+
接着,输入 npm -v 并回车,npm 是 Node.js 的包管理器,如果返回了版本号,表示 npm 也已正确安装。
|
27 |
+
|
28 |
+
### 在 Linux 上安装 Node.js
|
29 |
+
注意: 由于 Linux 发行版众多,以下以 Ubuntu 为例说明,其他发行版(如 CentOS、Debian 等)的安装方式可能略有不同,可自行查询对应的安装办法。
|
30 |
+
|
31 |
+
- 步骤 1: 更新你的包管理器
|
32 |
+
|
33 |
+
打开终端。
|
34 |
+
|
35 |
+
输入 sudo apt update 并回车,以更新 Ubuntu 的包索引。
|
36 |
+
|
37 |
+
- 步骤 2: 安装 Node.js
|
38 |
+
|
39 |
+
对于 Ubuntu 18.04 及更高版本,Node.js 可以直接从 Ubuntu 的仓库中安装。
|
40 |
+
输入 sudo apt install nodejs npm 并回车。
|
41 |
+
对于旧版本的 Ubuntu 或需要安装特定版本的 Node.js,你可能需要使用如 NodeSource 这样的第三方仓库。
|
42 |
+
|
43 |
+
- 步骤 3: 验证安装
|
44 |
+
|
45 |
+
在终端中,输入 node -v 和 npm -v 来验证 Node.js 和 npm 是否已正确安装。
|
46 |
+
|
47 |
+
### 在 macOS 上安装 Node.js
|
48 |
+
|
49 |
+
#### 下载安装
|
50 |
+
- 步骤 1: 访问 Node.js 官网
|
51 |
+
|
52 |
+
打开浏览器,访问 Node.js 官方网站。
|
53 |
+
|
54 |
+
- 步骤 2: 下载 Node.js 安装包
|
55 |
+
|
56 |
+
在首页找到 macOS 对应的安装包(通常是 .pkg 文件),点击下载。
|
57 |
+
|
58 |
+
- 步骤 3: 安装 Node.js
|
59 |
+
|
60 |
+
找到下载的 .pkg 文件,双击打开。
|
61 |
+
跟随安装向导的指示进行安装。
|
62 |
+
安装完成后,点击“Close”结束安装。
|
63 |
+
|
64 |
+
- 步骤 4: 验证安装
|
65 |
+
|
66 |
+
打开终端。
|
67 |
+
|
68 |
+
输入 node -v 和 npm -v 来验证 Node.js 和 npm 是否已正确安装。
|
69 |
+
|
70 |
+
#### 使用HomeBrew安装
|
71 |
+
前提条件:确保你的macOS上已经安装了Homebrew。如果尚未安装,可以通过以下命令进行安装(以终端操作为例):
|
72 |
+
```
|
73 |
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
74 |
+
```
|
75 |
+
按照提示输入密码以确认安装。安装过程中,可能需要你同意许可协议等。
|
76 |
+
|
77 |
+
- 打开终端:
|
78 |
+
在macOS上找到并打开“终端”应用程序。
|
79 |
+
|
80 |
+
- 使用Homebrew安装Node.js:
|
81 |
+
在终端中输入以下命令来安装最新版本的Node.js
|
82 |
+
```
|
83 |
+
brew install node
|
84 |
+
```
|
85 |
+
Homebrew会自动下载Node.js的安装包,并处理相关的依赖项和安装过程。你需要等待一段时间,直到安装完成。
|
86 |
+
|
87 |
+
- 验证安装:
|
88 |
+
安装完成后,你可以通过输入以下命令来验证Node.js是否成功安装:
|
89 |
+
```
|
90 |
+
node -v
|
91 |
+
```
|
92 |
+
如果终端输出了Node.js的版本号,那么表示安装成功。同时,你也可以通过输入npm -v来验证npm(Node.js的包管理器)是否也成功安装。
|
93 |
+
|
94 |
+
完成以上步骤后,你应该能在你的 Windows、Linux 或 macOS 系统上成功安装并运行 Node.js。
|
95 |
+
|
96 |
+
### 更多
|
97 |
+
如需了解更多,可参照:https://nodejs.org/en
|
98 |
+
|
99 |
+
如环境已经准备好,跳转下一步
|
100 |
+
|
101 |
+
## 安装依赖
|
102 |
+
进入前端项目根目录
|
103 |
+
```
|
104 |
+
npm install
|
105 |
+
```
|
106 |
+
|
107 |
+
## 启动
|
108 |
+
```
|
109 |
+
npm start
|
110 |
+
```
|
111 |
+
|
112 |
+
启动成功后,界面将出现可访问的本地url
|
113 |
+
|
114 |
+
## 配置
|
115 |
+
### 接口请求配置
|
116 |
+
- 如您需要配置的服务支持跨域,可至/src/config/cgi.ts中修改请求链接,请求链接为http://ip:port/path;
|
117 |
+
- 如您需要配置的服务不支持跨域,可至vite.config.ts中配置proxy,示例如下:
|
118 |
+
|
119 |
+
```
|
120 |
+
server: {
|
121 |
+
port: 8080,
|
122 |
+
proxy: {
|
123 |
+
"/solve": {
|
124 |
+
target: "https://example.com",
|
125 |
+
changeOrigin: true,
|
126 |
+
}
|
127 |
+
}
|
128 |
+
}
|
129 |
+
```
|
130 |
+
|
131 |
+
## 知悉
|
132 |
+
- 前端服务基于react开发,如需了解react相关知识,可参考:https://react.dev/
|
frontend/React/index.html
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!doctype html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<link rel="icon" type="image/svg+xml" href="" />
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7 |
+
<title></title>
|
8 |
+
</head>
|
9 |
+
|
10 |
+
<body>
|
11 |
+
<div id="root"></div>
|
12 |
+
<script type="module" src="/src/index.tsx"></script>
|
13 |
+
</body>
|
14 |
+
</html>
|
frontend/React/package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
frontend/React/package.json
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "test-react-flow",
|
3 |
+
"private": true,
|
4 |
+
"version": "0.0.0",
|
5 |
+
"type": "module",
|
6 |
+
"scripts": {
|
7 |
+
"start": "vite --host --mode dev",
|
8 |
+
"build": "tsc && vite build",
|
9 |
+
"preview": "vite preview",
|
10 |
+
"prettier": "prettier --write ."
|
11 |
+
},
|
12 |
+
"devDependencies": {
|
13 |
+
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
14 |
+
"@types/classnames": "^2.3.1",
|
15 |
+
"@types/js-cookie": "^3.0.3",
|
16 |
+
"@types/node": "^18.15.11",
|
17 |
+
"@types/react": "^18.0.28",
|
18 |
+
"@types/react-dom": "^18.0.11",
|
19 |
+
"@vitejs/plugin-legacy": "^4.0.2",
|
20 |
+
"@vitejs/plugin-react": "^3.1.0",
|
21 |
+
"husky": "^9.0.11",
|
22 |
+
"less": "^4.1.3",
|
23 |
+
"lint-staged": "^15.2.7",
|
24 |
+
"prettier": "^3.0.0",
|
25 |
+
"react": "^18.2.0",
|
26 |
+
"react-dom": "^18.2.0",
|
27 |
+
"terser": "^5.16.9",
|
28 |
+
"typescript": "^4.9.3",
|
29 |
+
"vite": "^4.2.1",
|
30 |
+
"vite-babel-plugin": "^0.0.2"
|
31 |
+
},
|
32 |
+
"dependencies": {
|
33 |
+
"@antv/x6": "^2.18.1",
|
34 |
+
"@microsoft/fetch-event-source": "^2.0.1",
|
35 |
+
"antd": "^5.18.3",
|
36 |
+
"axios": "^1.3.5",
|
37 |
+
"classnames": "^2.5.1",
|
38 |
+
"elkjs": "^0.9.3",
|
39 |
+
"js-cookie": "^3.0.1",
|
40 |
+
"react-markdown": "^9.0.1",
|
41 |
+
"react-router": "^6.11.2",
|
42 |
+
"react-router-dom": "^6.11.2",
|
43 |
+
"reactflow": "^11.11.3",
|
44 |
+
"rehype-raw": "^7.0.0"
|
45 |
+
},
|
46 |
+
"lint-staged": {
|
47 |
+
"**/*.{ts, tsx, less, module.less, json, md, .html}": "prettier --write ."
|
48 |
+
}
|
49 |
+
}
|
frontend/React/src/App.module.less
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.app {
|
2 |
+
height: 100%;
|
3 |
+
display: flex;
|
4 |
+
justify-content: space-between;
|
5 |
+
background: url(./assets/background.png) rgb(247, 248, 255);
|
6 |
+
background-size: cover;
|
7 |
+
overflow: hidden;
|
8 |
+
}
|
9 |
+
|
10 |
+
.content {
|
11 |
+
padding-top: 64px;
|
12 |
+
width: 100%;
|
13 |
+
height: 100%;
|
14 |
+
box-sizing: border-box;
|
15 |
+
// display: flex;
|
16 |
+
// justify-content: center;
|
17 |
+
}
|
18 |
+
|
19 |
+
.header {
|
20 |
+
position: fixed;
|
21 |
+
padding: 16px 32px;
|
22 |
+
width: 100%;
|
23 |
+
display: flex;
|
24 |
+
align-items: center;
|
25 |
+
box-sizing: border-box;
|
26 |
+
|
27 |
+
&-nav {
|
28 |
+
flex: 1;
|
29 |
+
|
30 |
+
img {
|
31 |
+
height: 40px;
|
32 |
+
}
|
33 |
+
|
34 |
+
a {
|
35 |
+
display: inline-block;
|
36 |
+
text-decoration: none;
|
37 |
+
color: black;
|
38 |
+
|
39 |
+
&:not(:first-of-type) {
|
40 |
+
margin-left: 40px;
|
41 |
+
}
|
42 |
+
|
43 |
+
&.active {
|
44 |
+
font-weight: bold;
|
45 |
+
}
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
&-opt {
|
50 |
+
flex-shrink: 0;
|
51 |
+
display: flex;
|
52 |
+
align-items: center;
|
53 |
+
}
|
54 |
+
}
|
frontend/React/src/App.tsx
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import style from "./App.module.less";
|
2 |
+
import Logo from "@/assets/logo.svg";
|
3 |
+
import { BrowserRouter } from "react-router-dom";
|
4 |
+
import RouterRoutes from "@/routes/routes";
|
5 |
+
|
6 |
+
function App() {
|
7 |
+
return (
|
8 |
+
<BrowserRouter>
|
9 |
+
<div className={style.app} id="app">
|
10 |
+
<div className={style.header}>
|
11 |
+
<div className={style.headerNav}>
|
12 |
+
<img src={Logo} />
|
13 |
+
</div>
|
14 |
+
</div>
|
15 |
+
<div className={style.content}>
|
16 |
+
<RouterRoutes />
|
17 |
+
</div>
|
18 |
+
</div>
|
19 |
+
</BrowserRouter>
|
20 |
+
);
|
21 |
+
}
|
22 |
+
|
23 |
+
export default App;
|
frontend/React/src/assets/background.png
ADDED
frontend/React/src/assets/fold-icon.svg
ADDED
frontend/React/src/assets/logo.svg
ADDED
frontend/React/src/assets/pack-up.svg
ADDED
frontend/React/src/assets/sendIcon.svg
ADDED
frontend/React/src/assets/show-right-icon.png
ADDED
frontend/React/src/assets/unflod-icon.svg
ADDED
frontend/React/src/components/iconfont/index.tsx
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { createFromIconfontCN } from "@ant-design/icons";
|
2 |
+
|
3 |
+
const IconFont = createFromIconfontCN({
|
4 |
+
scriptUrl: "//at.alicdn.com/t/c/font_3858115_p8dw9q83s0h.js"
|
5 |
+
});
|
6 |
+
|
7 |
+
export default IconFont;
|
frontend/React/src/config/cgi.ts
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
export const mode = import.meta.env.MODE;
|
2 |
+
export const GET_SSE_DATA = 'http://127.0.0.1:8002/solve';
|
frontend/React/src/global.d.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
declare module 'event-source-polyfill';
|
frontend/React/src/index.less
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body,
|
2 |
+
html,
|
3 |
+
#root {
|
4 |
+
padding: 0;
|
5 |
+
margin: 0;
|
6 |
+
width: 100%;
|
7 |
+
height: 100%;
|
8 |
+
font-family: "PingFang SC";
|
9 |
+
font-size: 14px;
|
10 |
+
line-height: 21px;
|
11 |
+
}
|
12 |
+
|
13 |
+
#global__message-container {
|
14 |
+
position: fixed;
|
15 |
+
left: 0;
|
16 |
+
right: 0;
|
17 |
+
top: 72px;
|
18 |
+
z-index: 999;
|
19 |
+
display: flex;
|
20 |
+
flex-direction: column;
|
21 |
+
justify-content: center;
|
22 |
+
align-items: center;
|
23 |
+
}
|
24 |
+
|
25 |
+
.f {
|
26 |
+
color: #6674D6;
|
27 |
+
font-family: DIN;
|
28 |
+
font-size: 12px;
|
29 |
+
font-style: normal;
|
30 |
+
font-weight: 500;
|
31 |
+
line-height: 14px;
|
32 |
+
position: relative;
|
33 |
+
top: -4px;
|
34 |
+
padding: 0 3px;
|
35 |
+
|
36 |
+
&::after {
|
37 |
+
content: '·';
|
38 |
+
position: absolute;
|
39 |
+
top: 0;
|
40 |
+
right: -2px;
|
41 |
+
color: #6674D6;
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
p> :nth-last-child(1).f,
|
46 |
+
li> :nth-last-child(1).f {
|
47 |
+
&::after {
|
48 |
+
content: '';
|
49 |
+
opacity: 0;
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
.fnn2 {
|
54 |
+
color: #6674D6;
|
55 |
+
font-family: DIN;
|
56 |
+
font-size: 14px;
|
57 |
+
font-style: normal;
|
58 |
+
font-weight: 500;
|
59 |
+
line-height: 14px;
|
60 |
+
position: relative;
|
61 |
+
top: -2px;
|
62 |
+
}
|
frontend/React/src/index.tsx
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react";
|
2 |
+
import ReactDOM from "react-dom/client";
|
3 |
+
import "./index.less";
|
4 |
+
import App from "./App";
|
5 |
+
|
6 |
+
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
7 |
+
<React.StrictMode>
|
8 |
+
<App />
|
9 |
+
</React.StrictMode>,
|
10 |
+
);
|
frontend/React/src/pages/render/index.module.less
ADDED
@@ -0,0 +1,848 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// inspired by https://www.youtube.com/watch?v=Pl1Gw14pS2I
|
2 |
+
.mainPage {
|
3 |
+
display: flex;
|
4 |
+
justify-content: flex-start;
|
5 |
+
align-items: flex-start;
|
6 |
+
padding: 0 60px 60px;
|
7 |
+
height: 100%;
|
8 |
+
overflow: hidden;
|
9 |
+
position: relative;
|
10 |
+
min-width: 1280px;
|
11 |
+
max-width: 1920px;
|
12 |
+
margin: 0 auto;
|
13 |
+
|
14 |
+
.chatContent {
|
15 |
+
position: relative;
|
16 |
+
display: flex;
|
17 |
+
justify-content: flex-start;
|
18 |
+
flex-direction: column;
|
19 |
+
flex-grow: 1;
|
20 |
+
margin-right: 40px;
|
21 |
+
height: calc(100% - 60px);
|
22 |
+
overflow-y: hidden;
|
23 |
+
padding: 32px 0;
|
24 |
+
box-sizing: border-box;
|
25 |
+
|
26 |
+
.top {
|
27 |
+
height: calc(100% - 110px);
|
28 |
+
overflow-y: auto;
|
29 |
+
margin-bottom: 40px;
|
30 |
+
}
|
31 |
+
|
32 |
+
.top::-webkit-scrollbar {
|
33 |
+
width: 6px;
|
34 |
+
}
|
35 |
+
|
36 |
+
.top::-webkit-scrollbar-track {
|
37 |
+
background-color: rgba(255, 255, 255, 0);
|
38 |
+
border-radius: 100px;
|
39 |
+
}
|
40 |
+
|
41 |
+
.top::-webkit-scrollbar-thumb {
|
42 |
+
background-color: rgba(255, 255, 255, 0);
|
43 |
+
border-radius: 100px;
|
44 |
+
}
|
45 |
+
|
46 |
+
.question {
|
47 |
+
display: flex;
|
48 |
+
justify-content: flex-end;
|
49 |
+
margin-bottom: 40px;
|
50 |
+
|
51 |
+
span {
|
52 |
+
padding: 12px 20px;
|
53 |
+
color: #121316;
|
54 |
+
font-size: 14px;
|
55 |
+
line-height: 24px;
|
56 |
+
border-radius: 8px;
|
57 |
+
background: #FFF;
|
58 |
+
max-width: 93.75%;
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
+
.end {
|
63 |
+
position: absolute;
|
64 |
+
right: 0;
|
65 |
+
background-color: #fff;
|
66 |
+
display: flex;
|
67 |
+
justify-content: center;
|
68 |
+
align-items: center;
|
69 |
+
border-left: 1px solid #D7D8DD;
|
70 |
+
padding-left: 16px;
|
71 |
+
|
72 |
+
.node {
|
73 |
+
position: relative;
|
74 |
+
|
75 |
+
&::before {
|
76 |
+
content: "";
|
77 |
+
border: 1px solid #D7D8DD;
|
78 |
+
border-top: none;
|
79 |
+
border-left: none;
|
80 |
+
width: calc(16px - 2px);
|
81 |
+
height: 0px;
|
82 |
+
position: absolute;
|
83 |
+
left: -16px;
|
84 |
+
top: 50%;
|
85 |
+
// transform: translateY(-50%);
|
86 |
+
}
|
87 |
+
|
88 |
+
article {
|
89 |
+
padding: 8px 16px;
|
90 |
+
border-radius: 8px;
|
91 |
+
border: 1px solid transparent;
|
92 |
+
color: #4082FE;
|
93 |
+
text-align: center;
|
94 |
+
font-size: 14px;
|
95 |
+
line-height: 24px;
|
96 |
+
box-sizing: border-box;
|
97 |
+
background: rgba(232, 233, 249);
|
98 |
+
color: #2126C0;
|
99 |
+
}
|
100 |
+
}
|
101 |
+
}
|
102 |
+
|
103 |
+
.answer {
|
104 |
+
border-radius: 8px;
|
105 |
+
background: rgba(33, 38, 192, 0.10);
|
106 |
+
padding: 12px;
|
107 |
+
|
108 |
+
.inner {
|
109 |
+
width: 100%;
|
110 |
+
background-color: #fff;
|
111 |
+
border-radius: 4px;
|
112 |
+
padding: 8px;
|
113 |
+
box-sizing: border-box;
|
114 |
+
transition: all 0.5s ease;
|
115 |
+
margin-bottom: 18px;
|
116 |
+
|
117 |
+
.mapArea {
|
118 |
+
width: 100%;
|
119 |
+
overflow-x: auto;
|
120 |
+
overflow-y: hidden;
|
121 |
+
|
122 |
+
&::-webkit-scrollbar {
|
123 |
+
height: 6px;
|
124 |
+
}
|
125 |
+
|
126 |
+
&::-webkit-scrollbar-track {
|
127 |
+
background-color: rgba(255, 255, 255, 0);
|
128 |
+
border-radius: 10px;
|
129 |
+
}
|
130 |
+
|
131 |
+
&::-webkit-scrollbar-thumb {
|
132 |
+
background-color: #d7d8dd;
|
133 |
+
border-radius: 100px;
|
134 |
+
}
|
135 |
+
}
|
136 |
+
|
137 |
+
}
|
138 |
+
|
139 |
+
|
140 |
+
.response {
|
141 |
+
color: #121316;
|
142 |
+
font-size: 14px;
|
143 |
+
line-height: 24px;
|
144 |
+
padding: 18px 42px;
|
145 |
+
|
146 |
+
h3 {
|
147 |
+
font-size: 24px;
|
148 |
+
font-weight: 600;
|
149 |
+
line-height: 36px;
|
150 |
+
margin: 0 0 16px 0;
|
151 |
+
}
|
152 |
+
|
153 |
+
h4 {
|
154 |
+
font-size: 20px;
|
155 |
+
font-weight: 600;
|
156 |
+
line-height: 30px;
|
157 |
+
margin: 0 0 8px 0;
|
158 |
+
}
|
159 |
+
|
160 |
+
p {
|
161 |
+
color: rgba(18, 19, 22, 0.80);
|
162 |
+
font-size: 16px;
|
163 |
+
font-weight: 400;
|
164 |
+
line-height: 28px;
|
165 |
+
margin: 0 0 16px 0;
|
166 |
+
}
|
167 |
+
|
168 |
+
ul {
|
169 |
+
margin-bottom: 8px;
|
170 |
+
padding-left: 22px;
|
171 |
+
}
|
172 |
+
|
173 |
+
li {
|
174 |
+
color: rgba(18, 19, 22, 0.80);
|
175 |
+
font-size: 16px;
|
176 |
+
font-weight: 400;
|
177 |
+
line-height: 28px;
|
178 |
+
|
179 |
+
p {
|
180 |
+
margin-bottom: 4px;
|
181 |
+
}
|
182 |
+
}
|
183 |
+
}
|
184 |
+
}
|
185 |
+
|
186 |
+
.sendArea {
|
187 |
+
display: flex;
|
188 |
+
width: 100%;
|
189 |
+
box-sizing: border-box;
|
190 |
+
padding: 10px 12px 10px 24px;
|
191 |
+
justify-content: space-between;
|
192 |
+
align-items: center;
|
193 |
+
border-radius: 8px;
|
194 |
+
border: 2px solid var(--fill-5, #464A53);
|
195 |
+
background: #FFF;
|
196 |
+
position: relative;
|
197 |
+
|
198 |
+
:global {
|
199 |
+
.ant-input {
|
200 |
+
&:focus {
|
201 |
+
box-shadow: none !important;
|
202 |
+
outline: 0 !important;
|
203 |
+
}
|
204 |
+
}
|
205 |
+
}
|
206 |
+
|
207 |
+
input {
|
208 |
+
height: 36px;
|
209 |
+
line-height: 36px;
|
210 |
+
flex-grow: 1;
|
211 |
+
border: 0;
|
212 |
+
outline: 0;
|
213 |
+
|
214 |
+
&:focus {
|
215 |
+
border: 0;
|
216 |
+
outline: 0;
|
217 |
+
}
|
218 |
+
}
|
219 |
+
|
220 |
+
button {
|
221 |
+
display: flex;
|
222 |
+
justify-content: flex-start;
|
223 |
+
align-items: center;
|
224 |
+
border: 0;
|
225 |
+
background-color: #fff;
|
226 |
+
cursor: pointer;
|
227 |
+
padding: 8px;
|
228 |
+
width: 65px;
|
229 |
+
flex-shrink: 0;
|
230 |
+
|
231 |
+
img {
|
232 |
+
margin-right: 4px;
|
233 |
+
}
|
234 |
+
}
|
235 |
+
}
|
236 |
+
|
237 |
+
.notice {
|
238 |
+
color: #12131659;
|
239 |
+
padding-top: 8px;
|
240 |
+
text-align: center;
|
241 |
+
font-weight: 400;
|
242 |
+
|
243 |
+
a {
|
244 |
+
text-decoration: none;
|
245 |
+
color: #444;
|
246 |
+
display: inline-flex;
|
247 |
+
align-items: center;
|
248 |
+
|
249 |
+
span {
|
250 |
+
font-size: 18px;
|
251 |
+
}
|
252 |
+
}
|
253 |
+
}
|
254 |
+
}
|
255 |
+
|
256 |
+
.progressContent {
|
257 |
+
width: 44.44%;
|
258 |
+
flex-shrink: 0;
|
259 |
+
box-sizing: border-box;
|
260 |
+
padding: 24px;
|
261 |
+
border-radius: 8px;
|
262 |
+
border: rgba(33, 38, 192, 0.10);
|
263 |
+
background: rgba(255, 255, 255, 0.80);
|
264 |
+
height: calc(100% - 60px);
|
265 |
+
overflow-y: auto;
|
266 |
+
position: relative;
|
267 |
+
|
268 |
+
&::-webkit-scrollbar {
|
269 |
+
width: 6px;
|
270 |
+
}
|
271 |
+
|
272 |
+
&::-webkit-scrollbar-track {
|
273 |
+
background-color: rgba(255, 255, 255, 0);
|
274 |
+
border-radius: 100px;
|
275 |
+
}
|
276 |
+
|
277 |
+
&::-webkit-scrollbar-thumb {
|
278 |
+
background-color: rgba(255, 255, 255, 0);
|
279 |
+
border-radius: 100px;
|
280 |
+
}
|
281 |
+
|
282 |
+
.toggleIcon {
|
283 |
+
position: absolute;
|
284 |
+
right: 24px;
|
285 |
+
top: 28px;
|
286 |
+
cursor: pointer;
|
287 |
+
}
|
288 |
+
|
289 |
+
.titleNode {
|
290 |
+
color: #121316;
|
291 |
+
font-size: 24px;
|
292 |
+
font-weight: 600;
|
293 |
+
line-height: 36px;
|
294 |
+
margin-bottom: 24px;
|
295 |
+
}
|
296 |
+
|
297 |
+
.conclusion {
|
298 |
+
padding-top: 8px;
|
299 |
+
color: #121316;
|
300 |
+
font-size: 14px;
|
301 |
+
line-height: 24px;
|
302 |
+
|
303 |
+
ul {
|
304 |
+
padding-left: 24px;
|
305 |
+
}
|
306 |
+
}
|
307 |
+
|
308 |
+
.steps {
|
309 |
+
.title {
|
310 |
+
color: var(--100-text-5, #121316);
|
311 |
+
font-size: 20px;
|
312 |
+
font-weight: 600;
|
313 |
+
line-height: 30px;
|
314 |
+
display: flex;
|
315 |
+
justify-content: flex-start;
|
316 |
+
align-items: center;
|
317 |
+
position: relative;
|
318 |
+
|
319 |
+
.open {
|
320 |
+
position: absolute;
|
321 |
+
right: 0;
|
322 |
+
font-size: 20px;
|
323 |
+
font-weight: normal;
|
324 |
+
|
325 |
+
span {
|
326 |
+
color: #121316;
|
327 |
+
opacity: 0.6;
|
328 |
+
}
|
329 |
+
}
|
330 |
+
|
331 |
+
i {
|
332 |
+
width: 12px;
|
333 |
+
height: 12px;
|
334 |
+
border-radius: 50%;
|
335 |
+
background-color: #2126C0;
|
336 |
+
margin-right: 8px;
|
337 |
+
}
|
338 |
+
}
|
339 |
+
|
340 |
+
&.thinking,
|
341 |
+
&.select {
|
342 |
+
margin-bottom: 24px;
|
343 |
+
}
|
344 |
+
|
345 |
+
&.select {
|
346 |
+
.searchList {
|
347 |
+
margin-top: 0 !important;
|
348 |
+
border-radius: 8px;
|
349 |
+
background: var(--fill-2, #F4F5F9);
|
350 |
+
padding: 8px;
|
351 |
+
}
|
352 |
+
}
|
353 |
+
|
354 |
+
.con {
|
355 |
+
margin-left: 5px;
|
356 |
+
padding-top: 8px;
|
357 |
+
padding-left: 15px;
|
358 |
+
border-left: 1px solid rgba(33, 38, 192, 0.20);
|
359 |
+
height: auto;
|
360 |
+
|
361 |
+
&.collapsed {
|
362 |
+
overflow: hidden;
|
363 |
+
height: 0;
|
364 |
+
padding-top: 0;
|
365 |
+
transition: all 1s;
|
366 |
+
}
|
367 |
+
|
368 |
+
.subTitle {
|
369 |
+
color: var(--100-text-5, #121316);
|
370 |
+
font-size: 14px;
|
371 |
+
font-weight: 600;
|
372 |
+
line-height: 24px;
|
373 |
+
margin-bottom: 4px;
|
374 |
+
|
375 |
+
span {
|
376 |
+
margin-right: 4px;
|
377 |
+
}
|
378 |
+
}
|
379 |
+
|
380 |
+
.query,
|
381 |
+
>.searchList {
|
382 |
+
margin-top: 24px;
|
383 |
+
// margin-bottom: 24px;
|
384 |
+
}
|
385 |
+
|
386 |
+
.query {
|
387 |
+
&-Item {
|
388 |
+
display: inline-flex;
|
389 |
+
padding: 4px 8px;
|
390 |
+
margin-right: 4px;
|
391 |
+
margin-bottom: 4px;
|
392 |
+
border-radius: 4px;
|
393 |
+
border: 1px solid #EBECF0;
|
394 |
+
color: rgba(18, 19, 22, 0.80);
|
395 |
+
font-size: 14px;
|
396 |
+
line-height: 24px;
|
397 |
+
height: 32px;
|
398 |
+
box-sizing: border-box;
|
399 |
+
overflow: hidden;
|
400 |
+
// animation: fadeIn linear 2s;
|
401 |
+
}
|
402 |
+
}
|
403 |
+
|
404 |
+
.searchList {
|
405 |
+
.thought {
|
406 |
+
color: rgba(18, 19, 22, 0.80);
|
407 |
+
font-size: 14px;
|
408 |
+
line-height: 24px;
|
409 |
+
margin-bottom: 16px;
|
410 |
+
}
|
411 |
+
|
412 |
+
.scrollCon {
|
413 |
+
padding-right: 6px;
|
414 |
+
max-height: 300px;
|
415 |
+
overflow-y: auto;
|
416 |
+
position: relative;
|
417 |
+
}
|
418 |
+
|
419 |
+
.scrollCon::-webkit-scrollbar {
|
420 |
+
width: 6px;
|
421 |
+
}
|
422 |
+
|
423 |
+
.scrollCon::-webkit-scrollbar-track {
|
424 |
+
background-color: rgba(255, 255, 255, 0);
|
425 |
+
border-radius: 100px;
|
426 |
+
}
|
427 |
+
|
428 |
+
.scrollCon::-webkit-scrollbar-thumb {
|
429 |
+
background-color: #d7d8dd;
|
430 |
+
border-radius: 100px;
|
431 |
+
}
|
432 |
+
|
433 |
+
.inner {
|
434 |
+
width: 100%;
|
435 |
+
border-radius: 8px;
|
436 |
+
background: var(--fill-2, #F4F5F9);
|
437 |
+
transition: all 0.5s ease;
|
438 |
+
box-sizing: border-box;
|
439 |
+
padding: 8px;
|
440 |
+
}
|
441 |
+
|
442 |
+
.searchItem {
|
443 |
+
border-radius: 8px;
|
444 |
+
background: var(---fill-0, #FFF);
|
445 |
+
margin-bottom: 6px;
|
446 |
+
padding: 4px 8px;
|
447 |
+
transition: all 0.5s ease-in-out;
|
448 |
+
|
449 |
+
&.highLight {
|
450 |
+
border: 1px solid var(---Success-6, #00B365);
|
451 |
+
background: linear-gradient(0deg, rgba(218, 242, 228, 0.40) 0%, rgba(218, 242, 228, 0.40) 100%), #FFF;
|
452 |
+
}
|
453 |
+
|
454 |
+
p {
|
455 |
+
white-space: nowrap;
|
456 |
+
max-width: 95%;
|
457 |
+
overflow: hidden;
|
458 |
+
text-overflow: ellipsis;
|
459 |
+
margin: 0;
|
460 |
+
}
|
461 |
+
|
462 |
+
p.summ {
|
463 |
+
color: rgba(18, 19, 22, 0.80);
|
464 |
+
font-size: 13px;
|
465 |
+
line-height: 20px;
|
466 |
+
margin-bottom: 2px;
|
467 |
+
}
|
468 |
+
|
469 |
+
p.url {
|
470 |
+
color: var(--60-text-3, rgba(18, 19, 22, 0.60));
|
471 |
+
font-size: 12px;
|
472 |
+
line-height: 18px;
|
473 |
+
padding-left: 20px;
|
474 |
+
}
|
475 |
+
}
|
476 |
+
}
|
477 |
+
|
478 |
+
|
479 |
+
}
|
480 |
+
}
|
481 |
+
}
|
482 |
+
}
|
483 |
+
|
484 |
+
pre {
|
485 |
+
margin: 0;
|
486 |
+
padding-top: 8px;
|
487 |
+
color: #121316;
|
488 |
+
font-size: 14px;
|
489 |
+
line-height: 24px;
|
490 |
+
font-family: 'PingFang SC', 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
491 |
+
white-space: wrap;
|
492 |
+
}
|
493 |
+
|
494 |
+
ul {
|
495 |
+
margin: 0;
|
496 |
+
padding: 0;
|
497 |
+
}
|
498 |
+
|
499 |
+
.draft {
|
500 |
+
width: 100%;
|
501 |
+
white-space: wrap;
|
502 |
+
// display: flex;
|
503 |
+
// justify-content: flex-start;
|
504 |
+
// align-items: flex-start;
|
505 |
+
position: relative;
|
506 |
+
|
507 |
+
.loading,
|
508 |
+
.loading>div {
|
509 |
+
position: relative;
|
510 |
+
box-sizing: border-box;
|
511 |
+
}
|
512 |
+
|
513 |
+
.loading {
|
514 |
+
display: flex;
|
515 |
+
justify-content: center;
|
516 |
+
align-items: center;
|
517 |
+
font-size: 0;
|
518 |
+
color: #fff;
|
519 |
+
background-color: #f90;
|
520 |
+
width: 20px;
|
521 |
+
height: 20px;
|
522 |
+
border-radius: 50%;
|
523 |
+
margin-right: 3px;
|
524 |
+
flex-shrink: 0;
|
525 |
+
position: absolute;
|
526 |
+
top: 0;
|
527 |
+
left: 0;
|
528 |
+
}
|
529 |
+
|
530 |
+
.loading>div {
|
531 |
+
display: inline-block;
|
532 |
+
float: none;
|
533 |
+
background-color: currentColor;
|
534 |
+
border: 0 solid currentColor;
|
535 |
+
}
|
536 |
+
|
537 |
+
.loading>div:nth-child(1) {
|
538 |
+
animation-delay: -200ms;
|
539 |
+
}
|
540 |
+
|
541 |
+
.loading>div:nth-child(2) {
|
542 |
+
animation-delay: -100ms;
|
543 |
+
}
|
544 |
+
|
545 |
+
.loading>div:nth-child(3) {
|
546 |
+
animation-delay: 0ms;
|
547 |
+
}
|
548 |
+
|
549 |
+
.loading>div {
|
550 |
+
width: 3px;
|
551 |
+
height: 3px;
|
552 |
+
margin: 2px 1px;
|
553 |
+
border-radius: 100%;
|
554 |
+
animation: ball-pulse 1s ease infinite;
|
555 |
+
}
|
556 |
+
}
|
557 |
+
|
558 |
+
.mindmap {
|
559 |
+
position: relative;
|
560 |
+
|
561 |
+
article {
|
562 |
+
padding: 6px 16px;
|
563 |
+
border-radius: 8px;
|
564 |
+
height: 38px;
|
565 |
+
border: 1px solid transparent;
|
566 |
+
background: #FFF;
|
567 |
+
color: #121316;
|
568 |
+
text-align: center;
|
569 |
+
font-size: 14px;
|
570 |
+
line-height: 24px;
|
571 |
+
position: relative;
|
572 |
+
box-sizing: border-box;
|
573 |
+
|
574 |
+
&.loading {
|
575 |
+
line-height: 20px;
|
576 |
+
border-radius: 8px;
|
577 |
+
overflow: hidden;
|
578 |
+
border: 1px solid transparent;
|
579 |
+
padding: 4px;
|
580 |
+
|
581 |
+
span {
|
582 |
+
color: #2126C0;
|
583 |
+
background-color: #fff;
|
584 |
+
border-radius: 4px;
|
585 |
+
line-height: 24px;
|
586 |
+
padding: 2px 12px;
|
587 |
+
}
|
588 |
+
|
589 |
+
.looping {
|
590 |
+
--border-width: 4px;
|
591 |
+
--follow-panel-linear-border: linear-gradient(91deg,
|
592 |
+
#5551FF 0.58%,
|
593 |
+
#FF87DE 100.36%);
|
594 |
+
|
595 |
+
position: absolute;
|
596 |
+
top: 0;
|
597 |
+
left: 0;
|
598 |
+
width: calc(100% + var(--border-width) * 2 - 8px);
|
599 |
+
height: calc(100%);
|
600 |
+
background: var(--follow-panel-linear-border);
|
601 |
+
background-size: 300% 300%;
|
602 |
+
background-position: 0 50%;
|
603 |
+
animation: moveGradient 4s alternate infinite;
|
604 |
+
}
|
605 |
+
}
|
606 |
+
|
607 |
+
&.disabled {
|
608 |
+
border-radius: 8px;
|
609 |
+
border: 1px solid #D7D8DD;
|
610 |
+
color: rgba(18, 19, 22, 0.35);
|
611 |
+
}
|
612 |
+
|
613 |
+
&.finished {
|
614 |
+
// cursor: pointer;
|
615 |
+
border: 1px solid #2126C0;
|
616 |
+
|
617 |
+
.finishDot {
|
618 |
+
position: absolute;
|
619 |
+
top: 6px;
|
620 |
+
right: 6px;
|
621 |
+
width: 6px;
|
622 |
+
height: 6px;
|
623 |
+
background-color: #C9C0FE;
|
624 |
+
border-radius: 50%;
|
625 |
+
}
|
626 |
+
}
|
627 |
+
|
628 |
+
&.init {
|
629 |
+
border: 1px solid transparent;
|
630 |
+
cursor: auto;
|
631 |
+
}
|
632 |
+
|
633 |
+
span {
|
634 |
+
display: block;
|
635 |
+
white-space: nowrap;
|
636 |
+
max-width: 160px;
|
637 |
+
overflow: hidden;
|
638 |
+
text-overflow: ellipsis;
|
639 |
+
position: relative;
|
640 |
+
z-index: 20;
|
641 |
+
}
|
642 |
+
|
643 |
+
span.status {
|
644 |
+
color: #4082FE;
|
645 |
+
}
|
646 |
+
|
647 |
+
}
|
648 |
+
|
649 |
+
// 第一个article,起始节点
|
650 |
+
>li {
|
651 |
+
>article {
|
652 |
+
border-radius: 8px;
|
653 |
+
background: rgba(33, 38, 192, 0.10);
|
654 |
+
color: #2126C0;
|
655 |
+
}
|
656 |
+
}
|
657 |
+
|
658 |
+
li {
|
659 |
+
list-style: none;
|
660 |
+
display: flex;
|
661 |
+
align-items: center;
|
662 |
+
box-sizing: border-box;
|
663 |
+
margin: 16px;
|
664 |
+
line-height: 1;
|
665 |
+
position: relative;
|
666 |
+
|
667 |
+
&>ul.onlyone {
|
668 |
+
&:before {
|
669 |
+
opacity: 0;
|
670 |
+
}
|
671 |
+
|
672 |
+
>li {
|
673 |
+
margin-left: 0px;
|
674 |
+
}
|
675 |
+
|
676 |
+
&>li:after {
|
677 |
+
opacity: 0;
|
678 |
+
}
|
679 |
+
|
680 |
+
&>li:before {
|
681 |
+
// left: 0;
|
682 |
+
}
|
683 |
+
}
|
684 |
+
|
685 |
+
&>ul:before {
|
686 |
+
content: "";
|
687 |
+
border: 1px solid #D7D8DD;
|
688 |
+
border-top: none;
|
689 |
+
border-left: none;
|
690 |
+
width: calc(16px - 2px);
|
691 |
+
height: 0px;
|
692 |
+
position: absolute;
|
693 |
+
left: 0;
|
694 |
+
top: 50%;
|
695 |
+
// transform: translateY(-50%);
|
696 |
+
}
|
697 |
+
|
698 |
+
&:before {
|
699 |
+
content: "";
|
700 |
+
border: 1px solid #D7D8DD;
|
701 |
+
border-top: none;
|
702 |
+
border-left: none;
|
703 |
+
width: 16px;
|
704 |
+
height: 0px;
|
705 |
+
position: absolute;
|
706 |
+
left: calc(-16px - 1px);
|
707 |
+
}
|
708 |
+
|
709 |
+
&:after {
|
710 |
+
content: "";
|
711 |
+
border: 1px solid #D7D8DD;
|
712 |
+
border-top: none;
|
713 |
+
border-left: none;
|
714 |
+
width: 0px;
|
715 |
+
height: calc(100% / 2 + 33px);
|
716 |
+
position: absolute;
|
717 |
+
left: calc(-16px - 2px);
|
718 |
+
}
|
719 |
+
|
720 |
+
&:first-of-type:after {
|
721 |
+
top: 50%;
|
722 |
+
}
|
723 |
+
|
724 |
+
&:last-of-type:after {
|
725 |
+
bottom: 50%;
|
726 |
+
}
|
727 |
+
|
728 |
+
ul {
|
729 |
+
padding: 0 0 0 16px;
|
730 |
+
position: relative;
|
731 |
+
}
|
732 |
+
}
|
733 |
+
|
734 |
+
&>li {
|
735 |
+
|
736 |
+
&:after,
|
737 |
+
&:before {
|
738 |
+
display: none;
|
739 |
+
}
|
740 |
+
}
|
741 |
+
|
742 |
+
.endLine {
|
743 |
+
border-bottom: 1px solid #D7D8DD;
|
744 |
+
width: 3000px;
|
745 |
+
transition: width 1s ease-in-out;
|
746 |
+
}
|
747 |
+
}
|
748 |
+
|
749 |
+
.showRight {
|
750 |
+
position: fixed;
|
751 |
+
top: 80px;
|
752 |
+
right: -10px;
|
753 |
+
width: 42px;
|
754 |
+
cursor: pointer;
|
755 |
+
|
756 |
+
img {
|
757 |
+
width: 100%;
|
758 |
+
}
|
759 |
+
}
|
760 |
+
|
761 |
+
@keyframes ball-pulse {
|
762 |
+
|
763 |
+
0%,
|
764 |
+
60%,
|
765 |
+
100% {
|
766 |
+
opacity: 1;
|
767 |
+
transform: scale(1);
|
768 |
+
}
|
769 |
+
|
770 |
+
30% {
|
771 |
+
opacity: 0.1;
|
772 |
+
transform: scale(0.01);
|
773 |
+
}
|
774 |
+
}
|
775 |
+
|
776 |
+
@keyframes moveGradient {
|
777 |
+
50% {
|
778 |
+
background-position: 100% 50%;
|
779 |
+
}
|
780 |
+
}
|
781 |
+
|
782 |
+
@keyframes fadeIn {
|
783 |
+
0% {
|
784 |
+
width: 0;
|
785 |
+
opacity: 0;
|
786 |
+
}
|
787 |
+
|
788 |
+
100% {
|
789 |
+
width: auto;
|
790 |
+
opacity: 1;
|
791 |
+
}
|
792 |
+
}
|
793 |
+
|
794 |
+
@keyframes unfold {
|
795 |
+
0% {
|
796 |
+
height: auto;
|
797 |
+
}
|
798 |
+
|
799 |
+
100% {
|
800 |
+
height: 0;
|
801 |
+
}
|
802 |
+
}
|
803 |
+
|
804 |
+
|
805 |
+
.loading99 {
|
806 |
+
margin: 20px;
|
807 |
+
position: relative;
|
808 |
+
width: 1px;
|
809 |
+
height: 1px;
|
810 |
+
}
|
811 |
+
|
812 |
+
.loading99:before,
|
813 |
+
.loading99:after {
|
814 |
+
position: absolute;
|
815 |
+
display: inline-block;
|
816 |
+
width: 15px;
|
817 |
+
height: 15px;
|
818 |
+
content: "";
|
819 |
+
border-radius: 100%;
|
820 |
+
background-color: #5551FF;
|
821 |
+
}
|
822 |
+
|
823 |
+
.loading99:before {
|
824 |
+
left: -15px;
|
825 |
+
animation: ball-pulse infinite 0.75s -0.4s cubic-bezier(0.2, 0.68, 0.18, 1.08);
|
826 |
+
}
|
827 |
+
|
828 |
+
.loading99:after {
|
829 |
+
right: -15px;
|
830 |
+
animation: ball-pulse infinite 0.75s cubic-bezier(0.2, 0.68, 0.18, 1.08);
|
831 |
+
}
|
832 |
+
|
833 |
+
@keyframes ball-pulse {
|
834 |
+
0% {
|
835 |
+
transform: scale(1);
|
836 |
+
opacity: 1;
|
837 |
+
}
|
838 |
+
|
839 |
+
50% {
|
840 |
+
transform: scale(0.1);
|
841 |
+
opacity: 0.6;
|
842 |
+
}
|
843 |
+
|
844 |
+
100% {
|
845 |
+
transform: scale(1);
|
846 |
+
opacity: 1;
|
847 |
+
}
|
848 |
+
}
|
frontend/React/src/pages/render/index.tsx
ADDED
@@ -0,0 +1,681 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import styles from './index.module.less';
|
2 |
+
import { useEffect, useState, useRef, Children } from 'react';
|
3 |
+
import MindMapItem from './mindMapItem';
|
4 |
+
import PackIcon from '@/assets/pack-up.svg';
|
5 |
+
import SendIcon from '@/assets/sendIcon.svg';
|
6 |
+
import { Tooltip, Input, message } from 'antd';
|
7 |
+
import IconFont from '@/components/iconfont';
|
8 |
+
import ReactMarkdown from "react-markdown";
|
9 |
+
import ShowRightIcon from "@/assets/show-right-icon.png";
|
10 |
+
import rehypeRaw from 'rehype-raw';
|
11 |
+
import classNames from 'classnames';
|
12 |
+
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
13 |
+
import { GET_SSE_DATA } from '@/config/cgi';
|
14 |
+
import { replaceStr } from '@/utils/tools';
|
15 |
+
|
16 |
+
const RenderTest = () => {
|
17 |
+
let eventSource: any = null;
|
18 |
+
let sseTimer: any = useRef(null);
|
19 |
+
const [isWaiting, setIsWaiting] = useState(false);
|
20 |
+
const [question, setQuestion] = useState("");
|
21 |
+
const [stashQuestion, setStashQuestion] = useState("");
|
22 |
+
const [isEnd, setIsEnd] = useState(false);
|
23 |
+
const [showEndNode, setShowEndNode] = useState(false);
|
24 |
+
// 一组节点的渲染草稿
|
25 |
+
const [draft, setDraft] = useState('');
|
26 |
+
// 一轮完整对话结束
|
27 |
+
const [chatIsOver, setChatIsOver] = useState(true);
|
28 |
+
// 一组节点的思考草稿是不是打印结束
|
29 |
+
const [draftEnd, setDraftEnd] = useState(false);
|
30 |
+
|
31 |
+
const [progress1, setProgress1] = useState('');
|
32 |
+
const [progress2, setProgress2] = useState('');
|
33 |
+
const [progressEnd, setProgressEnd] = useState(false);
|
34 |
+
|
35 |
+
const [conclusion, setConclusion] = useState('');
|
36 |
+
const [stashConclusion, setstashConclusion] = useState('');
|
37 |
+
|
38 |
+
const [query, setQuery] = useState([]);
|
39 |
+
const [searchList, setSearchList] = useState([]);
|
40 |
+
// 整体的渲染树
|
41 |
+
const [renderData, setRenderData] = useState<any[]>([]);
|
42 |
+
const [currentNode, setCurrentNode] = useState<any>(null);
|
43 |
+
// 渲染minddata里的第几个item
|
44 |
+
const [renderIndex, setRenderIndex] = useState<number>(0);
|
45 |
+
const [response, setResponse] = useState("");
|
46 |
+
const [currentStep, setCurrentStep] = useState(0);
|
47 |
+
// steps展开收起的信息
|
48 |
+
const [collapseInfo, setCollapseInfo] = useState([true, true]);
|
49 |
+
const [mapWidth, setMapWidth] = useState(0);
|
50 |
+
// 是否展示右侧内容
|
51 |
+
const [showRight, setShowRight] = useState(true);
|
52 |
+
|
53 |
+
const [currentNodeRendering, setCurrentNodeRendering] = useState(false);
|
54 |
+
const [selectedIds, setSelectedIds] = useState([]);
|
55 |
+
const [nodeName, setNodeName] = useState('');
|
56 |
+
const hasHighlight = useRef(false);
|
57 |
+
const conclusionRender = useRef(false);
|
58 |
+
const nodeDraftRender = useRef(false);
|
59 |
+
const [obj, setObj] = useState<any>(null);
|
60 |
+
const [nodeOutputEnd, setNodeEnd] = useState(false);
|
61 |
+
const [adjList, setAdjList] = useState([]);
|
62 |
+
|
63 |
+
const TEXT_INTERVAL = 20;
|
64 |
+
const SEARCHLIST_INTERVAL = 80;
|
65 |
+
|
66 |
+
|
67 |
+
const toggleRight = () => {
|
68 |
+
setShowRight(!showRight);
|
69 |
+
};
|
70 |
+
|
71 |
+
const findAndUpdateStatus = (nodes: any[], targetNode: any) => {
|
72 |
+
return nodes.map((node) => {
|
73 |
+
if (node.state === 1 && node.id !== 0) {
|
74 |
+
return { ...node, state: 3 };
|
75 |
+
}
|
76 |
+
|
77 |
+
if (node.name === targetNode) {
|
78 |
+
return { ...node, state: 1 };
|
79 |
+
}
|
80 |
+
|
81 |
+
if (node.children) {
|
82 |
+
// 递归地在子节点中查找
|
83 |
+
node.children = findAndUpdateStatus(node.children, targetNode);
|
84 |
+
}
|
85 |
+
|
86 |
+
return node;
|
87 |
+
});
|
88 |
+
}
|
89 |
+
|
90 |
+
const generateEndStyle = () => {
|
91 |
+
// 获取所有class为endline的div元素
|
92 |
+
const endlineDivs = document.getElementsByClassName('endline');
|
93 |
+
const mindMap = document.getElementById("mindMap");
|
94 |
+
// 确保至少有两个元素
|
95 |
+
if (endlineDivs.length >= 2 && mindMap) {
|
96 |
+
// 获取第一个和最后一个元素的边界框(bounding rectangle)
|
97 |
+
const firstRect = endlineDivs[0].getBoundingClientRect();
|
98 |
+
const lastRect = endlineDivs[endlineDivs.length - 1].getBoundingClientRect();
|
99 |
+
const mindMapRect = mindMap?.getBoundingClientRect();
|
100 |
+
// 计算y值的差值
|
101 |
+
const yDiff = lastRect.top - firstRect.top;
|
102 |
+
// const top = firstRect.top - mindMapRect.top;
|
103 |
+
// 如果需要包含元素的完整高度(不仅仅是顶部位置),可以加上元素的高度
|
104 |
+
// const yDiffWithHeight = yDiff + (lastRect.height - firstRect.height);
|
105 |
+
return {
|
106 |
+
top: firstRect.top - mindMapRect.top,
|
107 |
+
height: yDiff + 1
|
108 |
+
};
|
109 |
+
} else {
|
110 |
+
return {
|
111 |
+
top: '50%',
|
112 |
+
height: 0
|
113 |
+
};
|
114 |
+
}
|
115 |
+
};
|
116 |
+
|
117 |
+
const generateWidth = () => {
|
118 |
+
const articles = document.querySelectorAll('article');
|
119 |
+
// 确保至少有两个元素
|
120 |
+
if (articles?.length) {
|
121 |
+
let maxRight = 0;
|
122 |
+
articles.forEach((item, index) => {
|
123 |
+
if (item.getBoundingClientRect().right > maxRight) {
|
124 |
+
maxRight = item.getBoundingClientRect().right;
|
125 |
+
}
|
126 |
+
})
|
127 |
+
const firstArticle = articles[0].getBoundingClientRect();
|
128 |
+
if (maxRight - firstArticle.left + 200 > mapWidth) {
|
129 |
+
return maxRight - firstArticle.left + 200
|
130 |
+
} else {
|
131 |
+
return mapWidth;
|
132 |
+
}
|
133 |
+
} else {
|
134 |
+
return 100;
|
135 |
+
}
|
136 |
+
};
|
137 |
+
|
138 |
+
// 逐字渲染
|
139 |
+
const renderDraft = (str: string, type: string, endCallback: () => void) => {
|
140 |
+
// 已经输出的字符数量
|
141 |
+
let outputIndex = 0;
|
142 |
+
|
143 |
+
// 输出字符的函数
|
144 |
+
const outputText = () => {
|
145 |
+
// 给出高亮后draft输出的结束标志
|
146 |
+
if (type === 'stepDraft-1' && outputIndex + 3 > str?.length) {
|
147 |
+
nodeDraftRender.current = true;
|
148 |
+
}
|
149 |
+
// 如果还有字符未输出
|
150 |
+
if (outputIndex < str?.length) {
|
151 |
+
// 获取接下来要输出的1个字符(或剩余字符,如果不足3个)
|
152 |
+
let chunk = str.slice(outputIndex, Math.min(outputIndex + 10, str.length));
|
153 |
+
// 更新已输出字符的索引
|
154 |
+
outputIndex += chunk.length;
|
155 |
+
if (type === 'thought') {
|
156 |
+
setDraft(str.slice(0, outputIndex));
|
157 |
+
} else if (type === "stepDraft-0") {
|
158 |
+
setProgress1(str.slice(0, outputIndex));
|
159 |
+
} else if (type === "stepDraft-1") {
|
160 |
+
setProgress2(str.slice(0, outputIndex));
|
161 |
+
} else if (type === "conclusion") {
|
162 |
+
setConclusion(str.slice(0, outputIndex));
|
163 |
+
} else if (type === "response") {
|
164 |
+
setResponse(str.slice(0, outputIndex));
|
165 |
+
}
|
166 |
+
} else {
|
167 |
+
// 如果没有更多字符需要输出,则清除定时器
|
168 |
+
clearInterval(intervalId);
|
169 |
+
endCallback && endCallback()
|
170 |
+
}
|
171 |
+
}
|
172 |
+
|
173 |
+
// 设定定时器ID
|
174 |
+
let intervalId = setInterval(outputText, TEXT_INTERVAL);
|
175 |
+
}
|
176 |
+
|
177 |
+
// 渲染搜索结果renderSearchList
|
178 |
+
const renderSearchList = () => {
|
179 |
+
let outputIndex = 0;
|
180 |
+
const content = JSON.parse(currentNode.actions[currentStep].result[0].content);
|
181 |
+
|
182 |
+
const arr: any = Object.keys(content).map(item => {
|
183 |
+
return { id: item, ...content[item] };
|
184 |
+
});
|
185 |
+
const len = Object.keys(content).length;
|
186 |
+
const outputText = () => {
|
187 |
+
outputIndex++;
|
188 |
+
if (outputIndex < len + 1) {
|
189 |
+
setSearchList(arr.slice(0, outputIndex));
|
190 |
+
} else {
|
191 |
+
clearInterval(intervalId);
|
192 |
+
}
|
193 |
+
};
|
194 |
+
// 设定定时器ID
|
195 |
+
let intervalId = setInterval(outputText, SEARCHLIST_INTERVAL);
|
196 |
+
};
|
197 |
+
|
198 |
+
// 高亮searchList
|
199 |
+
const highLightSearchList = (ids: any) => {
|
200 |
+
setSelectedIds([]);
|
201 |
+
const newStep = currentStep + 1;
|
202 |
+
setCurrentStep(newStep);
|
203 |
+
const highlightArr: any = [...searchList];
|
204 |
+
highlightArr.forEach((item: any) => {
|
205 |
+
if (ids.includes(Number(item.id))) {
|
206 |
+
item.highLight = true;
|
207 |
+
}
|
208 |
+
})
|
209 |
+
highlightArr.sort((item1: any, item2: any) => {
|
210 |
+
if (item1.highLight === item2.highLight) {
|
211 |
+
return 0;
|
212 |
+
}
|
213 |
+
// 如果item1是highlight,放在前面
|
214 |
+
if (item1.highLight) {
|
215 |
+
return -1;
|
216 |
+
}
|
217 |
+
// 如果item2是highlight,放在后面
|
218 |
+
return 1;
|
219 |
+
})
|
220 |
+
setSearchList(highlightArr);
|
221 |
+
renderDraft(currentNode.actions[1].thought, `stepDraft-1`, () => { });
|
222 |
+
hasHighlight.current = true; // 标记为高亮已执行
|
223 |
+
};
|
224 |
+
|
225 |
+
// 渲染结论
|
226 |
+
const renderConclusion = () => {
|
227 |
+
const res = window.localStorage.getItem('nodeRes') || '';
|
228 |
+
const replaced = replaceStr(res);
|
229 |
+
// setTimeout(() => { setCollapseInfo([false, false]); }, 2000);
|
230 |
+
setCollapseInfo([false, false]);
|
231 |
+
setConclusion(replaced);
|
232 |
+
setstashConclusion(res);
|
233 |
+
// 给出conclusion结束的条件
|
234 |
+
if (stashConclusion.length + 5 > res.length) {
|
235 |
+
conclusionRender.current = true;
|
236 |
+
setProgressEnd(true);
|
237 |
+
}
|
238 |
+
};
|
239 |
+
|
240 |
+
// 渲染query
|
241 |
+
const renderQuery = (endCallback: () => void) => {
|
242 |
+
const queries = currentNode.actions[currentStep]?.args?.query;
|
243 |
+
setQuery(queries);
|
244 |
+
endCallback && endCallback();
|
245 |
+
};
|
246 |
+
|
247 |
+
const renderSteps = () => {
|
248 |
+
setCurrentNodeRendering(true);
|
249 |
+
const queryEndCallback = () => {
|
250 |
+
if (currentNode.actions[currentStep].result[0].content) {
|
251 |
+
if (currentNode.actions[currentStep].type === "BingBrowser.search" || currentNode.actions[currentStep].type === "BingBrowser") {
|
252 |
+
renderSearchList();
|
253 |
+
}
|
254 |
+
}
|
255 |
+
};
|
256 |
+
const thoughtEndCallback = () => {
|
257 |
+
if (currentNode.actions[currentStep]?.args?.query?.length) {
|
258 |
+
renderQuery(queryEndCallback);
|
259 |
+
} else {
|
260 |
+
queryEndCallback();
|
261 |
+
}
|
262 |
+
};
|
263 |
+
if (currentNode.actions[currentStep].thought) {
|
264 |
+
renderDraft(currentNode.actions[currentStep].thought, `stepDraft-${currentStep}`, thoughtEndCallback);
|
265 |
+
}
|
266 |
+
}
|
267 |
+
|
268 |
+
// 展开收起
|
269 |
+
const toggleCard = (index: number) => {
|
270 |
+
const arr = [...collapseInfo];
|
271 |
+
arr[index] = !arr[index];
|
272 |
+
setCollapseInfo(arr);
|
273 |
+
};
|
274 |
+
|
275 |
+
// 渲染过程中保持渲染文字可见
|
276 |
+
const keepScrollTop = (divA: any, divB: any) => {
|
277 |
+
// 获取 divB 的当前高度
|
278 |
+
const bHeight = divB.offsetHeight;
|
279 |
+
|
280 |
+
// 检查 divA 是否需要滚动(即 divB 的高度是否大于 divA 的可视高度)
|
281 |
+
if (bHeight > divA.offsetHeight) {
|
282 |
+
// 滚动到 divB 的底部在 divA 的可视区域内
|
283 |
+
divA.scrollTop = bHeight - divA.offsetHeight;
|
284 |
+
}
|
285 |
+
};
|
286 |
+
|
287 |
+
useEffect(() => {
|
288 |
+
setRenderData([
|
289 |
+
{
|
290 |
+
id: 0,
|
291 |
+
state: 3,
|
292 |
+
name: '原始问题',
|
293 |
+
children: adjList
|
294 |
+
}
|
295 |
+
])
|
296 |
+
}, [JSON.stringify(adjList)]);
|
297 |
+
|
298 |
+
useEffect(() => {
|
299 |
+
console.log('render data changed-----', renderData);
|
300 |
+
}, [renderData]);
|
301 |
+
|
302 |
+
useEffect(() => {
|
303 |
+
if (currentStep === 1) {
|
304 |
+
setCollapseInfo([false, true]);
|
305 |
+
}
|
306 |
+
}, [currentStep]);
|
307 |
+
|
308 |
+
useEffect(() => {
|
309 |
+
if (nodeOutputEnd && !localStorage.getItem('nodeRes')) {
|
310 |
+
// 如果节点输出结束了,但是response还没有结束,认为节点渲染已结束
|
311 |
+
conclusionRender.current = true;
|
312 |
+
setProgressEnd(true);
|
313 |
+
return;
|
314 |
+
}
|
315 |
+
if (nodeDraftRender.current && localStorage.getItem('nodeRes')) {
|
316 |
+
renderConclusion();
|
317 |
+
}
|
318 |
+
}, [localStorage.getItem('nodeRes'), nodeDraftRender.current, nodeOutputEnd]);
|
319 |
+
|
320 |
+
useEffect(() => {
|
321 |
+
if (obj?.response?.nodes[obj.current_node]?.detail?.state !== 1) {
|
322 |
+
setIsWaiting(true);
|
323 |
+
}
|
324 |
+
if (obj?.response?.nodes?.[obj.current_node].detail?.state === 0 && currentNode?.current_node === obj.current_node) {
|
325 |
+
console.log('node render end-----', obj);
|
326 |
+
setNodeEnd(true);
|
327 |
+
}
|
328 |
+
|
329 |
+
if (obj?.current_node && obj?.response?.state === 3) {
|
330 |
+
// 当node节点的数据可以开始渲染时,给currentnode赋值
|
331 |
+
// update conclusion
|
332 |
+
if (obj.response.nodes[obj.current_node]?.detail?.actions?.length === 2 &&
|
333 |
+
obj.response.nodes[obj.current_node]?.detail?.state === 1 &&
|
334 |
+
obj.response.nodes[obj.current_node]?.detail.response) {
|
335 |
+
window.localStorage.setItem('nodeRes', obj.response.nodes[obj.current_node]?.detail.response);
|
336 |
+
}
|
337 |
+
if (obj.current_node &&
|
338 |
+
(obj.response.nodes[obj.current_node]?.detail?.state === 1) &&
|
339 |
+
obj.response.nodes[obj.current_node]?.detail?.actions?.length &&
|
340 |
+
currentStep === 0 &&
|
341 |
+
currentNode?.current_node !== obj?.current_node
|
342 |
+
) {
|
343 |
+
// 更新当前渲染节点
|
344 |
+
console.log('update current node----');
|
345 |
+
setIsWaiting(false);
|
346 |
+
setCurrentNode({ ...obj.response.nodes[obj.current_node]?.detail, current_node: obj.current_node });
|
347 |
+
}
|
348 |
+
|
349 |
+
// 设置highlight
|
350 |
+
if (!selectedIds.length &&
|
351 |
+
obj.response.nodes[obj.current_node]?.detail?.actions?.[1]?.type === 'BingBrowser.select' &&
|
352 |
+
(obj.response.nodes[obj.current_node]?.detail?.state === 1)) {
|
353 |
+
setSelectedIds(obj.response.nodes[obj.current_node]?.detail?.actions?.[1]?.args?.select_ids || []);
|
354 |
+
setCurrentNode({ ...obj.response.nodes[obj.current_node]?.detail, current_node: obj.current_node });
|
355 |
+
}
|
356 |
+
}
|
357 |
+
}, [obj]);
|
358 |
+
|
359 |
+
useEffect(() => {
|
360 |
+
// 输出思考过程
|
361 |
+
if (!currentNode || currentNodeRendering) { return; }
|
362 |
+
renderSteps();
|
363 |
+
}, [currentNode, currentNodeRendering, selectedIds]);
|
364 |
+
|
365 |
+
useEffect(() => {
|
366 |
+
if (!hasHighlight.current && selectedIds.length && currentNode?.actions.length === 2) {
|
367 |
+
// 渲染高亮的search信息
|
368 |
+
highLightSearchList(selectedIds);
|
369 |
+
}
|
370 |
+
}, [selectedIds, currentNode]);
|
371 |
+
|
372 |
+
useEffect(() => {
|
373 |
+
// 当前节点渲染结束
|
374 |
+
if (nodeName && nodeName !== currentNode?.current_node && progressEnd && !isEnd) {
|
375 |
+
resetNode(nodeName);
|
376 |
+
setMapWidth(generateWidth());
|
377 |
+
}
|
378 |
+
}, [nodeName, currentNode, progressEnd, isEnd]);
|
379 |
+
|
380 |
+
let responseTimer: any = useRef(null);
|
381 |
+
useEffect(() => {
|
382 |
+
if (isEnd) {
|
383 |
+
responseTimer.current = setInterval(() => {
|
384 |
+
const divA = document.getElementById('chatArea') as HTMLDivElement;
|
385 |
+
const divB = document.getElementById('messageWindowId') as HTMLDivElement;
|
386 |
+
keepScrollTop(divA, divB);
|
387 |
+
if (chatIsOver) {
|
388 |
+
clearInterval(responseTimer.current);
|
389 |
+
}
|
390 |
+
}, 500);
|
391 |
+
setTimeout(() => {
|
392 |
+
setShowEndNode(true);
|
393 |
+
}, 300);
|
394 |
+
} else if (responseTimer.current) {
|
395 |
+
// 如果 isEnd 变为 false,清除定时器
|
396 |
+
clearInterval(responseTimer.current);
|
397 |
+
responseTimer.current = null;
|
398 |
+
}
|
399 |
+
|
400 |
+
// 返回清理函数,确保组件卸载时清除定时器
|
401 |
+
return () => {
|
402 |
+
if (responseTimer.current) {
|
403 |
+
clearInterval(responseTimer.current);
|
404 |
+
responseTimer.current = null;
|
405 |
+
}
|
406 |
+
};
|
407 |
+
}, [isEnd, chatIsOver]);
|
408 |
+
|
409 |
+
useEffect(() => {
|
410 |
+
setRenderData([]);
|
411 |
+
setResponse('');
|
412 |
+
setDraft('');
|
413 |
+
setIsEnd(false);
|
414 |
+
setShowRight(true);
|
415 |
+
window.localStorage.setItem('nodeRes', '');
|
416 |
+
window.localStorage.setItem('finishedNodes', '');
|
417 |
+
}, [question]);
|
418 |
+
|
419 |
+
const resetNode = (targetNode: string) => {
|
420 |
+
if (targetNode === 'response') return; // 如果开始response了,所有节点都渲染完了,不需要reset
|
421 |
+
// 渲染下一个节点前,初始化状态
|
422 |
+
const newData = findAndUpdateStatus(renderData, targetNode);
|
423 |
+
console.log('reset node------', targetNode, renderData);
|
424 |
+
setCurrentStep(0);
|
425 |
+
setQuery([]);
|
426 |
+
setSearchList([]);
|
427 |
+
setConclusion('');
|
428 |
+
setCollapseInfo([true, true]);
|
429 |
+
setProgress1('');
|
430 |
+
setProgress2('');
|
431 |
+
setProgressEnd(false);
|
432 |
+
setCurrentNode(null);
|
433 |
+
setCurrentNodeRendering(false);
|
434 |
+
setSelectedIds([]);
|
435 |
+
setNodeEnd(false);
|
436 |
+
hasHighlight.current = false;
|
437 |
+
nodeDraftRender.current = false;
|
438 |
+
conclusionRender.current = false;
|
439 |
+
window.localStorage.setItem('nodeRes', '');
|
440 |
+
};
|
441 |
+
|
442 |
+
const formatData = (data: any) => {
|
443 |
+
try {
|
444 |
+
setIsWaiting(false);
|
445 |
+
const obj = JSON.parse(data);
|
446 |
+
if (!obj.current_node && obj.response.state === 0) {
|
447 |
+
console.log('chat is over end-------');
|
448 |
+
setChatIsOver(true);
|
449 |
+
return;
|
450 |
+
}
|
451 |
+
if (!obj.current_node && obj.response.state === 9) {
|
452 |
+
setShowRight(false);
|
453 |
+
setIsEnd(true);
|
454 |
+
const replaced = replaceStr(obj.response.response);
|
455 |
+
setResponse(replaced);
|
456 |
+
return;
|
457 |
+
}
|
458 |
+
if (!obj.current_node && obj.response.state === 1 && !currentNode) {
|
459 |
+
// 有thought,没有node
|
460 |
+
setDraftEnd(false);
|
461 |
+
setDraft(obj.response.response);
|
462 |
+
}
|
463 |
+
if (!obj.current_node && (obj.response.state !== 1 || obj.response.state !== 0 || obj.response.state !== 9)) {
|
464 |
+
// 有thought,没有node, 不用处理渲染
|
465 |
+
//console.log('loading-------------', obj);
|
466 |
+
setDraftEnd(true);
|
467 |
+
setIsWaiting(true);
|
468 |
+
}
|
469 |
+
if (obj.current_node && obj.response.state === 3) {
|
470 |
+
setNodeName(obj.current_node);
|
471 |
+
// 有node
|
472 |
+
setObj(obj);
|
473 |
+
const newAdjList = obj.response?.adjacency_list;
|
474 |
+
if (newAdjList?.length > 0) {
|
475 |
+
setAdjList(newAdjList);
|
476 |
+
}
|
477 |
+
}
|
478 |
+
} catch (err) {
|
479 |
+
console.log('format error-----', err);
|
480 |
+
}
|
481 |
+
};
|
482 |
+
|
483 |
+
const startEventSource = () => {
|
484 |
+
if (!chatIsOver) {
|
485 |
+
message.warning('有对话进行中!');
|
486 |
+
return;
|
487 |
+
}
|
488 |
+
setQuestion(stashQuestion);
|
489 |
+
setChatIsOver(false);
|
490 |
+
const postData = {
|
491 |
+
inputs: [
|
492 |
+
{
|
493 |
+
role: 'user',
|
494 |
+
content: stashQuestion
|
495 |
+
}
|
496 |
+
]
|
497 |
+
}
|
498 |
+
const ctrl = new AbortController();
|
499 |
+
eventSource = fetchEventSource(GET_SSE_DATA, {
|
500 |
+
method: 'POST',
|
501 |
+
headers: {
|
502 |
+
'Content-Type': 'application/json',
|
503 |
+
},
|
504 |
+
body: JSON.stringify(postData),
|
505 |
+
onmessage(ev) {
|
506 |
+
formatData(ev.data);
|
507 |
+
},
|
508 |
+
onerror(err) {
|
509 |
+
console.log('sse error------', err);
|
510 |
+
},
|
511 |
+
// signal: ctrl.signal,
|
512 |
+
});
|
513 |
+
};
|
514 |
+
|
515 |
+
const abortEventSource = () => {
|
516 |
+
if (eventSource) {
|
517 |
+
eventSource.close(); // 或使用其他方法关闭连接,具体取决于库的API
|
518 |
+
eventSource = null;
|
519 |
+
console.log('EventSource connection aborted due to timeout.');
|
520 |
+
message.error('连接中断,2s后即将刷新页面---');
|
521 |
+
setTimeout(() => {
|
522 |
+
location.reload();
|
523 |
+
}, 2000);
|
524 |
+
}
|
525 |
+
};
|
526 |
+
|
527 |
+
return <div className={styles.mainPage} style={!showRight ? { maxWidth: '1000px' } : {}}>
|
528 |
+
<div className={styles.chatContent}>
|
529 |
+
<div className={styles.top} id="chatArea">
|
530 |
+
<div id="messageWindowId">
|
531 |
+
{
|
532 |
+
question && <div className={styles.question}>
|
533 |
+
<span>{question}</span>
|
534 |
+
</div>
|
535 |
+
}
|
536 |
+
{
|
537 |
+
(draft || response || renderData?.length > 0) &&
|
538 |
+
<div className={styles.answer}>
|
539 |
+
{
|
540 |
+
renderData?.length > 0 ? <div className={styles.inner}>
|
541 |
+
<div className={styles.mapArea}>
|
542 |
+
<ul className={styles.mindmap} id="mindMap" style={isEnd ? { width: mapWidth, overflow: "hidden" } : {}}>
|
543 |
+
{renderData.map((item: any) => (
|
544 |
+
<MindMapItem key={item.name} item={item} isEnd={isEnd} />
|
545 |
+
))}
|
546 |
+
{showEndNode &&
|
547 |
+
<div className={styles.end} style={generateEndStyle()}>
|
548 |
+
<div className={styles.node}>
|
549 |
+
<article>最终回复</article>
|
550 |
+
</div>
|
551 |
+
</div>
|
552 |
+
}
|
553 |
+
</ul>
|
554 |
+
</div>
|
555 |
+
</div> : <></>
|
556 |
+
}
|
557 |
+
{
|
558 |
+
!response && <div className={styles.draft}>
|
559 |
+
{/* {!draftEnd && draft && <div className={styles.loading}>
|
560 |
+
<div></div>
|
561 |
+
<div></div>
|
562 |
+
<div></div>
|
563 |
+
</div>} */}
|
564 |
+
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{replaceStr(draft)}</ReactMarkdown>
|
565 |
+
</div>
|
566 |
+
}
|
567 |
+
{response && <div className={styles.response}>
|
568 |
+
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{response}</ReactMarkdown>
|
569 |
+
</div>}
|
570 |
+
</div>
|
571 |
+
}
|
572 |
+
</div>
|
573 |
+
</div>
|
574 |
+
<div className={styles.sendArea}>
|
575 |
+
<Input type="text" placeholder='说点什么吧~ Shift+Enter 换行 ; Enter 发送' onChange={(e) => { setStashQuestion(e.target.value) }}
|
576 |
+
onPressEnter={startEventSource} />
|
577 |
+
<button onClick={startEventSource}>
|
578 |
+
<img src={SendIcon} />
|
579 |
+
发送
|
580 |
+
</button>
|
581 |
+
</div>
|
582 |
+
<div className={styles.notice}>如果想要更丝滑的体验,请在本地搭建-<a href='https://github.com/InternLM/MindSearch' target='_blank'>MindSearch <IconFont type='icon-GithubFilled' /></a></div>
|
583 |
+
</div>
|
584 |
+
{showRight && <div className={styles.progressContent}>
|
585 |
+
{
|
586 |
+
currentNode && <>
|
587 |
+
<div className={styles.toggleIcon} onClick={toggleRight}>
|
588 |
+
<Tooltip placement="top" title="收起">
|
589 |
+
<img src={PackIcon} />
|
590 |
+
</Tooltip></div>
|
591 |
+
<div className={styles.titleNode}>{currentNode?.content || currentNode?.node}</div>
|
592 |
+
{
|
593 |
+
currentNode?.actions?.length ? <>
|
594 |
+
{
|
595 |
+
currentNode.actions.map((item: any, idx: number) => (
|
596 |
+
currentStep >= idx && <div className={classNames(
|
597 |
+
styles.steps,
|
598 |
+
item.type === "BingBrowser.search" ? styles.thinking : styles.select
|
599 |
+
)} key={`step-${idx}`}>
|
600 |
+
<div className={styles.title}>
|
601 |
+
<i></i>{item.type === "BingBrowser.search" ? "思考" : item.type === "BingBrowser.select" ? "信息来源" : "信息整合"}
|
602 |
+
<div className={styles.open} onClick={() => { toggleCard(idx) }}>
|
603 |
+
<IconFont type={collapseInfo[idx] ? "icon-shouqi" : "icon-xiangxiazhankai"} />
|
604 |
+
</div>
|
605 |
+
</div>
|
606 |
+
<div className={classNames(
|
607 |
+
styles.con,
|
608 |
+
!collapseInfo[idx] ? styles.collapsed : ""
|
609 |
+
)}>
|
610 |
+
{
|
611 |
+
item.type === "BingBrowser.search" && <div className={styles.thought}>
|
612 |
+
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{progress1}</ReactMarkdown>
|
613 |
+
</div>
|
614 |
+
}
|
615 |
+
{
|
616 |
+
item.type === "BingBrowser.search" && query.length > 0 && <div className={styles.query}>
|
617 |
+
<div className={styles.subTitle}><IconFont type="icon-SearchOutlined" />搜索关键词</div>
|
618 |
+
{
|
619 |
+
query.map((item, index) => (<div key={`query-item-${item}`} className={classNames(styles.queryItem, styles.fadeIn)}>
|
620 |
+
{item}
|
621 |
+
</div>))
|
622 |
+
}
|
623 |
+
</div>
|
624 |
+
}
|
625 |
+
{
|
626 |
+
currentStep === idx && searchList.length > 0 && <div className={styles.searchList}>
|
627 |
+
{item.type === "BingBrowser.search" && <div className={styles.subTitle}><IconFont type="icon-DocOutlined" />信息来源</div>}
|
628 |
+
{
|
629 |
+
item.type === "BingBrowser.select" && <div className={styles.thought}>
|
630 |
+
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{progress2}</ReactMarkdown>
|
631 |
+
</div>
|
632 |
+
}
|
633 |
+
<div className={styles.scrollCon} style={(searchList.length > 5 && currentStep === 0) ? { height: '300px' } : {}}>
|
634 |
+
|
635 |
+
<div className={styles.inner} style={(searchList.length > 5 && currentStep === 0) ? { position: 'absolute', bottom: 0, left: 0 } : {}}>
|
636 |
+
|
637 |
+
{
|
638 |
+
searchList.map((item: any, num: number) => (
|
639 |
+
<div className={classNames(
|
640 |
+
styles.searchItem,
|
641 |
+
item.highLight ? styles.highLight : ""
|
642 |
+
)} key={`search-item-${item.url}-${idx}`}>
|
643 |
+
<p className={styles.summ}>{item.id}. {item?.title}</p>
|
644 |
+
<p className={styles.url}>{item?.url}</p>
|
645 |
+
</div>
|
646 |
+
))
|
647 |
+
}
|
648 |
+
</div>
|
649 |
+
</div>
|
650 |
+
</div>
|
651 |
+
}
|
652 |
+
</div>
|
653 |
+
</div>
|
654 |
+
))
|
655 |
+
}
|
656 |
+
</> : <></>
|
657 |
+
}
|
658 |
+
</>
|
659 |
+
}
|
660 |
+
{
|
661 |
+
conclusion && <div className={styles.steps}>
|
662 |
+
<div className={styles.title}>
|
663 |
+
<i></i>信息整合
|
664 |
+
</div>
|
665 |
+
<div className={styles.conclusion}>
|
666 |
+
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{conclusion}</ReactMarkdown>
|
667 |
+
</div>
|
668 |
+
</div>
|
669 |
+
}
|
670 |
+
{isWaiting && question && <div className={styles.loading99}></div>}
|
671 |
+
</div>}
|
672 |
+
{
|
673 |
+
!showRight && <div className={styles.showRight} onClick={toggleRight}>
|
674 |
+
<img src={ShowRightIcon} />
|
675 |
+
</div>
|
676 |
+
}
|
677 |
+
|
678 |
+
</div>
|
679 |
+
};
|
680 |
+
|
681 |
+
export default RenderTest;
|
frontend/React/src/pages/render/mindMapItem.tsx
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import styles from './index.module.less';
|
2 |
+
import classNames from 'classnames';
|
3 |
+
|
4 |
+
// 递归组件用于渲染mindMap中的节点
|
5 |
+
const MindMapItem = ({ item, isEnd }: any) => {
|
6 |
+
// 递归渲染子节点
|
7 |
+
const renderChildren = () => {
|
8 |
+
if (item.children && item.children.length > 0) {
|
9 |
+
return (
|
10 |
+
<ul className={item.children.length === 1 ? styles.onlyone : ''}>
|
11 |
+
{item.children.map((child: any) => (
|
12 |
+
<MindMapItem key={child.name} item={child} isEnd={isEnd} />
|
13 |
+
))}
|
14 |
+
</ul>
|
15 |
+
);
|
16 |
+
}
|
17 |
+
return null;
|
18 |
+
};
|
19 |
+
|
20 |
+
return (
|
21 |
+
<li>
|
22 |
+
<article className={
|
23 |
+
classNames(
|
24 |
+
item.state === 1 ? styles.loading : item.state === 2 ? styles.disabled : item.state === 3 ? styles.finished : "",
|
25 |
+
item.id === 0 ? styles.init : ''
|
26 |
+
)}>
|
27 |
+
<span>{item.name}</span>
|
28 |
+
{item.state === 1 && <div className={styles.looping}></div>}
|
29 |
+
{item.id !== 0 && <div className={styles.finishDot}></div>}
|
30 |
+
</article>
|
31 |
+
{item.children.length > 0 && renderChildren()}
|
32 |
+
{
|
33 |
+
isEnd && item.children?.length === 0 && <div className={classNames(styles.endLine, "endline")}></div>
|
34 |
+
}
|
35 |
+
</li>
|
36 |
+
);
|
37 |
+
};
|
38 |
+
|
39 |
+
export default MindMapItem;
|
frontend/React/src/routes/routes.tsx
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import RenderTest from "@/pages/render";
|
2 |
+
|
3 |
+
import { ReactElement } from "react";
|
4 |
+
import { Navigate, useRoutes } from "react-router-dom";
|
5 |
+
|
6 |
+
interface RouteItem {
|
7 |
+
path: string;
|
8 |
+
needLogin?: boolean;
|
9 |
+
element: ReactElement;
|
10 |
+
}
|
11 |
+
|
12 |
+
const routes: RouteItem[] = [
|
13 |
+
{
|
14 |
+
path: "/",
|
15 |
+
needLogin: false,
|
16 |
+
element: <RenderTest />,
|
17 |
+
},
|
18 |
+
{
|
19 |
+
path: "*",
|
20 |
+
element: <Navigate to="/" />,
|
21 |
+
},
|
22 |
+
];
|
23 |
+
|
24 |
+
const WrapperRoutes = () => {
|
25 |
+
return useRoutes(
|
26 |
+
routes.map((item: RouteItem) => {
|
27 |
+
if (item.needLogin) {
|
28 |
+
return {
|
29 |
+
...item,
|
30 |
+
element: <></>,
|
31 |
+
};
|
32 |
+
}
|
33 |
+
return item;
|
34 |
+
}),
|
35 |
+
);
|
36 |
+
};
|
37 |
+
|
38 |
+
export default WrapperRoutes;
|
frontend/React/src/utils/tools.ts
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export const getQueryString = (search: string, name: string) => {
|
2 |
+
if (!search) return "";
|
3 |
+
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
4 |
+
const result = search.substring(1).match(reg);
|
5 |
+
if (result != null) return result[2];
|
6 |
+
return "";
|
7 |
+
};
|
8 |
+
|
9 |
+
export const isInWhiteList = (url: string = "", list: string[] = []) => {
|
10 |
+
const baseUrl = url.split("?")[0];
|
11 |
+
for (let whiteApi of list) {
|
12 |
+
if (baseUrl.endsWith(whiteApi)) {
|
13 |
+
return true;
|
14 |
+
}
|
15 |
+
}
|
16 |
+
return false;
|
17 |
+
};
|
18 |
+
|
19 |
+
export const replaceStr = (str: string) => {
|
20 |
+
return str.replace(/\[\[(\d+)\]\]/g, (match: any, number: any) => {
|
21 |
+
// 创建一个带有class为'fnn2'的span元素,并将数字作为文本内容
|
22 |
+
return `<i class='f'>${number}</i>`;
|
23 |
+
});
|
24 |
+
};
|
frontend/React/src/vite-env.d.ts
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference types="vite/client" />
|
2 |
+
|
3 |
+
interface ImportMetaEnv {
|
4 |
+
readonly VITE_SSO_URL: string;
|
5 |
+
}
|
6 |
+
|
7 |
+
interface ImportMeta {
|
8 |
+
readonly env: ImportMetaEnv;
|
9 |
+
}
|
frontend/React/tsconfig.json
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "ES5",
|
4 |
+
"useDefineForClassFields": true,
|
5 |
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
6 |
+
"allowJs": false,
|
7 |
+
"skipLibCheck": true,
|
8 |
+
"esModuleInterop": false,
|
9 |
+
"allowSyntheticDefaultImports": true,
|
10 |
+
"strict": true,
|
11 |
+
"forceConsistentCasingInFileNames": true,
|
12 |
+
"module": "ESNext",
|
13 |
+
"moduleResolution": "Node",
|
14 |
+
"resolveJsonModule": true,
|
15 |
+
"isolatedModules": true,
|
16 |
+
"noEmit": true,
|
17 |
+
"jsx": "react-jsx",
|
18 |
+
"baseUrl": "./",
|
19 |
+
"paths": {
|
20 |
+
"@/*": ["src/*"]
|
21 |
+
}
|
22 |
+
},
|
23 |
+
"include": ["src"]
|
24 |
+
}
|
frontend/React/vite.config.ts
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { defineConfig } from "vite";
|
2 |
+
import react from "@vitejs/plugin-react";
|
3 |
+
import path from "path";
|
4 |
+
import legacy from "@vitejs/plugin-legacy";
|
5 |
+
|
6 |
+
// https://vitejs.dev/config/
|
7 |
+
export default defineConfig({
|
8 |
+
plugins: [
|
9 |
+
react({
|
10 |
+
babel: {
|
11 |
+
plugins: [
|
12 |
+
"@babel/plugin-proposal-optional-chaining", // 兼容老版本浏览器的语法解译
|
13 |
+
],
|
14 |
+
},
|
15 |
+
}),
|
16 |
+
legacy({
|
17 |
+
targets: ["defaults", "ie >= 11", "chrome >= 52"], //需要兼容的目标列表,可以设置多个
|
18 |
+
additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
|
19 |
+
renderLegacyChunks: true,
|
20 |
+
polyfills: [
|
21 |
+
"es.symbol",
|
22 |
+
"es.array.filter",
|
23 |
+
"es.promise",
|
24 |
+
"es.promise.finally",
|
25 |
+
"es/map",
|
26 |
+
"es/set",
|
27 |
+
"es.array.for-each",
|
28 |
+
"es.object.define-properties",
|
29 |
+
"es.object.define-property",
|
30 |
+
"es.object.get-own-property-descriptor",
|
31 |
+
"es.object.get-own-property-descriptors",
|
32 |
+
"es.object.keys",
|
33 |
+
"es.object.to-string",
|
34 |
+
"web.dom-collections.for-each",
|
35 |
+
"esnext.global-this",
|
36 |
+
"esnext.string.match-all",
|
37 |
+
],
|
38 |
+
}),
|
39 |
+
],
|
40 |
+
build: {
|
41 |
+
target: "es5",
|
42 |
+
},
|
43 |
+
resolve: {
|
44 |
+
alias: {
|
45 |
+
"@": path.resolve(__dirname, "src"),
|
46 |
+
},
|
47 |
+
},
|
48 |
+
css: {
|
49 |
+
modules: {
|
50 |
+
localsConvention: "camelCase",
|
51 |
+
},
|
52 |
+
},
|
53 |
+
server: {
|
54 |
+
port: 8080,
|
55 |
+
proxy: {
|
56 |
+
// "/solve": {
|
57 |
+
// target: "...",
|
58 |
+
// changeOrigin: true,
|
59 |
+
// },
|
60 |
+
},
|
61 |
+
},
|
62 |
+
});
|
frontend/React/windows-.png
ADDED
frontend/mindsearch_gradio.py
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
import gradio as gr
|
4 |
+
import requests
|
5 |
+
from lagent.schema import AgentStatusCode
|
6 |
+
|
7 |
+
PLANNER_HISTORY = []
|
8 |
+
SEARCHER_HISTORY = []
|
9 |
+
|
10 |
+
|
11 |
+
def rst_mem(history_planner: list, history_searcher: list):
|
12 |
+
'''
|
13 |
+
Reset the chatbot memory.
|
14 |
+
'''
|
15 |
+
history_planner = []
|
16 |
+
history_searcher = []
|
17 |
+
if PLANNER_HISTORY:
|
18 |
+
PLANNER_HISTORY.clear()
|
19 |
+
return history_planner, history_searcher
|
20 |
+
|
21 |
+
|
22 |
+
def format_response(gr_history, agent_return):
|
23 |
+
if agent_return['state'] in [
|
24 |
+
AgentStatusCode.STREAM_ING, AgentStatusCode.ANSWER_ING
|
25 |
+
]:
|
26 |
+
gr_history[-1][1] = agent_return['response']
|
27 |
+
elif agent_return['state'] == AgentStatusCode.PLUGIN_START:
|
28 |
+
thought = gr_history[-1][1].split('```')[0]
|
29 |
+
if agent_return['response'].startswith('```'):
|
30 |
+
gr_history[-1][1] = thought + '\n' + agent_return['response']
|
31 |
+
elif agent_return['state'] == AgentStatusCode.PLUGIN_END:
|
32 |
+
thought = gr_history[-1][1].split('```')[0]
|
33 |
+
if isinstance(agent_return['response'], dict):
|
34 |
+
gr_history[-1][
|
35 |
+
1] = thought + '\n' + f'```json\n{json.dumps(agent_return["response"], ensure_ascii=False, indent=4)}\n```' # noqa: E501
|
36 |
+
elif agent_return['state'] == AgentStatusCode.PLUGIN_RETURN:
|
37 |
+
assert agent_return['inner_steps'][-1]['role'] == 'environment'
|
38 |
+
item = agent_return['inner_steps'][-1]
|
39 |
+
gr_history.append([
|
40 |
+
None,
|
41 |
+
f"```json\n{json.dumps(item['content'], ensure_ascii=False, indent=4)}\n```"
|
42 |
+
])
|
43 |
+
gr_history.append([None, ''])
|
44 |
+
return
|
45 |
+
|
46 |
+
|
47 |
+
def predict(history_planner, history_searcher):
|
48 |
+
|
49 |
+
def streaming(raw_response):
|
50 |
+
for chunk in raw_response.iter_lines(chunk_size=8192,
|
51 |
+
decode_unicode=False,
|
52 |
+
delimiter=b'\n'):
|
53 |
+
if chunk:
|
54 |
+
decoded = chunk.decode('utf-8')
|
55 |
+
if decoded == '\r':
|
56 |
+
continue
|
57 |
+
if decoded[:6] == 'data: ':
|
58 |
+
decoded = decoded[6:]
|
59 |
+
elif decoded.startswith(': ping - '):
|
60 |
+
continue
|
61 |
+
response = json.loads(decoded)
|
62 |
+
yield (response['response'], response['current_node'])
|
63 |
+
|
64 |
+
global PLANNER_HISTORY
|
65 |
+
PLANNER_HISTORY.append(dict(role='user', content=history_planner[-1][0]))
|
66 |
+
new_search_turn = True
|
67 |
+
|
68 |
+
url = 'http://localhost:8002/solve'
|
69 |
+
headers = {'Content-Type': 'application/json'}
|
70 |
+
data = {'inputs': PLANNER_HISTORY}
|
71 |
+
raw_response = requests.post(url,
|
72 |
+
headers=headers,
|
73 |
+
data=json.dumps(data),
|
74 |
+
timeout=20,
|
75 |
+
stream=True)
|
76 |
+
|
77 |
+
for resp in streaming(raw_response):
|
78 |
+
agent_return, node_name = resp
|
79 |
+
if node_name:
|
80 |
+
if node_name in ['root', 'response']:
|
81 |
+
continue
|
82 |
+
agent_return = agent_return['nodes'][node_name]['detail']
|
83 |
+
if new_search_turn:
|
84 |
+
history_searcher.append([agent_return['content'], ''])
|
85 |
+
new_search_turn = False
|
86 |
+
format_response(history_searcher, agent_return)
|
87 |
+
if agent_return['state'] == AgentStatusCode.END:
|
88 |
+
new_search_turn = True
|
89 |
+
yield history_planner, history_searcher
|
90 |
+
else:
|
91 |
+
new_search_turn = True
|
92 |
+
format_response(history_planner, agent_return)
|
93 |
+
if agent_return['state'] == AgentStatusCode.END:
|
94 |
+
PLANNER_HISTORY = agent_return['inner_steps']
|
95 |
+
yield history_planner, history_searcher
|
96 |
+
return history_planner, history_searcher
|
97 |
+
|
98 |
+
|
99 |
+
with gr.Blocks() as demo:
|
100 |
+
gr.HTML("""<h1 align="center">WebAgent Gradio Simple Demo</h1>""")
|
101 |
+
with gr.Row():
|
102 |
+
with gr.Column(scale=10):
|
103 |
+
with gr.Row():
|
104 |
+
with gr.Column():
|
105 |
+
planner = gr.Chatbot(label='planner',
|
106 |
+
height=700,
|
107 |
+
show_label=True,
|
108 |
+
show_copy_button=True,
|
109 |
+
bubble_full_width=False,
|
110 |
+
render_markdown=True)
|
111 |
+
with gr.Column():
|
112 |
+
searcher = gr.Chatbot(label='searcher',
|
113 |
+
height=700,
|
114 |
+
show_label=True,
|
115 |
+
show_copy_button=True,
|
116 |
+
bubble_full_width=False,
|
117 |
+
render_markdown=True)
|
118 |
+
with gr.Row():
|
119 |
+
user_input = gr.Textbox(show_label=False,
|
120 |
+
placeholder='inputs...',
|
121 |
+
lines=5,
|
122 |
+
container=False)
|
123 |
+
with gr.Row():
|
124 |
+
with gr.Column(scale=2):
|
125 |
+
submitBtn = gr.Button('Submit')
|
126 |
+
with gr.Column(scale=1, min_width=20):
|
127 |
+
emptyBtn = gr.Button('Clear History')
|
128 |
+
|
129 |
+
def user(query, history):
|
130 |
+
return '', history + [[query, '']]
|
131 |
+
|
132 |
+
submitBtn.click(user, [user_input, planner], [user_input, planner],
|
133 |
+
queue=False).then(predict, [planner, searcher],
|
134 |
+
[planner, searcher])
|
135 |
+
emptyBtn.click(rst_mem, [planner, searcher], [planner, searcher],
|
136 |
+
queue=False)
|
137 |
+
|
138 |
+
demo.queue()
|
139 |
+
demo.launch(server_name='127.0.0.1',
|
140 |
+
server_port=7882,
|
141 |
+
inbrowser=True,
|
142 |
+
share=True)
|
frontend/mindsearch_streamlit.py
ADDED
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import tempfile
|
3 |
+
|
4 |
+
import requests
|
5 |
+
import streamlit as st
|
6 |
+
from lagent.schema import AgentStatusCode
|
7 |
+
from pyvis.network import Network
|
8 |
+
|
9 |
+
|
10 |
+
# Function to create the network graph
|
11 |
+
def create_network_graph(nodes, adjacency_list):
|
12 |
+
net = Network(height='500px',
|
13 |
+
width='60%',
|
14 |
+
bgcolor='white',
|
15 |
+
font_color='black')
|
16 |
+
for node_id, node_data in nodes.items():
|
17 |
+
if node_id in ['root', 'response']:
|
18 |
+
title = node_data.get('content', node_id)
|
19 |
+
else:
|
20 |
+
title = node_data['detail']['content']
|
21 |
+
net.add_node(node_id,
|
22 |
+
label=node_id,
|
23 |
+
title=title,
|
24 |
+
color='#FF5733',
|
25 |
+
size=25)
|
26 |
+
for node_id, neighbors in adjacency_list.items():
|
27 |
+
for neighbor in neighbors:
|
28 |
+
if neighbor['name'] in nodes:
|
29 |
+
net.add_edge(node_id, neighbor['name'])
|
30 |
+
net.show_buttons(filter_=['physics'])
|
31 |
+
return net
|
32 |
+
|
33 |
+
|
34 |
+
# Function to draw the graph and return the HTML file path
|
35 |
+
def draw_graph(net):
|
36 |
+
path = tempfile.mktemp(suffix='.html')
|
37 |
+
net.save_graph(path)
|
38 |
+
return path
|
39 |
+
|
40 |
+
|
41 |
+
def streaming(raw_response):
|
42 |
+
for chunk in raw_response.iter_lines(chunk_size=8192,
|
43 |
+
decode_unicode=False,
|
44 |
+
delimiter=b'\n'):
|
45 |
+
if chunk:
|
46 |
+
decoded = chunk.decode('utf-8')
|
47 |
+
if decoded == '\r':
|
48 |
+
continue
|
49 |
+
if decoded[:6] == 'data: ':
|
50 |
+
decoded = decoded[6:]
|
51 |
+
elif decoded.startswith(': ping - '):
|
52 |
+
continue
|
53 |
+
response = json.loads(decoded)
|
54 |
+
yield (response['response'], response['current_node'])
|
55 |
+
|
56 |
+
|
57 |
+
# Initialize Streamlit session state
|
58 |
+
if 'queries' not in st.session_state:
|
59 |
+
st.session_state['queries'] = []
|
60 |
+
st.session_state['responses'] = []
|
61 |
+
st.session_state['graphs_html'] = []
|
62 |
+
st.session_state['nodes_list'] = []
|
63 |
+
st.session_state['adjacency_list_list'] = []
|
64 |
+
st.session_state['history'] = []
|
65 |
+
st.session_state['already_used_keys'] = list()
|
66 |
+
|
67 |
+
# Set up page layout
|
68 |
+
st.set_page_config(layout='wide')
|
69 |
+
st.title('MindSearch-思索')
|
70 |
+
|
71 |
+
|
72 |
+
# Function to update chat
|
73 |
+
def update_chat(query):
|
74 |
+
with st.chat_message('user'):
|
75 |
+
st.write(query)
|
76 |
+
if query not in st.session_state['queries']:
|
77 |
+
# Mock data to simulate backend response
|
78 |
+
# response, history, nodes, adjacency_list
|
79 |
+
st.session_state['queries'].append(query)
|
80 |
+
st.session_state['responses'].append([])
|
81 |
+
history = None
|
82 |
+
# 暂不支持多轮
|
83 |
+
message = [dict(role='user', content=query)]
|
84 |
+
|
85 |
+
url = 'http://localhost:8002/solve'
|
86 |
+
headers = {'Content-Type': 'application/json'}
|
87 |
+
data = {'inputs': message}
|
88 |
+
raw_response = requests.post(url,
|
89 |
+
headers=headers,
|
90 |
+
data=json.dumps(data),
|
91 |
+
timeout=20,
|
92 |
+
stream=True)
|
93 |
+
|
94 |
+
for resp in streaming(raw_response):
|
95 |
+
agent_return, node_name = resp
|
96 |
+
if node_name and node_name in ['root', 'response']:
|
97 |
+
continue
|
98 |
+
nodes = agent_return['nodes']
|
99 |
+
adjacency_list = agent_return['adj']
|
100 |
+
response = agent_return['response']
|
101 |
+
history = agent_return['inner_steps']
|
102 |
+
if nodes:
|
103 |
+
net = create_network_graph(nodes, adjacency_list)
|
104 |
+
graph_html_path = draw_graph(net)
|
105 |
+
with open(graph_html_path, encoding='utf-8') as f:
|
106 |
+
graph_html = f.read()
|
107 |
+
else:
|
108 |
+
graph_html = None
|
109 |
+
if 'graph_placeholder' not in st.session_state:
|
110 |
+
st.session_state['graph_placeholder'] = st.empty()
|
111 |
+
if 'expander_placeholder' not in st.session_state:
|
112 |
+
st.session_state['expander_placeholder'] = st.empty()
|
113 |
+
if graph_html:
|
114 |
+
with st.session_state['expander_placeholder'].expander(
|
115 |
+
'Show Graph', expanded=False):
|
116 |
+
st.session_state['graph_placeholder']._html(graph_html,
|
117 |
+
height=500)
|
118 |
+
if 'container_placeholder' not in st.session_state:
|
119 |
+
st.session_state['container_placeholder'] = st.empty()
|
120 |
+
with st.session_state['container_placeholder'].container():
|
121 |
+
if 'columns_placeholder' not in st.session_state:
|
122 |
+
st.session_state['columns_placeholder'] = st.empty()
|
123 |
+
col1, col2 = st.session_state['columns_placeholder'].columns(
|
124 |
+
[2, 1])
|
125 |
+
with col1:
|
126 |
+
if 'planner_placeholder' not in st.session_state:
|
127 |
+
st.session_state['planner_placeholder'] = st.empty()
|
128 |
+
if 'session_info_temp' not in st.session_state:
|
129 |
+
st.session_state['session_info_temp'] = ''
|
130 |
+
if not node_name:
|
131 |
+
if agent_return['state'] in [
|
132 |
+
AgentStatusCode.STREAM_ING,
|
133 |
+
AgentStatusCode.ANSWER_ING
|
134 |
+
]:
|
135 |
+
st.session_state['session_info_temp'] = response
|
136 |
+
elif agent_return[
|
137 |
+
'state'] == AgentStatusCode.PLUGIN_START:
|
138 |
+
thought = st.session_state[
|
139 |
+
'session_info_temp'].split('```')[0]
|
140 |
+
if agent_return['response'].startswith('```'):
|
141 |
+
st.session_state[
|
142 |
+
'session_info_temp'] = thought + '\n' + response
|
143 |
+
elif agent_return[
|
144 |
+
'state'] == AgentStatusCode.PLUGIN_RETURN:
|
145 |
+
assert agent_return['inner_steps'][-1][
|
146 |
+
'role'] == 'environment'
|
147 |
+
st.session_state[
|
148 |
+
'session_info_temp'] += '\n' + agent_return[
|
149 |
+
'inner_steps'][-1]['content']
|
150 |
+
st.session_state['planner_placeholder'].markdown(
|
151 |
+
st.session_state['session_info_temp'])
|
152 |
+
if agent_return[
|
153 |
+
'state'] == AgentStatusCode.PLUGIN_RETURN:
|
154 |
+
st.session_state['responses'][-1].append(
|
155 |
+
st.session_state['session_info_temp'])
|
156 |
+
st.session_state['session_info_temp'] = ''
|
157 |
+
else:
|
158 |
+
st.session_state['planner_placeholder'].markdown(
|
159 |
+
st.session_state['responses'][-1][-1] if
|
160 |
+
not st.session_state['session_info_temp'] else st.
|
161 |
+
session_state['session_info_temp'])
|
162 |
+
with col2:
|
163 |
+
if 'selectbox_placeholder' not in st.session_state:
|
164 |
+
st.session_state['selectbox_placeholder'] = st.empty()
|
165 |
+
if 'searcher_placeholder' not in st.session_state:
|
166 |
+
st.session_state['searcher_placeholder'] = st.empty()
|
167 |
+
# st.session_state['searcher_placeholder'].markdown('')
|
168 |
+
if node_name:
|
169 |
+
selected_node_key = f"selected_node_{len(st.session_state['queries'])}_{node_name}"
|
170 |
+
if selected_node_key not in st.session_state:
|
171 |
+
st.session_state[selected_node_key] = node_name
|
172 |
+
if selected_node_key not in st.session_state[
|
173 |
+
'already_used_keys']:
|
174 |
+
selected_node = st.session_state[
|
175 |
+
'selectbox_placeholder'].selectbox(
|
176 |
+
'Select a node:',
|
177 |
+
list(nodes.keys()),
|
178 |
+
key=f'key_{selected_node_key}',
|
179 |
+
index=list(nodes.keys()).index(node_name))
|
180 |
+
st.session_state['already_used_keys'].append(
|
181 |
+
selected_node_key)
|
182 |
+
else:
|
183 |
+
selected_node = node_name
|
184 |
+
st.session_state[selected_node_key] = selected_node
|
185 |
+
if selected_node in nodes:
|
186 |
+
node = nodes[selected_node]
|
187 |
+
agent_return = node['detail']
|
188 |
+
node_info_key = f'{selected_node}_info'
|
189 |
+
if 'node_info_temp' not in st.session_state:
|
190 |
+
st.session_state[
|
191 |
+
'node_info_temp'] = f'### {agent_return["content"]}'
|
192 |
+
if node_info_key not in st.session_state:
|
193 |
+
st.session_state[node_info_key] = []
|
194 |
+
if agent_return['state'] in [
|
195 |
+
AgentStatusCode.STREAM_ING,
|
196 |
+
AgentStatusCode.ANSWER_ING
|
197 |
+
]:
|
198 |
+
st.session_state[
|
199 |
+
'node_info_temp'] = agent_return[
|
200 |
+
'response']
|
201 |
+
elif agent_return[
|
202 |
+
'state'] == AgentStatusCode.PLUGIN_START:
|
203 |
+
thought = st.session_state[
|
204 |
+
'node_info_temp'].split('```')[0]
|
205 |
+
if agent_return['response'].startswith('```'):
|
206 |
+
st.session_state[
|
207 |
+
'node_info_temp'] = thought + '\n' + agent_return[
|
208 |
+
'response']
|
209 |
+
elif agent_return[
|
210 |
+
'state'] == AgentStatusCode.PLUGIN_END:
|
211 |
+
thought = st.session_state[
|
212 |
+
'node_info_temp'].split('```')[0]
|
213 |
+
if isinstance(agent_return['response'], dict):
|
214 |
+
st.session_state[
|
215 |
+
'node_info_temp'] = thought + '\n' + f'```json\n{json.dumps(agent_return["response"], ensure_ascii=False, indent=4)}\n```' # noqa: E501
|
216 |
+
elif agent_return[
|
217 |
+
'state'] == AgentStatusCode.PLUGIN_RETURN:
|
218 |
+
assert agent_return['inner_steps'][-1][
|
219 |
+
'role'] == 'environment'
|
220 |
+
st.session_state[node_info_key].append(
|
221 |
+
('thought',
|
222 |
+
st.session_state['node_info_temp']))
|
223 |
+
st.session_state[node_info_key].append(
|
224 |
+
('observation',
|
225 |
+
agent_return['inner_steps'][-1]['content']
|
226 |
+
))
|
227 |
+
st.session_state['searcher_placeholder'].markdown(
|
228 |
+
st.session_state['node_info_temp'])
|
229 |
+
if agent_return['state'] == AgentStatusCode.END:
|
230 |
+
st.session_state[node_info_key].append(
|
231 |
+
('answer',
|
232 |
+
st.session_state['node_info_temp']))
|
233 |
+
st.session_state['node_info_temp'] = ''
|
234 |
+
if st.session_state['session_info_temp']:
|
235 |
+
st.session_state['responses'][-1].append(
|
236 |
+
st.session_state['session_info_temp'])
|
237 |
+
st.session_state['session_info_temp'] = ''
|
238 |
+
# st.session_state['responses'][-1] = '\n'.join(st.session_state['responses'][-1])
|
239 |
+
st.session_state['graphs_html'].append(graph_html)
|
240 |
+
st.session_state['nodes_list'].append(nodes)
|
241 |
+
st.session_state['adjacency_list_list'].append(adjacency_list)
|
242 |
+
st.session_state['history'] = history
|
243 |
+
|
244 |
+
|
245 |
+
def display_chat_history():
|
246 |
+
for i, query in enumerate(st.session_state['queries'][-1:]):
|
247 |
+
# with st.chat_message('assistant'):
|
248 |
+
if st.session_state['graphs_html'][i]:
|
249 |
+
with st.session_state['expander_placeholder'].expander(
|
250 |
+
'Show Graph', expanded=False):
|
251 |
+
st.session_state['graph_placeholder']._html(
|
252 |
+
st.session_state['graphs_html'][i], height=500)
|
253 |
+
with st.session_state['container_placeholder'].container():
|
254 |
+
col1, col2 = st.session_state['columns_placeholder'].columns(
|
255 |
+
[2, 1])
|
256 |
+
with col1:
|
257 |
+
st.session_state['planner_placeholder'].markdown(
|
258 |
+
st.session_state['responses'][-1][-1])
|
259 |
+
with col2:
|
260 |
+
selected_node_key = st.session_state['already_used_keys'][
|
261 |
+
-1]
|
262 |
+
st.session_state['selectbox_placeholder'] = st.empty()
|
263 |
+
selected_node = st.session_state[
|
264 |
+
'selectbox_placeholder'].selectbox(
|
265 |
+
'Select a node:',
|
266 |
+
list(st.session_state['nodes_list'][i].keys()),
|
267 |
+
key=f'replay_key_{i}',
|
268 |
+
index=list(st.session_state['nodes_list'][i].keys(
|
269 |
+
)).index(st.session_state[selected_node_key]))
|
270 |
+
st.session_state[selected_node_key] = selected_node
|
271 |
+
if selected_node not in [
|
272 |
+
'root', 'response'
|
273 |
+
] and selected_node in st.session_state['nodes_list'][i]:
|
274 |
+
node_info_key = f'{selected_node}_info'
|
275 |
+
for item in st.session_state[node_info_key]:
|
276 |
+
if item[0] in ['thought', 'answer']:
|
277 |
+
st.session_state[
|
278 |
+
'searcher_placeholder'] = st.empty()
|
279 |
+
st.session_state[
|
280 |
+
'searcher_placeholder'].markdown(item[1])
|
281 |
+
elif item[0] == 'observation':
|
282 |
+
st.session_state[
|
283 |
+
'observation_expander'] = st.empty()
|
284 |
+
with st.session_state[
|
285 |
+
'observation_expander'].expander(
|
286 |
+
'Results'):
|
287 |
+
st.write(item[1])
|
288 |
+
# st.session_state['searcher_placeholder'].markdown(st.session_state[node_info_key])
|
289 |
+
|
290 |
+
|
291 |
+
def clean_history():
|
292 |
+
st.session_state['queries'] = []
|
293 |
+
st.session_state['responses'] = []
|
294 |
+
st.session_state['graphs_html'] = []
|
295 |
+
st.session_state['nodes_list'] = []
|
296 |
+
st.session_state['adjacency_list_list'] = []
|
297 |
+
st.session_state['history'] = []
|
298 |
+
st.session_state['already_used_keys'] = list()
|
299 |
+
for k in st.session_state:
|
300 |
+
if k.endswith('placeholder') or k.endswith('_info'):
|
301 |
+
del st.session_state[k]
|
302 |
+
|
303 |
+
|
304 |
+
# Main function to run the Streamlit app
|
305 |
+
def main():
|
306 |
+
st.sidebar.title('Model Control')
|
307 |
+
col1, col2 = st.columns([4, 1])
|
308 |
+
with col1:
|
309 |
+
user_input = st.chat_input('Enter your query:')
|
310 |
+
with col2:
|
311 |
+
if st.button('Clear History'):
|
312 |
+
clean_history()
|
313 |
+
if user_input:
|
314 |
+
update_chat(user_input)
|
315 |
+
display_chat_history()
|
316 |
+
|
317 |
+
|
318 |
+
if __name__ == '__main__':
|
319 |
+
main()
|
mindsearch/agent/__init__.py
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from datetime import datetime
|
3 |
+
|
4 |
+
from lagent.actions import ActionExecutor, BingBrowser
|
5 |
+
|
6 |
+
import mindsearch.agent.models as llm_factory
|
7 |
+
from mindsearch.agent.mindsearch_agent import (MindSearchAgent,
|
8 |
+
MindSearchProtocol)
|
9 |
+
from mindsearch.agent.mindsearch_prompt import (
|
10 |
+
FINAL_RESPONSE_CN, FINAL_RESPONSE_EN, GRAPH_PROMPT_CN, GRAPH_PROMPT_EN,
|
11 |
+
searcher_context_template_cn, searcher_context_template_en,
|
12 |
+
searcher_input_template_cn, searcher_input_template_en,
|
13 |
+
searcher_system_prompt_cn, searcher_system_prompt_en)
|
14 |
+
|
15 |
+
LLM = {}
|
16 |
+
|
17 |
+
|
18 |
+
def init_agent(lang='cn', model_format='internlm_server'):
|
19 |
+
llm = LLM.get(model_format, None)
|
20 |
+
if llm is None:
|
21 |
+
llm_cfg = getattr(llm_factory, model_format)
|
22 |
+
if llm_cfg is None:
|
23 |
+
raise NotImplementedError
|
24 |
+
llm_cfg = llm_cfg.copy()
|
25 |
+
llm = llm_cfg.pop('type')(**llm_cfg)
|
26 |
+
LLM[model_format] = llm
|
27 |
+
|
28 |
+
agent = MindSearchAgent(
|
29 |
+
llm=llm,
|
30 |
+
protocol=MindSearchProtocol(meta_prompt=datetime.now().strftime(
|
31 |
+
'The current date is %Y-%m-%d.'),
|
32 |
+
interpreter_prompt=GRAPH_PROMPT_CN
|
33 |
+
if lang == 'cn' else GRAPH_PROMPT_EN,
|
34 |
+
response_prompt=FINAL_RESPONSE_CN
|
35 |
+
if lang == 'cn' else FINAL_RESPONSE_EN),
|
36 |
+
searcher_cfg=dict(
|
37 |
+
llm=llm,
|
38 |
+
plugin_executor=ActionExecutor(
|
39 |
+
BingBrowser(searcher_type='DuckDuckGoSearch',
|
40 |
+
topk=6,
|
41 |
+
api_key=os.environ.get('BING_API_KEY',
|
42 |
+
'YOUR BING API'))),
|
43 |
+
protocol=MindSearchProtocol(
|
44 |
+
meta_prompt=datetime.now().strftime(
|
45 |
+
'The current date is %Y-%m-%d.'),
|
46 |
+
plugin_prompt=searcher_system_prompt_cn
|
47 |
+
if lang == 'cn' else searcher_system_prompt_en,
|
48 |
+
),
|
49 |
+
template=dict(input=searcher_input_template_cn
|
50 |
+
if lang == 'cn' else searcher_input_template_en,
|
51 |
+
context=searcher_context_template_cn
|
52 |
+
if lang == 'cn' else searcher_context_template_en)),
|
53 |
+
max_turn=10)
|
54 |
+
return agent
|
mindsearch/agent/mindsearch_agent.py
ADDED
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import logging
|
3 |
+
import queue
|
4 |
+
import random
|
5 |
+
import re
|
6 |
+
import threading
|
7 |
+
import uuid
|
8 |
+
from collections import defaultdict
|
9 |
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
10 |
+
from copy import deepcopy
|
11 |
+
from dataclasses import asdict
|
12 |
+
from typing import Dict, List, Optional
|
13 |
+
|
14 |
+
from lagent.actions import ActionExecutor
|
15 |
+
from lagent.agents import BaseAgent, Internlm2Agent
|
16 |
+
from lagent.agents.internlm2_agent import Internlm2Protocol
|
17 |
+
from lagent.schema import AgentReturn, AgentStatusCode, ModelStatusCode
|
18 |
+
from termcolor import colored
|
19 |
+
|
20 |
+
# 初始化日志记录
|
21 |
+
logging.basicConfig(level=logging.INFO)
|
22 |
+
logger = logging.getLogger(__name__)
|
23 |
+
|
24 |
+
|
25 |
+
class SearcherAgent(Internlm2Agent):
|
26 |
+
|
27 |
+
def __init__(self, template='{query}', **kwargs) -> None:
|
28 |
+
super().__init__(**kwargs)
|
29 |
+
self.template = template
|
30 |
+
|
31 |
+
def stream_chat(self,
|
32 |
+
question: str,
|
33 |
+
root_question: str = None,
|
34 |
+
parent_response: List[dict] = None,
|
35 |
+
**kwargs) -> AgentReturn:
|
36 |
+
message = self.template['input'].format(question=question,
|
37 |
+
topic=root_question)
|
38 |
+
if parent_response:
|
39 |
+
if 'context' in self.template:
|
40 |
+
parent_response = [
|
41 |
+
self.template['context'].format(**item)
|
42 |
+
for item in parent_response
|
43 |
+
]
|
44 |
+
message = '\n'.join(parent_response + [message])
|
45 |
+
print(colored(f'current query: {message}', 'green'))
|
46 |
+
for agent_return in super().stream_chat(message,
|
47 |
+
session_id=random.randint(
|
48 |
+
0, 999999),
|
49 |
+
**kwargs):
|
50 |
+
agent_return.type = 'searcher'
|
51 |
+
agent_return.content = question
|
52 |
+
yield deepcopy(agent_return)
|
53 |
+
|
54 |
+
|
55 |
+
class MindSearchProtocol(Internlm2Protocol):
|
56 |
+
|
57 |
+
def __init__(
|
58 |
+
self,
|
59 |
+
meta_prompt: str = None,
|
60 |
+
interpreter_prompt: str = None,
|
61 |
+
plugin_prompt: str = None,
|
62 |
+
few_shot: Optional[List] = None,
|
63 |
+
response_prompt: str = None,
|
64 |
+
language: Dict = dict(
|
65 |
+
begin='',
|
66 |
+
end='',
|
67 |
+
belong='assistant',
|
68 |
+
),
|
69 |
+
tool: Dict = dict(
|
70 |
+
begin='{start_token}{name}\n',
|
71 |
+
start_token='<|action_start|>',
|
72 |
+
name_map=dict(plugin='<|plugin|>', interpreter='<|interpreter|>'),
|
73 |
+
belong='assistant',
|
74 |
+
end='<|action_end|>\n',
|
75 |
+
),
|
76 |
+
execute: Dict = dict(role='execute',
|
77 |
+
begin='',
|
78 |
+
end='',
|
79 |
+
fallback_role='environment'),
|
80 |
+
) -> None:
|
81 |
+
self.response_prompt = response_prompt
|
82 |
+
super().__init__(meta_prompt=meta_prompt,
|
83 |
+
interpreter_prompt=interpreter_prompt,
|
84 |
+
plugin_prompt=plugin_prompt,
|
85 |
+
few_shot=few_shot,
|
86 |
+
language=language,
|
87 |
+
tool=tool,
|
88 |
+
execute=execute)
|
89 |
+
|
90 |
+
def format(self,
|
91 |
+
inner_step: List[Dict],
|
92 |
+
plugin_executor: ActionExecutor = None,
|
93 |
+
**kwargs) -> list:
|
94 |
+
formatted = []
|
95 |
+
if self.meta_prompt:
|
96 |
+
formatted.append(dict(role='system', content=self.meta_prompt))
|
97 |
+
if self.plugin_prompt:
|
98 |
+
plugin_prompt = self.plugin_prompt.format(tool_info=json.dumps(
|
99 |
+
plugin_executor.get_actions_info(), ensure_ascii=False))
|
100 |
+
formatted.append(
|
101 |
+
dict(role='system', content=plugin_prompt, name='plugin'))
|
102 |
+
if self.interpreter_prompt:
|
103 |
+
formatted.append(
|
104 |
+
dict(role='system',
|
105 |
+
content=self.interpreter_prompt,
|
106 |
+
name='interpreter'))
|
107 |
+
if self.few_shot:
|
108 |
+
for few_shot in self.few_shot:
|
109 |
+
formatted += self.format_sub_role(few_shot)
|
110 |
+
formatted += self.format_sub_role(inner_step)
|
111 |
+
return formatted
|
112 |
+
|
113 |
+
|
114 |
+
class WebSearchGraph:
|
115 |
+
end_signal = 'end'
|
116 |
+
searcher_cfg = dict()
|
117 |
+
|
118 |
+
def __init__(self):
|
119 |
+
self.nodes = {}
|
120 |
+
self.adjacency_list = defaultdict(list)
|
121 |
+
self.executor = ThreadPoolExecutor(max_workers=10)
|
122 |
+
self.future_to_query = dict()
|
123 |
+
self.searcher_resp_queue = queue.Queue()
|
124 |
+
|
125 |
+
def add_root_node(self, node_content, node_name='root'):
|
126 |
+
self.nodes[node_name] = dict(content=node_content, type='root')
|
127 |
+
self.adjacency_list[node_name] = []
|
128 |
+
self.searcher_resp_queue.put((node_name, self.nodes[node_name], []))
|
129 |
+
|
130 |
+
def add_node(self, node_name, node_content):
|
131 |
+
self.nodes[node_name] = dict(content=node_content, type='searcher')
|
132 |
+
self.adjacency_list[node_name] = []
|
133 |
+
|
134 |
+
def model_stream_thread():
|
135 |
+
agent = SearcherAgent(**self.searcher_cfg)
|
136 |
+
try:
|
137 |
+
parent_nodes = []
|
138 |
+
for start_node, adj in self.adjacency_list.items():
|
139 |
+
for neighbor in adj:
|
140 |
+
if node_name == neighbor[
|
141 |
+
'name'] and start_node in self.nodes and 'response' in self.nodes[
|
142 |
+
start_node]:
|
143 |
+
parent_nodes.append(self.nodes[start_node])
|
144 |
+
parent_response = [
|
145 |
+
dict(question=node['content'], answer=node['response'])
|
146 |
+
for node in parent_nodes
|
147 |
+
]
|
148 |
+
for answer in agent.stream_chat(
|
149 |
+
node_content,
|
150 |
+
self.nodes['root']['content'],
|
151 |
+
parent_response=parent_response):
|
152 |
+
self.searcher_resp_queue.put(
|
153 |
+
deepcopy((node_name,
|
154 |
+
dict(response=answer.response,
|
155 |
+
detail=answer), [])))
|
156 |
+
self.nodes[node_name]['response'] = answer.response
|
157 |
+
self.nodes[node_name]['detail'] = answer
|
158 |
+
except Exception as e:
|
159 |
+
logger.exception(f'Error in model_stream_thread: {e}')
|
160 |
+
|
161 |
+
self.future_to_query[self.executor.submit(
|
162 |
+
model_stream_thread)] = f'{node_name}-{node_content}'
|
163 |
+
|
164 |
+
def add_response_node(self, node_name='response'):
|
165 |
+
self.nodes[node_name] = dict(type='end')
|
166 |
+
self.searcher_resp_queue.put((node_name, self.nodes[node_name], []))
|
167 |
+
|
168 |
+
def add_edge(self, start_node, end_node):
|
169 |
+
self.adjacency_list[start_node].append(
|
170 |
+
dict(id=str(uuid.uuid4()), name=end_node, state=2))
|
171 |
+
self.searcher_resp_queue.put((start_node, self.nodes[start_node],
|
172 |
+
self.adjacency_list[start_node]))
|
173 |
+
|
174 |
+
def reset(self):
|
175 |
+
self.nodes = {}
|
176 |
+
self.adjacency_list = defaultdict(list)
|
177 |
+
|
178 |
+
def node(self, node_name):
|
179 |
+
return self.nodes[node_name].copy()
|
180 |
+
|
181 |
+
|
182 |
+
class MindSearchAgent(BaseAgent):
|
183 |
+
|
184 |
+
def __init__(self,
|
185 |
+
llm,
|
186 |
+
searcher_cfg,
|
187 |
+
protocol=MindSearchProtocol(),
|
188 |
+
max_turn=10):
|
189 |
+
self.local_dict = {}
|
190 |
+
self.ptr = 0
|
191 |
+
self.llm = llm
|
192 |
+
self.max_turn = max_turn
|
193 |
+
WebSearchGraph.searcher_cfg = searcher_cfg
|
194 |
+
super().__init__(llm=llm, action_executor=None, protocol=protocol)
|
195 |
+
|
196 |
+
def stream_chat(self, message, **kwargs):
|
197 |
+
if isinstance(message, str):
|
198 |
+
message = [{'role': 'user', 'content': message}]
|
199 |
+
elif isinstance(message, dict):
|
200 |
+
message = [message]
|
201 |
+
self.local_dict.clear()
|
202 |
+
self.ptr = 0
|
203 |
+
inner_history = message[:]
|
204 |
+
agent_return = AgentReturn()
|
205 |
+
agent_return.type = 'planner'
|
206 |
+
agent_return.nodes = {}
|
207 |
+
agent_return.adjacency_list = {}
|
208 |
+
agent_return.inner_steps = deepcopy(inner_history)
|
209 |
+
for _ in range(self.max_turn):
|
210 |
+
prompt = self._protocol.format(inner_step=inner_history)
|
211 |
+
for model_state, response, _ in self.llm.stream_chat(
|
212 |
+
prompt, session_id=random.randint(0, 999999), **kwargs):
|
213 |
+
if model_state.value < 0:
|
214 |
+
agent_return.state = getattr(AgentStatusCode,
|
215 |
+
model_state.name)
|
216 |
+
yield deepcopy(agent_return)
|
217 |
+
return
|
218 |
+
response = response.replace('<|plugin|>', '<|interpreter|>')
|
219 |
+
_, language, action = self._protocol.parse(response)
|
220 |
+
if not language and not action:
|
221 |
+
continue
|
222 |
+
code = action['parameters']['command'] if action else ''
|
223 |
+
agent_return.state = self._determine_agent_state(
|
224 |
+
model_state, code, agent_return)
|
225 |
+
agent_return.response = language if not code else code
|
226 |
+
|
227 |
+
# if agent_return.state == AgentStatusCode.STREAM_ING:
|
228 |
+
yield deepcopy(agent_return)
|
229 |
+
|
230 |
+
inner_history.append({'role': 'language', 'content': language})
|
231 |
+
print(colored(response, 'blue'))
|
232 |
+
|
233 |
+
if code:
|
234 |
+
yield from self._process_code(
|
235 |
+
agent_return, inner_history, code,
|
236 |
+
kwargs.get('as_dict', False),
|
237 |
+
kwargs.get('return_early', False))
|
238 |
+
else:
|
239 |
+
agent_return.state = AgentStatusCode.END
|
240 |
+
yield deepcopy(agent_return)
|
241 |
+
return
|
242 |
+
|
243 |
+
agent_return.state = AgentStatusCode.END
|
244 |
+
yield deepcopy(agent_return)
|
245 |
+
|
246 |
+
def _determine_agent_state(self, model_state, code, agent_return):
|
247 |
+
if code:
|
248 |
+
return (AgentStatusCode.PLUGIN_START if model_state
|
249 |
+
== ModelStatusCode.END else AgentStatusCode.PLUGIN_START)
|
250 |
+
return (AgentStatusCode.ANSWER_ING
|
251 |
+
if agent_return.nodes and 'response' in agent_return.nodes else
|
252 |
+
AgentStatusCode.STREAM_ING)
|
253 |
+
|
254 |
+
def _process_code(self,
|
255 |
+
agent_return,
|
256 |
+
inner_history,
|
257 |
+
code,
|
258 |
+
as_dict=False,
|
259 |
+
return_early=False):
|
260 |
+
for node_name, node, adj in self.execute_code(
|
261 |
+
code, return_early=return_early):
|
262 |
+
if as_dict and 'detail' in node:
|
263 |
+
node['detail'] = asdict(node['detail'])
|
264 |
+
if not adj:
|
265 |
+
agent_return.nodes[node_name] = node
|
266 |
+
else:
|
267 |
+
agent_return.adjacency_list[node_name] = adj
|
268 |
+
# state 1进行中,2未开始,3已结束
|
269 |
+
for start_node, neighbors in agent_return.adjacency_list.items():
|
270 |
+
for neighbor in neighbors:
|
271 |
+
if neighbor['name'] not in agent_return.nodes:
|
272 |
+
state = 2
|
273 |
+
elif 'detail' not in agent_return.nodes[neighbor['name']]:
|
274 |
+
state = 2
|
275 |
+
elif agent_return.nodes[neighbor['name']][
|
276 |
+
'detail'].state == AgentStatusCode.END:
|
277 |
+
state = 3
|
278 |
+
else:
|
279 |
+
state = 1
|
280 |
+
neighbor['state'] = state
|
281 |
+
if not adj:
|
282 |
+
yield deepcopy((agent_return, node_name))
|
283 |
+
reference, references_url = self._generate_reference(
|
284 |
+
agent_return, code, as_dict)
|
285 |
+
inner_history.append({
|
286 |
+
'role': 'tool',
|
287 |
+
'content': code,
|
288 |
+
'name': 'plugin'
|
289 |
+
})
|
290 |
+
inner_history.append({
|
291 |
+
'role': 'environment',
|
292 |
+
'content': reference,
|
293 |
+
'name': 'plugin'
|
294 |
+
})
|
295 |
+
agent_return.inner_steps = deepcopy(inner_history)
|
296 |
+
agent_return.state = AgentStatusCode.PLUGIN_RETURN
|
297 |
+
agent_return.references.update(references_url)
|
298 |
+
yield deepcopy(agent_return)
|
299 |
+
|
300 |
+
def _generate_reference(self, agent_return, code, as_dict):
|
301 |
+
node_list = [
|
302 |
+
node.strip().strip('\"') for node in re.findall(
|
303 |
+
r'graph\.node\("((?:[^"\\]|\\.)*?)"\)', code)
|
304 |
+
]
|
305 |
+
if 'add_response_node' in code:
|
306 |
+
return self._protocol.response_prompt, dict()
|
307 |
+
references = []
|
308 |
+
references_url = dict()
|
309 |
+
for node_name in node_list:
|
310 |
+
if as_dict:
|
311 |
+
ref_results = agent_return.nodes[node_name]['detail'][
|
312 |
+
'actions'][0]['result'][0]['content']
|
313 |
+
else:
|
314 |
+
ref_results = agent_return.nodes[node_name]['detail'].actions[
|
315 |
+
0].result[0]['content']
|
316 |
+
ref_results = json.loads(ref_results)
|
317 |
+
ref2url = {idx: item['url'] for idx, item in ref_results.items()}
|
318 |
+
ref = f"## {node_name}\n\n{agent_return.nodes[node_name]['response']}\n"
|
319 |
+
updated_ref = re.sub(
|
320 |
+
r'\[\[(\d+)\]\]',
|
321 |
+
lambda match: f'[[{int(match.group(1)) + self.ptr}]]', ref)
|
322 |
+
numbers = [int(n) for n in re.findall(r'\[\[(\d+)\]\]', ref)]
|
323 |
+
if numbers:
|
324 |
+
assert all(str(elem) in ref2url for elem in numbers)
|
325 |
+
references_url.update({
|
326 |
+
str(idx + self.ptr): ref2url[str(idx)]
|
327 |
+
for idx in set(numbers)
|
328 |
+
})
|
329 |
+
self.ptr += max(numbers) + 1
|
330 |
+
references.append(updated_ref)
|
331 |
+
return '\n'.join(references), references_url
|
332 |
+
|
333 |
+
def execute_code(self, command: str, return_early=False):
|
334 |
+
|
335 |
+
def extract_code(text: str) -> str:
|
336 |
+
text = re.sub(r'from ([\w.]+) import WebSearchGraph', '', text)
|
337 |
+
triple_match = re.search(r'```[^\n]*\n(.+?)```', text, re.DOTALL)
|
338 |
+
single_match = re.search(r'`([^`]*)`', text, re.DOTALL)
|
339 |
+
if triple_match:
|
340 |
+
return triple_match.group(1)
|
341 |
+
elif single_match:
|
342 |
+
return single_match.group(1)
|
343 |
+
return text
|
344 |
+
|
345 |
+
def run_command(cmd):
|
346 |
+
try:
|
347 |
+
exec(cmd, globals(), self.local_dict)
|
348 |
+
plan_graph = self.local_dict.get('graph')
|
349 |
+
assert plan_graph is not None
|
350 |
+
for future in as_completed(plan_graph.future_to_query):
|
351 |
+
future.result()
|
352 |
+
plan_graph.future_to_query.clear()
|
353 |
+
plan_graph.searcher_resp_queue.put(plan_graph.end_signal)
|
354 |
+
except Exception as e:
|
355 |
+
logger.exception(f'Error executing code: {e}')
|
356 |
+
|
357 |
+
command = extract_code(command)
|
358 |
+
producer_thread = threading.Thread(target=run_command,
|
359 |
+
args=(command, ))
|
360 |
+
producer_thread.start()
|
361 |
+
|
362 |
+
responses = defaultdict(list)
|
363 |
+
ordered_nodes = []
|
364 |
+
active_node = None
|
365 |
+
|
366 |
+
while True:
|
367 |
+
try:
|
368 |
+
item = self.local_dict.get('graph').searcher_resp_queue.get(
|
369 |
+
timeout=60)
|
370 |
+
if item is WebSearchGraph.end_signal:
|
371 |
+
for node_name in ordered_nodes:
|
372 |
+
# resp = None
|
373 |
+
for resp in responses[node_name]:
|
374 |
+
yield deepcopy(resp)
|
375 |
+
# if resp:
|
376 |
+
# assert resp[1][
|
377 |
+
# 'detail'].state == AgentStatusCode.END
|
378 |
+
break
|
379 |
+
node_name, node, adj = item
|
380 |
+
if node_name in ['root', 'response']:
|
381 |
+
yield deepcopy((node_name, node, adj))
|
382 |
+
else:
|
383 |
+
if node_name not in ordered_nodes:
|
384 |
+
ordered_nodes.append(node_name)
|
385 |
+
responses[node_name].append((node_name, node, adj))
|
386 |
+
if not active_node and ordered_nodes:
|
387 |
+
active_node = ordered_nodes[0]
|
388 |
+
while active_node and responses[active_node]:
|
389 |
+
if return_early:
|
390 |
+
if 'detail' in responses[active_node][-1][
|
391 |
+
1] and responses[active_node][-1][1][
|
392 |
+
'detail'].state == AgentStatusCode.END:
|
393 |
+
item = responses[active_node][-1]
|
394 |
+
else:
|
395 |
+
item = responses[active_node].pop(0)
|
396 |
+
else:
|
397 |
+
item = responses[active_node].pop(0)
|
398 |
+
if 'detail' in item[1] and item[1][
|
399 |
+
'detail'].state == AgentStatusCode.END:
|
400 |
+
ordered_nodes.pop(0)
|
401 |
+
responses[active_node].clear()
|
402 |
+
active_node = None
|
403 |
+
yield deepcopy(item)
|
404 |
+
except queue.Empty:
|
405 |
+
if not producer_thread.is_alive():
|
406 |
+
break
|
407 |
+
producer_thread.join()
|
408 |
+
return
|
mindsearch/agent/mindsearch_prompt.py
ADDED
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# flake8: noqa
|
2 |
+
|
3 |
+
searcher_system_prompt_cn = """## 人物简介
|
4 |
+
你是一个可以调用网络搜索工具的智能助手。请根据"当前问题",调用搜索工具收集信息并回复问题。你能够调用如下工具:
|
5 |
+
{tool_info}
|
6 |
+
## 回复格式
|
7 |
+
|
8 |
+
调用工具时,请按照以下格式:
|
9 |
+
```
|
10 |
+
你的思考过程...<|action_start|><|plugin|>{{"name": "tool_name", "parameters": {{"param1": "value1"}}}}<|action_end|>
|
11 |
+
```
|
12 |
+
|
13 |
+
## 要求
|
14 |
+
|
15 |
+
- 回答中每个关键点需标注引用的搜索结果来源,以确保信息的可信度。给出索引的形式为`[[int]]`,如果有多个索引,则用多个[[]]表示,如`[[id_1]][[id_2]]`。
|
16 |
+
- 基于"当前问题"的搜索结果,撰写详细完备的回复,优先回答"当前问题"。
|
17 |
+
|
18 |
+
"""
|
19 |
+
|
20 |
+
searcher_system_prompt_en = """## Character Introduction
|
21 |
+
You are an intelligent assistant that can call web search tools. Please collect information and reply to the question based on the current problem. You can use the following tools:
|
22 |
+
{tool_info}
|
23 |
+
## Reply Format
|
24 |
+
|
25 |
+
When calling the tool, please follow the format below:
|
26 |
+
```
|
27 |
+
Your thought process...<|action_start|><|plugin|>{{"name": "tool_name", "parameters": {{"param1": "value1"}}}}<|action_end|>
|
28 |
+
```
|
29 |
+
|
30 |
+
## Requirements
|
31 |
+
|
32 |
+
- Each key point in the response should be marked with the source of the search results to ensure the credibility of the information. The citation format is `[[int]]`. If there are multiple citations, use multiple [[]] to provide the index, such as `[[id_1]][[id_2]]`.
|
33 |
+
- Based on the search results of the "current problem", write a detailed and complete reply to answer the "current problem".
|
34 |
+
"""
|
35 |
+
|
36 |
+
searcher_input_template_en = """## Final Problem
|
37 |
+
{topic}
|
38 |
+
## Current Problem
|
39 |
+
{question}
|
40 |
+
"""
|
41 |
+
|
42 |
+
searcher_input_template_cn = """## 主问题
|
43 |
+
{topic}
|
44 |
+
## 当前问题
|
45 |
+
{question}
|
46 |
+
"""
|
47 |
+
|
48 |
+
searcher_context_template_en = """## Historical Problem
|
49 |
+
{question}
|
50 |
+
Answer: {answer}
|
51 |
+
"""
|
52 |
+
|
53 |
+
searcher_context_template_cn = """## 历史问题
|
54 |
+
{question}
|
55 |
+
回答:{answer}
|
56 |
+
"""
|
57 |
+
|
58 |
+
search_template_cn = '## {query}\n\n{result}\n'
|
59 |
+
search_template_en = '## {query}\n\n{result}\n'
|
60 |
+
|
61 |
+
GRAPH_PROMPT_CN = """## 人物简介
|
62 |
+
你是一个可以利用 Jupyter 环境 Python 编程的程序员。你可以利用提供的 API 来构建 Web 搜索图,最终生成代码并执行。
|
63 |
+
|
64 |
+
## API 介绍
|
65 |
+
|
66 |
+
下面是包含属性详细说明的 `WebSearchGraph` 类的 API 文档:
|
67 |
+
|
68 |
+
### 类:`WebSearchGraph`
|
69 |
+
|
70 |
+
此类用于管理网络搜索图的节点和边,并通过网络代理进行搜索。
|
71 |
+
|
72 |
+
#### 初始化方法
|
73 |
+
|
74 |
+
初始化 `WebSearchGraph` 实例。
|
75 |
+
|
76 |
+
**属性:**
|
77 |
+
|
78 |
+
- `nodes` (Dict[str, Dict[str, str]]): 存储图中所有节点的字典。每个节点由其名称索引,并包含内容、类型以及其他相关信息。
|
79 |
+
- `adjacency_list` (Dict[str, List[str]]): 存储图中所有节点之间连接关系的邻接表。每个节点由其名称索引,并包含一个相邻节点名称的列表。
|
80 |
+
|
81 |
+
|
82 |
+
#### 方法:`add_root_node`
|
83 |
+
|
84 |
+
添加原始问题作为根节点。
|
85 |
+
**参数:**
|
86 |
+
|
87 |
+
- `node_content` (str): 用户提出的问题。
|
88 |
+
- `node_name` (str, 可选): 节点名称,默认为 'root'。
|
89 |
+
|
90 |
+
|
91 |
+
#### 方法:`add_node`
|
92 |
+
|
93 |
+
添加搜索子问题节点并返回搜索结果。
|
94 |
+
**参数:
|
95 |
+
|
96 |
+
- `node_name` (str): 节点名称。
|
97 |
+
- `node_content` (str): 子问题内容。
|
98 |
+
|
99 |
+
**返回:**
|
100 |
+
|
101 |
+
- `str`: 返回搜索结果。
|
102 |
+
|
103 |
+
|
104 |
+
#### 方法:`add_response_node`
|
105 |
+
|
106 |
+
当前获取的信息已经满足问题需求,添加回复节点。
|
107 |
+
|
108 |
+
**参数:**
|
109 |
+
|
110 |
+
- `node_name` (str, 可选): 节点名称,默认为 'response'。
|
111 |
+
|
112 |
+
|
113 |
+
#### 方法:`add_edge`
|
114 |
+
|
115 |
+
添加边。
|
116 |
+
|
117 |
+
**参数:**
|
118 |
+
|
119 |
+
- `start_node` (str): 起始节点名称。
|
120 |
+
- `end_node` (str): 结束节点名称。
|
121 |
+
|
122 |
+
|
123 |
+
#### 方法:`reset`
|
124 |
+
|
125 |
+
重置节点和边。
|
126 |
+
|
127 |
+
|
128 |
+
#### 方法:`node`
|
129 |
+
|
130 |
+
获取节点信息。
|
131 |
+
|
132 |
+
```python
|
133 |
+
def node(self, node_name: str) -> str
|
134 |
+
```
|
135 |
+
|
136 |
+
**参数:**
|
137 |
+
|
138 |
+
- `node_name` (str): 节点名称。
|
139 |
+
|
140 |
+
**返回:**
|
141 |
+
|
142 |
+
- `str`: 返回包含节点信息的字典,包含节点的内容、类型、思考过程(如果有)和前驱节点列表。
|
143 |
+
|
144 |
+
## 任务介绍
|
145 |
+
通过将一个问题拆分成能够通过搜索回答的子问题(没有关联的问题可以同步并列搜索),每个搜索的问题应该是一个单一问题,即单个具体人、事、物、具体时间点、地点或知识点的问题,不是一个复合问题(比如某个时间段), 一步步构建搜索图,最终回答问题。
|
146 |
+
|
147 |
+
## 注意事项
|
148 |
+
|
149 |
+
1. 注意,每个搜索节点的内容必须单个问题,不要包含多个问题(比如同时问多个知识点的问题或者多个事物的比较加筛选,类似 A, B, C 有什么区别,那个价格在哪个区间 -> 分别查询)
|
150 |
+
2. 不要杜撰搜索结果,要等待代码返回结果
|
151 |
+
3. 同样的问题不要重复提问,可以在已有问题的基础上继续提问
|
152 |
+
4. 添加 response 节点的时候,要单独添加,不要和其他节点一起添加,不能同时添加 response 节点和其他节点
|
153 |
+
5. 一次输出中,不要包含多个代码块,每次只能有一个代码块
|
154 |
+
6. 每个代码块应该放置在一个代码块标记中,同时生成完代码后添加一个<end>标志,如下所示:
|
155 |
+
<|action_start|><|interpreter|>```python
|
156 |
+
# 你的代码块
|
157 |
+
```<|action_end|>
|
158 |
+
7. 最后一次回复应该是添加 response 节点,必须添加 response 节点,不要添加其他节点
|
159 |
+
"""
|
160 |
+
|
161 |
+
GRAPH_PROMPT_EN = """## Character Profile
|
162 |
+
You are a programmer capable of Python programming in a Jupyter environment. You can utilize the provided API to construct a Web Search Graph, ultimately generating and executing code.
|
163 |
+
|
164 |
+
## API Description
|
165 |
+
|
166 |
+
Below is the API documentation for the WebSearchGraph class, including detailed attribute descriptions:
|
167 |
+
|
168 |
+
### Class: WebSearchGraph
|
169 |
+
|
170 |
+
This class manages nodes and edges of a web search graph and conducts searches via a web proxy.
|
171 |
+
|
172 |
+
#### Initialization Method
|
173 |
+
|
174 |
+
Initializes an instance of WebSearchGraph.
|
175 |
+
|
176 |
+
**Attributes:**
|
177 |
+
|
178 |
+
- nodes (Dict[str, Dict[str, str]]): A dictionary storing all nodes in the graph. Each node is indexed by its name and contains content, type, and other related information.
|
179 |
+
- adjacency_list (Dict[str, List[str]]): An adjacency list storing the connections between all nodes in the graph. Each node is indexed by its name and contains a list of adjacent node names.
|
180 |
+
|
181 |
+
#### Method: add_root_node
|
182 |
+
|
183 |
+
Adds the initial question as the root node.
|
184 |
+
**Parameters:**
|
185 |
+
|
186 |
+
- node_content (str): The user's question.
|
187 |
+
- node_name (str, optional): The node name, default is 'root'.
|
188 |
+
|
189 |
+
#### Method: add_node
|
190 |
+
|
191 |
+
Adds a sub-question node and returns search results.
|
192 |
+
**Parameters:**
|
193 |
+
|
194 |
+
- node_name (str): The node name.
|
195 |
+
- node_content (str): The sub-question content.
|
196 |
+
|
197 |
+
**Returns:**
|
198 |
+
|
199 |
+
- str: Returns the search results.
|
200 |
+
|
201 |
+
#### Method: add_response_node
|
202 |
+
|
203 |
+
Adds a response node when the current information satisfies the question's requirements.
|
204 |
+
|
205 |
+
**Parameters:**
|
206 |
+
|
207 |
+
- node_name (str, optional): The node name, default is 'response'.
|
208 |
+
|
209 |
+
#### Method: add_edge
|
210 |
+
|
211 |
+
Adds an edge.
|
212 |
+
|
213 |
+
**Parameters:**
|
214 |
+
|
215 |
+
- start_node (str): The starting node name.
|
216 |
+
- end_node (str): The ending node name.
|
217 |
+
|
218 |
+
#### Method: reset
|
219 |
+
|
220 |
+
Resets nodes and edges.
|
221 |
+
|
222 |
+
#### Method: node
|
223 |
+
|
224 |
+
Retrieves node information.
|
225 |
+
|
226 |
+
python
|
227 |
+
def node(self, node_name: str) -> str
|
228 |
+
|
229 |
+
**Parameters:**
|
230 |
+
|
231 |
+
- node_name (str): The node name.
|
232 |
+
|
233 |
+
**Returns:**
|
234 |
+
|
235 |
+
- str: Returns a dictionary containing the node's information, including content, type, thought process (if any), and list of predecessor nodes.
|
236 |
+
|
237 |
+
## Task Description
|
238 |
+
By breaking down a question into sub-questions that can be answered through searches (unrelated questions can be searched concurrently), each search query should be a single question focusing on a specific person, event, object, specific time point, location, or knowledge point. It should not be a compound question (e.g., a time period). Step by step, build the search graph to finally answer the question.
|
239 |
+
|
240 |
+
## Considerations
|
241 |
+
|
242 |
+
1. Each search node's content must be a single question; do not include multiple questions (e.g., do not ask multiple knowledge points or compare and filter multiple things simultaneously, like asking for differences between A, B, and C, or price ranges -> query each separately).
|
243 |
+
2. Do not fabricate search results; wait for the code to return results.
|
244 |
+
3. Do not repeat the same question; continue asking based on existing questions.
|
245 |
+
4. When adding a response node, add it separately; do not add a response node and other nodes simultaneously.
|
246 |
+
5. In a single output, do not include multiple code blocks; only one code block per output.
|
247 |
+
6. Each code block should be placed within a code block marker, and after generating the code, add an <end> tag as shown below:
|
248 |
+
<|action_start|><|interpreter|>
|
249 |
+
```python
|
250 |
+
# Your code block
|
251 |
+
```<|action_end|>
|
252 |
+
7. The final response should add a response node, and no other nodes should be added.
|
253 |
+
"""
|
254 |
+
|
255 |
+
FINAL_RESPONSE_CN = """基于提供的问答对,撰写一篇详细完备的最终回答。
|
256 |
+
- 回答内容需要逻辑清晰,层次分明,确保读者易于理解。
|
257 |
+
- 回答中每个关键点需标注引用的搜索结果来源(保持跟问答对中的索引一致),以确保信息的可信度。给出索引的形式为`[[int]]`,如果有多个索引,则用多个[[]]表示,如`[[id_1]][[id_2]]`。
|
258 |
+
- 回答部分需要全面且完备,不要出现"基于上述内容"等模糊表达,最终呈现的回答不包括提供给你的问答对。
|
259 |
+
- 语言风格需要专业、严谨,避免口语化表达。
|
260 |
+
- 保持统一的语法和词汇使用,确保整体文档的一致性和连贯性。"""
|
261 |
+
|
262 |
+
FINAL_RESPONSE_EN = """Based on the provided Q&A pairs, write a detailed and comprehensive final response.
|
263 |
+
- The response content should be logically clear and well-structured to ensure reader understanding.
|
264 |
+
- Each key point in the response should be marked with the source of the search results (consistent with the indices in the Q&A pairs) to ensure information credibility. The index is in the form of `[[int]]`, and if there are multiple indices, use multiple `[[]]`, such as `[[id_1]][[id_2]]`.
|
265 |
+
- The response should be comprehensive and complete, without vague expressions like "based on the above content". The final response should not include the Q&A pairs provided to you.
|
266 |
+
- The language style should be professional and rigorous, avoiding colloquial expressions.
|
267 |
+
- Maintain consistent grammar and vocabulary usage to ensure overall document consistency and coherence."""
|
mindsearch/agent/models.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
from lagent.llms import (GPTAPI, INTERNLM2_META, HFTransformerCasualLM,
|
4 |
+
LMDeployClient, LMDeployServer)
|
5 |
+
|
6 |
+
internlm_server = dict(type=LMDeployServer,
|
7 |
+
path='internlm/internlm2_5-7b-chat',
|
8 |
+
model_name='internlm2',
|
9 |
+
meta_template=INTERNLM2_META,
|
10 |
+
top_p=0.8,
|
11 |
+
top_k=1,
|
12 |
+
temperature=0,
|
13 |
+
max_new_tokens=8192,
|
14 |
+
repetition_penalty=1.02,
|
15 |
+
stop_words=['<|im_end|>'])
|
16 |
+
|
17 |
+
internlm_client = dict(type=LMDeployClient,
|
18 |
+
model_name='internlm2_5-7b-chat',
|
19 |
+
url='http://127.0.0.1:23333',
|
20 |
+
meta_template=INTERNLM2_META,
|
21 |
+
top_p=0.8,
|
22 |
+
top_k=1,
|
23 |
+
temperature=0,
|
24 |
+
max_new_tokens=8192,
|
25 |
+
repetition_penalty=1.02,
|
26 |
+
stop_words=['<|im_end|>'])
|
27 |
+
|
28 |
+
internlm_hf = dict(type=HFTransformerCasualLM,
|
29 |
+
path='internlm/internlm2_5-7b-chat',
|
30 |
+
meta_template=INTERNLM2_META,
|
31 |
+
top_p=0.8,
|
32 |
+
top_k=None,
|
33 |
+
temperature=1e-6,
|
34 |
+
max_new_tokens=8192,
|
35 |
+
repetition_penalty=1.02,
|
36 |
+
stop_words=['<|im_end|>'])
|
37 |
+
|
38 |
+
gpt4 = dict(type=GPTAPI,
|
39 |
+
model_type='gpt-4-turbo',
|
40 |
+
key=os.environ.get('OPENAI_API_KEY', 'YOUR OPENAI API KEY'))
|
41 |
+
deepseek_coder = dict(type=GPTAPI,
|
42 |
+
model_type='deepseek-coder',
|
43 |
+
key=os.environ.get('OPENAI_API_KEY', 'YOUR OPENAI API KEY'),
|
44 |
+
url="https://api.deepseek.com")
|
mindsearch/app.py
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import json
|
3 |
+
import logging
|
4 |
+
from copy import deepcopy
|
5 |
+
from dataclasses import asdict
|
6 |
+
from typing import Dict, List, Union
|
7 |
+
|
8 |
+
import janus
|
9 |
+
from fastapi import FastAPI
|
10 |
+
from fastapi.middleware.cors import CORSMiddleware
|
11 |
+
from lagent.schema import AgentStatusCode
|
12 |
+
from pydantic import BaseModel
|
13 |
+
from sse_starlette.sse import EventSourceResponse
|
14 |
+
|
15 |
+
from mindsearch.agent import init_agent
|
16 |
+
|
17 |
+
|
18 |
+
def parse_arguments():
|
19 |
+
import argparse
|
20 |
+
parser = argparse.ArgumentParser(description='MindSearch API')
|
21 |
+
parser.add_argument('--lang', default='cn', type=str, help='Language')
|
22 |
+
parser.add_argument('--model_format',
|
23 |
+
default='internlm_server',
|
24 |
+
type=str,
|
25 |
+
help='Model format')
|
26 |
+
return parser.parse_args()
|
27 |
+
|
28 |
+
|
29 |
+
args = parse_arguments()
|
30 |
+
app = FastAPI(docs_url='/')
|
31 |
+
|
32 |
+
app.add_middleware(CORSMiddleware,
|
33 |
+
allow_origins=['*'],
|
34 |
+
allow_credentials=True,
|
35 |
+
allow_methods=['*'],
|
36 |
+
allow_headers=['*'])
|
37 |
+
|
38 |
+
|
39 |
+
class GenerationParams(BaseModel):
|
40 |
+
inputs: Union[str, List[Dict]]
|
41 |
+
agent_cfg: Dict = dict()
|
42 |
+
|
43 |
+
|
44 |
+
@app.post('/solve')
|
45 |
+
async def run(request: GenerationParams):
|
46 |
+
|
47 |
+
def convert_adjacency_to_tree(adjacency_input, root_name):
|
48 |
+
|
49 |
+
def build_tree(node_name):
|
50 |
+
node = {'name': node_name, 'children': []}
|
51 |
+
if node_name in adjacency_input:
|
52 |
+
for child in adjacency_input[node_name]:
|
53 |
+
child_node = build_tree(child['name'])
|
54 |
+
child_node['state'] = child['state']
|
55 |
+
child_node['id'] = child['id']
|
56 |
+
node['children'].append(child_node)
|
57 |
+
return node
|
58 |
+
|
59 |
+
return build_tree(root_name)
|
60 |
+
|
61 |
+
async def generate():
|
62 |
+
try:
|
63 |
+
queue = janus.Queue()
|
64 |
+
|
65 |
+
# 使用 run_in_executor 将同步生成器包装成异步生成器
|
66 |
+
def sync_generator_wrapper():
|
67 |
+
try:
|
68 |
+
for response in agent.stream_chat(inputs):
|
69 |
+
queue.sync_q.put(response)
|
70 |
+
except KeyError as e:
|
71 |
+
logging.error(f'KeyError in sync_generator_wrapper: {e}')
|
72 |
+
|
73 |
+
async def async_generator_wrapper():
|
74 |
+
loop = asyncio.get_event_loop()
|
75 |
+
loop.run_in_executor(None, sync_generator_wrapper)
|
76 |
+
while True:
|
77 |
+
response = await queue.async_q.get()
|
78 |
+
yield response
|
79 |
+
if not isinstance(
|
80 |
+
response,
|
81 |
+
tuple) and response.state == AgentStatusCode.END:
|
82 |
+
break
|
83 |
+
|
84 |
+
async for response in async_generator_wrapper():
|
85 |
+
if isinstance(response, tuple):
|
86 |
+
agent_return, node_name = response
|
87 |
+
else:
|
88 |
+
agent_return = response
|
89 |
+
node_name = None
|
90 |
+
origin_adj = deepcopy(agent_return.adjacency_list)
|
91 |
+
adjacency_list = convert_adjacency_to_tree(
|
92 |
+
agent_return.adjacency_list, 'root')
|
93 |
+
assert adjacency_list[
|
94 |
+
'name'] == 'root' and 'children' in adjacency_list
|
95 |
+
agent_return.adjacency_list = adjacency_list['children']
|
96 |
+
agent_return = asdict(agent_return)
|
97 |
+
agent_return['adj'] = origin_adj
|
98 |
+
response_json = json.dumps(dict(response=agent_return,
|
99 |
+
current_node=node_name),
|
100 |
+
ensure_ascii=False)
|
101 |
+
yield {'data': response_json}
|
102 |
+
# yield f'data: {response_json}\n\n'
|
103 |
+
except Exception as exc:
|
104 |
+
msg = 'An error occurred while generating the response.'
|
105 |
+
logging.exception(msg)
|
106 |
+
response_json = json.dumps(
|
107 |
+
dict(error=dict(msg=msg, details=str(exc))),
|
108 |
+
ensure_ascii=False)
|
109 |
+
yield {'data': response_json}
|
110 |
+
# yield f'data: {response_json}\n\n'
|
111 |
+
|
112 |
+
inputs = request.inputs
|
113 |
+
agent = init_agent(lang=args.lang, model_format=args.model_format)
|
114 |
+
return EventSourceResponse(generate())
|
115 |
+
|
116 |
+
|
117 |
+
if __name__ == '__main__':
|
118 |
+
import uvicorn
|
119 |
+
uvicorn.run(app, host='0.0.0.0', port=8002, log_level='info')
|
mindsearch/terminal.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
|
3 |
+
from lagent.actions import ActionExecutor, BingBrowser
|
4 |
+
from lagent.llms import INTERNLM2_META, LMDeployServer
|
5 |
+
|
6 |
+
from mindsearch.agent.mindsearch_agent import (MindSearchAgent,
|
7 |
+
MindSearchProtocol)
|
8 |
+
from mindsearch.agent.mindsearch_prompt import (
|
9 |
+
FINAL_RESPONSE_CN, FINAL_RESPONSE_EN, GRAPH_PROMPT_CN, GRAPH_PROMPT_EN,
|
10 |
+
searcher_context_template_cn, searcher_context_template_en,
|
11 |
+
searcher_input_template_cn, searcher_input_template_en,
|
12 |
+
searcher_system_prompt_cn, searcher_system_prompt_en)
|
13 |
+
|
14 |
+
lang = 'cn'
|
15 |
+
llm = LMDeployServer(path='internlm/internlm2_5-7b',
|
16 |
+
model_name='internlm2',
|
17 |
+
meta_template=INTERNLM2_META,
|
18 |
+
top_p=0.8,
|
19 |
+
top_k=1,
|
20 |
+
temperature=0,
|
21 |
+
max_new_tokens=8192,
|
22 |
+
repetition_penalty=1.02,
|
23 |
+
stop_words=['<|im_end|>'])
|
24 |
+
|
25 |
+
agent = MindSearchAgent(
|
26 |
+
llm=llm,
|
27 |
+
protocol=MindSearchProtocol(
|
28 |
+
meta_prompt=datetime.now().strftime('The current date is %Y-%m-%d.'),
|
29 |
+
interpreter_prompt=GRAPH_PROMPT_CN
|
30 |
+
if lang == 'cn' else GRAPH_PROMPT_EN,
|
31 |
+
response_prompt=FINAL_RESPONSE_CN
|
32 |
+
if lang == 'cn' else FINAL_RESPONSE_EN),
|
33 |
+
searcher_cfg=dict(
|
34 |
+
llm=llm,
|
35 |
+
plugin_executor=ActionExecutor(
|
36 |
+
BingBrowser(searcher_type='DuckDuckGoSearch', topk=6)),
|
37 |
+
protocol=MindSearchProtocol(
|
38 |
+
meta_prompt=datetime.now().strftime(
|
39 |
+
'The current date is %Y-%m-%d.'),
|
40 |
+
plugin_prompt=searcher_system_prompt_cn
|
41 |
+
if lang == 'cn' else searcher_system_prompt_en,
|
42 |
+
),
|
43 |
+
template=dict(input=searcher_input_template_cn
|
44 |
+
if lang == 'cn' else searcher_input_template_en,
|
45 |
+
context=searcher_context_template_cn
|
46 |
+
if lang == 'cn' else searcher_context_template_en)),
|
47 |
+
max_turn=10)
|
48 |
+
|
49 |
+
for agent_return in agent.stream_chat('上海今天适合穿什么衣服'):
|
50 |
+
pass
|
requirements.txt
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
duckduckgo_search==5.3.1b1
|
2 |
+
einops
|
3 |
+
fastapi
|
4 |
+
git+https://github.com/InternLM/lagent.git
|
5 |
+
gradio
|
6 |
+
janus
|
7 |
+
lmdeploy==0.2.3
|
8 |
+
pyvis
|
9 |
+
sse-starlette
|
10 |
+
termcolor
|
11 |
+
uvicorn
|