solitudeLin commited on
Commit
c8210cf
1 Parent(s): f3d2a3c

Add application file

Browse files
Files changed (48) hide show
  1. .gitignore +162 -0
  2. .pre-commit-config.yaml +46 -0
  3. .pylintrc +428 -0
  4. Dockerfile +26 -0
  5. LICENSE +201 -0
  6. README.md +122 -12
  7. README_zh-CN.md +119 -0
  8. assets/logo.svg +24 -0
  9. assets/mindsearch_openset.png +0 -0
  10. frontend/React/.gitignore +20 -0
  11. frontend/React/.prettierignore +7 -0
  12. frontend/React/.prettierrc.json +7 -0
  13. frontend/React/README.md +132 -0
  14. frontend/React/index.html +14 -0
  15. frontend/React/package-lock.json +0 -0
  16. frontend/React/package.json +49 -0
  17. frontend/React/src/App.module.less +54 -0
  18. frontend/React/src/App.tsx +23 -0
  19. frontend/React/src/assets/background.png +0 -0
  20. frontend/React/src/assets/fold-icon.svg +3 -0
  21. frontend/React/src/assets/logo.svg +24 -0
  22. frontend/React/src/assets/pack-up.svg +4 -0
  23. frontend/React/src/assets/sendIcon.svg +4 -0
  24. frontend/React/src/assets/show-right-icon.png +0 -0
  25. frontend/React/src/assets/unflod-icon.svg +3 -0
  26. frontend/React/src/components/iconfont/index.tsx +7 -0
  27. frontend/React/src/config/cgi.ts +2 -0
  28. frontend/React/src/global.d.ts +1 -0
  29. frontend/React/src/index.less +62 -0
  30. frontend/React/src/index.tsx +10 -0
  31. frontend/React/src/pages/render/index.module.less +848 -0
  32. frontend/React/src/pages/render/index.tsx +681 -0
  33. frontend/React/src/pages/render/mindMapItem.tsx +39 -0
  34. frontend/React/src/routes/routes.tsx +38 -0
  35. frontend/React/src/utils/tools.ts +24 -0
  36. frontend/React/src/vite-env.d.ts +9 -0
  37. frontend/React/tsconfig.json +24 -0
  38. frontend/React/vite.config.ts +62 -0
  39. frontend/React/windows-.png +0 -0
  40. frontend/mindsearch_gradio.py +142 -0
  41. frontend/mindsearch_streamlit.py +319 -0
  42. mindsearch/agent/__init__.py +54 -0
  43. mindsearch/agent/mindsearch_agent.py +408 -0
  44. mindsearch/agent/mindsearch_prompt.py +267 -0
  45. mindsearch/agent/models.py +44 -0
  46. mindsearch/app.py +119 -0
  47. mindsearch/terminal.py +50 -0
  48. 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
- title: Mindsearch
3
- emoji: 👁
4
- colorFrom: yellow
5
- colorTo: gray
6
- sdk: streamlit
7
- sdk_version: 1.37.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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