Compare commits

...

558 Commits

Author SHA1 Message Date
wxg0103 847755b1c2 feat: add response body schemas for various API endpoints 2025-08-19 15:15:46 +08:00
wxg0103 b57455d0ee refactor: expand permissions for application access token
--bug=1060032 --user=王孝刚 【桂物智慧】 api 调用客户端修改对话摘要接口,报错403,没有访问权限 https://www.tapd.cn/57709429/s/1757188
2025-08-19 11:06:36 +08:00
maninhill 2a257edff9
chore: Update README.md (#3587)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-07-14 17:49:32 +08:00
maninhill d47699331c
chore: Update README_CN.md (#3585) 2025-07-14 17:28:34 +08:00
maninhill 90c64d77dd
chore: Update README.md (#3584) 2025-07-14 17:27:02 +08:00
liqiang-fit2cloud e1ada3ffe2
Update build-and-push.yml
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-07-10 17:37:21 +08:00
wangdan-fit2cloud b62c79fda6 fix: text bug
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-07-10 11:37:44 +08:00
wangdan-fit2cloud 3d9e7dd4b1 feat: optimization 2025-07-10 11:37:44 +08:00
CaptainB 8ff15865a7 fix: remove redundant import of pickle in function_code.py
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-07-09 15:02:06 +08:00
wanghe-fit2cloud 48899d55d1 docs: Add some badges
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-07-07 13:15:57 +08:00
CaptainB 1cc4107bfe chore: remove ignore rule for pymupdf dependency in dependabot configuration
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-07-04 11:38:01 +08:00
CaptainB b13cd03706 fix: prevent usage of 'stdio' in MCP server configuration
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-06-24 11:46:53 +08:00
liuchang_cloud 69f024492b
fix(documet):upgrade document updatetime when upgrade document_char_length (#3211)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
fixed #2942
2025-06-20 10:17:25 +08:00
Mr.zhao a9c46cd7e0
fix: Reorganize the model cache (#3315)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-06-19 18:01:45 +08:00
CaptainB a9e9f5b085 chore: add ignore rule for pymupdf dependency in dependabot configuration
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-06-16 13:07:33 +08:00
wxg0103 e12b1fe14e refactor: gemini
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-06-05 09:11:06 +08:00
shaohuzhang1 7ce66a7bf3
perf: Function library execution (#3175)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-05-30 18:56:01 +08:00
zhangshaohu decd3395db build: Sensitive directory permissions 2025-05-22 21:59:06 +08:00
shaohuzhang1 9d7a383348
fix: The dropdown data of subsequent nodes in the form cannot be displayed back (#3131)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-05-22 14:36:10 +08:00
shaohuzhang1 8f7d91798b
fix: The dropdown data of subsequent nodes in the form cannot be displayed back (#3129) 2025-05-22 10:54:25 +08:00
wxg0103 81a3af2c8b refactor: openai token 2025-05-22 09:11:12 +08:00
wxg0103 2ec0d22b14 refactor: openai token
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-05-21 18:50:49 +08:00
wxg0103 27a77dc657 refactor: openai token 2025-05-21 18:33:56 +08:00
shaohuzhang1 187e9c1e4e
build: Sensitive directory permissions (#3127) 2025-05-21 17:39:09 +08:00
shaohuzhang1 e5bab10824
build: Sensitive directory permissions (#3126) 2025-05-21 16:43:46 +08:00
shaohuzhang1 347f4a0b03
fix: The default suffix for workflow file upload nodes is set to uppercase DOC and PPT (#3125)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-05-21 13:25:46 +08:00
shaohuzhang1 289ebf42a6
fix: Verification code is not case sensitive (#3121) 2025-05-21 11:56:58 +08:00
shaohuzhang1 71fdce08d7
build: Sensitive directory permissions (#3120) 2025-05-21 11:45:38 +08:00
shaohuzhang1 adc5af9cef
fix: Upload a file in the application dialogue, with a file name containing HTML character entities. After uploading, the file_id of the file is empty #3070 (#3119) 2025-05-21 11:40:48 +08:00
wangdan-fit2cloud ce2ab322f6 fix: modification of copywriting 2025-05-21 11:15:20 +08:00
shaohuzhang1 a7e31b94c7
fix: AI conversation jumps to 404 (#3118) 2025-05-21 10:58:00 +08:00
wangdan-fit2cloud 8498687794 fix: captcha style 2025-05-21 10:48:27 +08:00
wxg0103 190ca3e198 refactor: login captcha 2025-05-21 10:34:01 +08:00
shaohuzhang1 c1ddec1a61
feat: Login and add graphic captcha (#3117)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-05-20 20:16:36 +08:00
wangdan-fit2cloud 1ba8077e95 fix: optimize markdown style 2025-05-20 19:34:48 +08:00
wxg0103 9a42bd2302 refactor: add client_id 2025-05-20 18:49:22 +08:00
shaohuzhang1 35b662a52d
perf: Optimize document extraction for complex table files (#3116)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-05-20 13:44:20 +08:00
wangdan-fit2cloud a4faf52261
fix: Parameter setting page optimization (#3115) 2025-05-20 11:46:16 +08:00
wangdan-fit2cloud a30316d87a
fix: breadcrumb issue (#3111)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-05-19 18:18:36 +08:00
wangdan-fit2cloud a4d10cbe3b
fix: Application of multi-file style issues and modification of copywriting (#3110) 2025-05-19 16:39:45 +08:00
wangdan-fit2cloud ceccf9f1fa
fix: Line break truncated characters in English(#3103) 2025-05-19 16:38:26 +08:00
shaohuzhang1 3964db20dc
fix: Advanced orchestration defines global variables and assigns them through assignment nodes. If there is a form collection node before the reference node, the assignment node becomes invalid and the initial variable value is still referenced #3082 (#3108) 2025-05-19 14:36:40 +08:00
shaohuzhang1 57ada0708f
fix: When the function parameter is of non string type, if the parameter is empty, the function will run with an error #3053 (#3107) 2025-05-19 14:13:49 +08:00
shaohuzhang1 a1a92a833a
fix: Excel export prohibits inputting external links or formulas (#3106) 2025-05-19 14:04:16 +08:00
shaohuzhang1 5e0d8048f9
fix: Excel export prohibits inputting external links or formulas (#3105) 2025-05-19 13:51:13 +08:00
shaohuzhang1 8903b35aec
fix: dialogue log export error (#3100)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-05-16 13:22:10 +08:00
shaohuzhang1 fa4f7e99fd
perf: Refine the Model Manager code (#3099) 2025-05-16 12:37:52 +08:00
shaohuzhang1 b0630b3ddd
perf: Refine the Model Manager code (#3098) 2025-05-16 12:09:39 +08:00
shaohuzhang1 b2bf69740c
perf: Refine the Model Manager code (#3094)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-05-15 22:47:46 +08:00
shaohuzhang1 8cf66b9eca
perf: Refine the Model Manager code (#3093) 2025-05-15 22:38:20 +08:00
shaohuzhang1 1db8577ca6
perf: Refine the Model Manager code (#3091) 2025-05-15 21:53:10 +08:00
shaohuzhang1 949e4dea9e
perf: Refine the Model Manager code (#3089) 2025-05-15 21:22:41 +08:00
CaptainB c12988bc8a fix: update MCP server configuration text to include STREAMABLE_HTTP support
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
--bug=1056115 --user=刘瑞斌 【应用编排】编辑mcp节点的config后,提问会报错 https://www.tapd.cn/57709429/s/1699333
--bug=1056092 --user=刘瑞斌 【应用编排】MCP的支持文案要加上支持 Streamable HTTP https://www.tapd.cn/57709429/s/1699357
2025-05-15 16:48:25 +08:00
shaohuzhang1 2728453f6c
fix: The workflow includes a form collection node. After the normal Q&A on the demonstration page is completed, refreshing the page will display the output of the nodes before the form collection node (#3081) 2025-05-14 10:06:07 +08:00
shaohuzhang1 ccf43bbcd9
feat: add pool options recycle (#3080) 2025-05-13 16:56:06 +08:00
CaptainB 8d8de53e38 chore: update langchain-mcp-adapters and mcp package versions
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-05-09 16:28:50 +08:00
CaptainB 7faf556771 refactor: remove unused initialization parameters from function library update
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
--bug=1055178 --user=刘瑞斌 【函数】启用的函数,禁用后再启用,函数的部分启用参数会被置为X https://www.tapd.cn/57709429/s/1694852
2025-05-08 13:05:34 +08:00
wxg0103 76ec8ad6f6 refactor: regolo icon
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-05-07 10:51:43 +08:00
Daniele Scasciafratte 0cf05a76a0
feat(provider): added regolo.ai (#3041) 2025-05-07 10:12:47 +08:00
CaptainB 357edbfbe0 ci: remove redundant pip package ecosystem configuration from dependabot
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-04-30 11:21:23 +08:00
CaptainB 0609a9afc8 ci: add pip package ecosystem support to dependabot configuration 2025-04-30 11:19:16 +08:00
shaohuzhang1 96e59a018f
fix: The simple application demonstration cannot be opened (#3028)
Some checks failed
Typos Check / Spell Check with Typos (push) Waiting to run
sync2gitee / repo-sync (push) Has been cancelled
2025-04-29 22:12:41 +08:00
shaohuzhang1 79b2de8893
feat: Ali Bailian supplier model list adds qwen3 model (#3026) 2025-04-29 19:08:17 +08:00
shaohuzhang1 0c7cca035e
fix: Cannot copy applications with opening statement characters exceeding 4096 (#3022)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-29 15:26:13 +08:00
shaohuzhang1 00a3e5ddc3
fix: The similarity of knowledge base mixed retrieval should be 0-2 (#3021) 2025-04-29 15:18:19 +08:00
shaohuzhang1 a6533c0db7
feat: Support Alibaba Bailian supplier qwen3 model (#3020) 2025-04-29 14:57:41 +08:00
shaohuzhang1 704077d066
fix: Workflow application node parameter saving cannot be reflected back (#3019) 2025-04-29 14:35:36 +08:00
shaohuzhang1 59ee0c1270
fix: During the conversation, there is sensitive data in the application profile obtained by the application (#3018) 2025-04-29 13:40:15 +08:00
wangdan-fit2cloud b37cc3ba1c
perf: The style of MCP nodes when there is no data (#3017) 2025-04-29 11:34:27 +08:00
yuxiaobin dfe6d0a91b
add support for docx, xlsx (#3014) 2025-04-29 11:25:12 +08:00
shaohuzhang1 5813eedd4f
perf: Slow dialogue log query (#3016) 2025-04-29 11:24:22 +08:00
liqiang-fit2cloud 363150380d build: update build-and-push.yml.
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-28 14:55:26 +08:00
liqiang-fit2cloud 47f9c04664 build(deps): update pyproject.toml. 2025-04-25 15:55:01 +08:00
dependabot[bot] 17cd88edda build(deps): update langchain-anthropic requirement
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
Updates the requirements on [langchain-anthropic](https://github.com/langchain-ai/langchain) to permit the latest version.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain-anthropic==0.3.10...langchain-anthropic==0.3.12)

---
updated-dependencies:
- dependency-name: langchain-anthropic
  dependency-version: 0.3.12
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-25 12:04:22 +08:00
dependabot[bot] d33b620dc8 build(deps): update langchain-ollama requirement from 0.3.1 to 0.3.2
Updates the requirements on [langchain-ollama](https://github.com/langchain-ai/langchain) to permit the latest version.
- [Release notes](https://github.com/langchain-ai/langchain/releases)
- [Commits](https://github.com/langchain-ai/langchain/compare/langchain-ollama==0.3.1...langchain-ollama==0.3.2)

---
updated-dependencies:
- dependency-name: langchain-ollama
  dependency-version: 0.3.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-25 12:02:46 +08:00
CaptainB b95cb20704 ci: add pip package ecosystem support to dependabot configuration 2025-04-25 11:44:15 +08:00
shaohuzhang1 4ff1944b60
fix: User input parameter occupancy issue (#2976)
Some checks failed
Typos Check / Spell Check with Typos (push) Waiting to run
sync2gitee / repo-sync (push) Has been cancelled
2025-04-24 17:39:44 +08:00
wangdan-fit2cloud 1c6b0f8a86 fix: api input bug 2025-04-24 17:32:22 +08:00
shaohuzhang1 d85801fe58
build: ui build error (#2975) 2025-04-24 16:39:01 +08:00
shaohuzhang1 e79e7d505d
feat: Add local model worker parameters (#2974) 2025-04-24 16:22:01 +08:00
shaohuzhang1 33b1cd65b0
fix: Ollama maximum output token field (#2973) 2025-04-24 16:16:36 +08:00
CaptainB 8d503c8bf8 fix: update post_cell function to handle different newline characters in cell values
--bug=1054683 --user=刘瑞斌 【github#2831】知识库上传excel、应用编排文档内容提取节点中上传excel,单元格中有换行,导入后没有在一个单元格里显示 https://www.tapd.cn/57709429/s/1690232
2025-04-24 16:05:09 +08:00
wangdan-fit2cloud df7f922013 fix: fix api input bug 2025-04-24 15:27:41 +08:00
liqiang-fit2cloud a0541203e4 deps: lock versions.
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-24 13:38:59 +08:00
liqiang-fit2cloud fb64731cd8 deps: lock versions. 2025-04-24 13:31:27 +08:00
shaohuzhang1 d960a18711
fix: Thinking about process labels during simple application debugging does not take effect (#2971) 2025-04-24 13:11:28 +08:00
liqiang-fit2cloud ee35cc21e9 build: update build-and-push.yml. 2025-04-24 12:54:36 +08:00
liqiang-fit2cloud e4a60daa17 deps: lock versions. 2025-04-24 12:45:49 +08:00
liqiang-fit2cloud 7bcb770ee5 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-24 12:33:38 +08:00
wangdan-fit2cloud b5b09dc8b4
fix: Fixed interface parameter transmission not displaying during debugging (#2969) 2025-04-24 11:24:44 +08:00
shaohuzhang1 b1f6092620
fix: The tag based thinking process does not output (#2968) 2025-04-24 10:57:10 +08:00
wxg0103 c8441cfd73 refactor: loading
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-23 16:04:57 +08:00
wangdan-fit2cloud 7e4b147576 fix: upload style 2025-04-23 15:35:54 +08:00
CaptainB 9cd082089a feat: update file extension display to uppercase in upload settings 2025-04-23 15:34:07 +08:00
wangdan-fit2cloud d4541e23f9 fix: Fix the issue of uploading and deleting other files 2025-04-23 15:02:05 +08:00
wxg0103 5e7e91cced refactor: loading 2025-04-23 13:53:04 +08:00
wangdan-fit2cloud d9787bb548 fix: style bug 2025-04-23 10:53:38 +08:00
CaptainB 131b5b3bbe feat: add existing file extensions warning and improve file type display in upload settings
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-23 10:48:29 +08:00
wxg0103 e58f95832b fix: aws computer tokens error
--bug=1055094 --user=王孝刚 【应用】aws模型在应用编排中使用时,回答内容会带一串报错信息,并且消耗tokens显示为0 https://www.tapd.cn/57709429/s/1689448
2025-04-23 10:41:18 +08:00
wxg0103 f24337d5f3 fix: The forgot password page displays an error
--bug=1055073 --user=王孝刚 【长城开发科技】自定平台登录设置在点击忘记密码时还是默认提示 https://www.tapd.cn/57709429/s/1689108
2025-04-23 10:05:46 +08:00
shaohuzhang1 d6f1d25b59
fix: Change the max_token of the Qianfan large model to max_output_token (#2955)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-22 18:10:49 +08:00
shaohuzhang1 0ec198fa43
fix: Lost historical conversation records (#2954)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-22 17:58:28 +08:00
wangdan-fit2cloud 77e96624ee
fix: upload file style (#2953) 2025-04-22 17:31:14 +08:00
CaptainB 5e02809db2 feat: add support for handling and displaying additional file types in the chat interface 2025-04-22 16:42:08 +08:00
shaohuzhang1 6fe001fcf8
fix: collection form function cannot be used normally and will be stuck in the answer #2857 (#2952) 2025-04-22 16:24:39 +08:00
CaptainB f646102262 fix: update file upload settings to allow dynamic video extensions and improve input handling for other file types 2025-04-22 16:20:01 +08:00
shaohuzhang1 b97f4e16ba
dependencies: Upgrade Django to 4.2.20 (#2950) 2025-04-22 14:58:31 +08:00
shaohuzhang1 0c14306889
fix: Docx segmented font title recognition (#2949) 2025-04-22 14:51:45 +08:00
wxg0103 f1d043f67b fix: The forgot password page displays an error
--bug=1055073 --user=王孝刚 【长城开发科技】自定平台登录设置在点击忘记密码时还是默认提示 https://www.tapd.cn/57709429/s/1689108
2025-04-22 14:19:43 +08:00
shaohuzhang1 c39a6e81d7
fix: When there is an empty string in the context, the conversation reports an error (#2947) 2025-04-22 13:59:53 +08:00
shaohuzhang1 9c56c7e198
fix: When automatic sending is not selected, the text conversion should be displayed in the position of the question input box (#2946) 2025-04-22 11:54:56 +08:00
shaohuzhang1 6484fef8ea
fix: Edit advanced orchestration applications, add application nodes, and display that some applications are unavailable after being added (#2945) 2025-04-22 11:25:47 +08:00
wangdan-fit2cloud 8a194481ac
feat: Adjust file upload and add other file function styles (#2944) 2025-04-22 11:12:42 +08:00
shaohuzhang1 072b817792
fix: OpenAI interface call for session, passing asker does not take effect (#2943) 2025-04-22 11:06:50 +08:00
CaptainB d32f7d36a6 feat: add support for uploading other file types and extend file upload settings
--story=1018411 --user=刘瑞斌 【工作流应用】文件上传可以支持其它文件类型 https://www.tapd.cn/57709429/s/1688679
2025-04-22 09:56:54 +08:00
wangdan-fit2cloud 54c9d4e725
feat: Support uploading files by copying, pasting, dragging and dropping (#2939) 2025-04-21 20:30:40 +08:00
shaohuzhang1 d2637c3de2
fix: Model parameters are not effective (#2937) 2025-04-21 18:06:09 +08:00
wxg0103 2550324003 refactor: oidc add field_mapping 2025-04-21 17:45:49 +08:00
wxg0103 bf52dd8174 fix: i18n error
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
--bug=1054853 --user=王孝刚 【API文档】-获取知识库文档分页列表接口的名称错误 https://www.tapd.cn/57709429/s/1688248
2025-04-21 10:20:44 +08:00
maninhill 2ecec57d2f
chore: Update README.md (#2927)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-04-18 17:34:11 +08:00
CaptainB b5fda0e020 chore: add dependabot configuration for weekly pip updates for v2
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-18 13:58:11 +08:00
liqiang-fit2cloud 45a60cd9a7 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-16 15:55:50 +08:00
maninhill 2b82675853
chore: Update README.md (#2897)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-04-16 12:15:24 +08:00
maninhill 1d3bf1ca73
chore: Update README_CN.md (#2893) 2025-04-16 09:50:28 +08:00
maninhill 04cb9c96fe
chore: Update README_CN.md (#2892) 2025-04-16 09:48:05 +08:00
liqiang-fit2cloud 45d8ac2eee Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-15 17:52:19 +08:00
maninhill eb60331c88
chore: Update README_CN.md (#2886)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-15 15:49:20 +08:00
liqiang-fit2cloud 8fc9b0a22d Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-15 15:24:31 +08:00
liqiang-fit2cloud ec5fd9e343 docs: change defaultSlogan.
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-15 14:37:10 +08:00
maninhill 1e49939c38
chore: Update README.md (#2884) 2025-04-15 14:27:10 +08:00
liqiang-fit2cloud 5a7b23aa00 docs: change defaultSlogan. 2025-04-15 14:23:32 +08:00
liqiang-fit2cloud 791505b7b8 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-15 13:50:17 +08:00
maninhill da10649adb Update README_CN.md 2025-04-15 12:57:07 +08:00
maninhill a025e3960d
chore: Update README_CN.md (#2880) 2025-04-15 12:51:12 +08:00
maninhill 98ed348de9
chore: Update README_CN.md (#2879) 2025-04-15 12:22:50 +08:00
maninhill b9dcc36b31
chore: Update README_CN.md (#2878) 2025-04-15 11:54:19 +08:00
liqiang-fit2cloud 7a3d3844ae Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-15 11:05:03 +08:00
maninhill 6ea2cf149a
chore: Update README_CN.md (#2874)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-14 19:18:44 +08:00
maninhill c30677d8b0
chore: Update README.md (#2873) 2025-04-14 19:02:23 +08:00
wxg0103 a1a2fb5628 fix: application title error
--bug=1054724 --user=王孝刚 【github#2855】【应用】修改应用名称后,演示界面及访问链接的web标签页名称还是旧名称 https://www.tapd.cn/57709429/s/1685555
2025-04-14 18:42:58 +08:00
CaptainB f9cb0e24d6 refactor: enhance input handling for custom sources in form fields
--bug=1054775 --user=刘瑞斌 【应用编排】MCP调用节点,自定义工具参数建议支持选择数据类型 https://www.tapd.cn/57709429/s/1685483
2025-04-14 17:43:42 +08:00
liqiang-fit2cloud 2dc42183cb Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-14 16:35:49 +08:00
wangdan-fit2cloud c781c11d26
fix: Application chat page style issue (#2866)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-14 15:11:55 +08:00
liqiang-fit2cloud e178cfe5c0 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-14 14:22:06 +08:00
CaptainB 3b24373cd0 fix: handle line breaks in cell content for markdown table formatting
--bug=1054683 --user=刘瑞斌 【github#2831】知识库上传excel、应用编排文档内容提取节点中上传excel,单元格中有换行,导入后没有在一个单元格里显示 https://www.tapd.cn/57709429/s/1685274
2025-04-14 14:21:51 +08:00
liqiang-fit2cloud 125ed8aa7a Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-14 11:01:00 +08:00
ivy 0b60a03e5d perf: refine copywriting
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-04-11 16:56:41 +08:00
liqiang-fit2cloud bb3f17ebfe Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-11 11:08:32 +08:00
maninhill ce7efd4758
chore: Update USE-CASES.md (#2850)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-10 17:11:50 +08:00
liqiang-fit2cloud 1695710cbe Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-10 16:42:08 +08:00
wangdan-fit2cloud b0366b18b6
fix: Optimize dialogue style (#2849) 2025-04-10 16:13:37 +08:00
wxg0103 a4f27249ed fix: openai error
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
2025-04-10 14:41:05 +08:00
liqiang-fit2cloud ebe8506c67 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-10 14:37:17 +08:00
shaohuzhang1 5243e42100
fix: MD editor table cannot have too many columns and cannot scroll horizontally (#2845) 2025-04-10 13:54:54 +08:00
wangdan-fit2cloud e5738f3b31
fix: History record error in dialogue basic mode (#2844) 2025-04-10 11:36:05 +08:00
liqiang-fit2cloud 3c3bd9884f Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-10 09:48:40 +08:00
shaohuzhang1 0213aff12a
fix: Historical user conversation data has been cleared (#2843)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-10 09:48:14 +08:00
shaohuzhang1 8dc793a128
fix: Historical user conversation data has been cleared (#2840)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-09 19:16:42 +08:00
shaohuzhang1 0861eb4cdc
fix: Invalid verification of dialogue user form (#2839)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-09 18:11:50 +08:00
wxg0103 d78c1459b7 refactor: oauth2 state 2025-04-09 17:56:45 +08:00
liqiang-fit2cloud f188383fea Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-09 17:33:23 +08:00
wangdan-fit2cloud 7421caba9b fix: Optimize details 2025-04-09 16:49:41 +08:00
liqiang-fit2cloud bbab359813 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-09 16:33:28 +08:00
shaohuzhang1 deb3844b4c
fix: Judgment tool drag and drop sorting (#2837) 2025-04-09 15:34:36 +08:00
maninhill 26f36ccee1
chore: Update README.md (#2835) 2025-04-09 15:15:25 +08:00
liqiang-fit2cloud 8381ca5287 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-09 15:08:32 +08:00
maninhill e8c1cdf959
chore: Update README.md (#2834) 2025-04-09 13:48:44 +08:00
wangdan-fit2cloud 675d2366da
fix: condition node supports drag and drop 2025-04-09 11:52:54 +08:00
wangdan-fit2cloud bbb63a5928
fix: chat avatar display issue fixed (#2832) 2025-04-09 10:56:58 +08:00
liqiang-fit2cloud f5282bf1e7 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-04-09 10:03:53 +08:00
liqiang-fit2cloud 4ae02c8d3e security: fix reverse shell vulnerability in function library.
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-09 10:01:11 +08:00
liqiang-fit2cloud c0ffc0aaf5 security: fix reverse shell vulnerability in function library. 2025-04-09 09:57:11 +08:00
shaohuzhang1 3557ea50fa
fix: Missing parameters such as character length when creating a knowledge base (#2827)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-08 17:24:33 +08:00
shaohuzhang1 43702e42b8
fix: When switching application models, the model parameters should be set to the default values of the current model (#2826) 2025-04-08 16:43:22 +08:00
wangdan-fit2cloud 7a3a975645
fix: After modifying the application name and publishing , name was not updated(#2799) 2025-04-08 16:00:24 +08:00
shaohuzhang1 9da7a553bf
fix: The form recall node cannot respond (#2824) 2025-04-08 15:50:57 +08:00
wangdan-fit2cloud 2d6d16e046
feat: Optimize the interface when there are too many interface parameters.(#2795)
* perf: Optimization drag sorting condition nodes

* feat: Optimize the interface when there are too many interface parameters.(#2795)
2025-04-08 15:10:53 +08:00
wangdan-fit2cloud 919a3eee5d fix: Fix type errors 2025-04-08 10:53:47 +08:00
shaohuzhang1 1a704f1c25
fix: Workflow debugging for authorized applications will result in an error message indicating unauthorized access to the model (#2819) 2025-04-08 10:51:35 +08:00
wxg0103 7ca0a7bd02 fix: Missing fields in exported logs
--bug=1054500 --user=王孝刚 【应用】对话日志导出,没有携带用户信息 https://www.tapd.cn/57709429/s/1681984
2025-04-08 10:12:20 +08:00
shaohuzhang1 2681d02728
fix: When the discriminator is true, there is no problem with the value (#2814)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-07 18:56:58 +08:00
shaohuzhang1 5e43bb9d2a
fix: jieba 分词使用全模式 (#2813) 2025-04-07 18:51:32 +08:00
wxg0103 ecd5fafbaa fix: The same observable object cannot appear twice in the same tree
--bug=1054451 --user=王孝刚 【应用嵌套】高级编排中应用关联设置用户输入参数的应用,会报该应用不可用 https://www.tapd.cn/57709429/s/1681701
2025-04-07 18:46:32 +08:00
wangdan-fit2cloud 23b47657c0
feat: The workflow condition node supports drag and drop sorting (#2648) 2025-04-07 18:19:38 +08:00
shaohuzhang1 76d050bea4
feat: New condition for determining whether the discriminator is true or not (#2809)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-07 14:07:30 +08:00
wxg0103 add9d1bab8 fix: In the permission settings, the knowledge base list does not distinguish between knowledge base types, and all are marked with the icon of the general knowledge base.
--bug=1054142 --user=王孝刚 【团队成员】权限设置中,知识库列表没有区分知识库类型,都用通用知识库的图标标识 https://www.tapd.cn/57709429/s/1681104
2025-04-07 13:53:39 +08:00
shaohuzhang1 9c36d8f30a
fix: Quick issue, API parameters cannot be carried (#2808) 2025-04-07 11:57:36 +08:00
wxg0103 189e2a6c63 fix: When adding a large language model in Application-Settings-Add AI node, the Base Model is not listed
--bug=1054358 --user=王孝刚 【应用】应用-设置-添加AI 节点时添加大语言模型,没有列出Base Model https://www.tapd.cn/57709429/s/1681102
2025-04-07 11:37:25 +08:00
shaohuzhang1 867c53984b
fix: The local rearrangement model cannot be loaded (#2805) 2025-04-07 11:22:01 +08:00
CaptainB 560890f717 fix: limit chapter title length to 256 characters in pdf_split_handle.py
--bug=1054363 --user=刘瑞斌 【知识库】导入PDF文档,分段标题长度超长时,没有自动截断 https://www.tapd.cn/57709429/s/1681044
2025-04-07 10:54:59 +08:00
CaptainB 675adeeb63 fix: exclude macOS specific files from zip processing
--bug=1054264 --user=刘瑞斌 【知识库】QA问答对模式,导入在mac上压缩的zip文件,会出现2个乱码文档 https://www.tapd.cn/57709429/s/1681034
2025-04-07 10:37:06 +08:00
shaohuzhang1 6bc00eb869
fix: During the debugging process, the browser voice will automatically play after returning to other pages (#2794)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-04-03 11:34:26 +08:00
shaohuzhang1 1eccb54199
fix: Knowledge base retrieval segmentation sorted by relevance (#2791) 2025-04-03 10:57:12 +08:00
wangdan-fit2cloud 86e11baeb2
perf: Optimization user input auto close when send message 2025-04-03 10:34:51 +08:00
shaohuzhang1 69ae1cafab
fix: After the page is redirected, the voice playback does not stop (#2789) 2025-04-03 09:53:54 +08:00
shaohuzhang1 9d6451b95b
fix: The interface for obtaining model metadata cannot access public models (#2787)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-02 20:10:20 +08:00
shaohuzhang1 add1cba8cb
feat: Swagger document response for adding OpenAI interface (#2786) 2025-04-02 19:59:57 +08:00
CaptainB 927f0c784a fix: remove multiple file upload option from file input in index.vue 2025-04-02 19:04:22 +08:00
shaohuzhang1 ac5a9d01a8
fix: When other users download the public model, they cannot obtain the download data (#2785) 2025-04-02 18:29:25 +08:00
CaptainB 7b213f547d fix: update default voice type and options in VolcanicEngineTTSModelGeneralParams 2025-04-02 18:25:06 +08:00
shaohuzhang1 7e4e2e98bb
fix: When answering a question - click to stop answering - wait until the question is approximately answered, then click to continue - it will remain in a stopped answering state (#2784) 2025-04-02 18:10:42 +08:00
wangdan-fit2cloud 21d2a44090
perf: Optimize application setting switch button control 2025-04-02 18:09:47 +08:00
shaohuzhang1 e364d6e373
fix: remove_form_rander (#2782) 2025-04-02 17:52:31 +08:00
shaohuzhang1 15feca802a
fix: OpenAI Vector Model Using Openai Supplier (#2781) 2025-04-02 17:45:18 +08:00
wxg0103 2686e76c8a fix: xinference rerank error
--bug=1054256 --user=王孝刚 【模型】添加硅基流动的重排序模型失败 https://www.tapd.cn/57709429/s/1679612
2025-04-02 16:54:58 +08:00
shaohuzhang1 6cf91098d6
fix: Dialogue embedding to modify name (#2780) 2025-04-02 16:49:52 +08:00
wangdan-fit2cloud 678a5ae4a5
fix: optimization user input 2025-04-02 16:42:58 +08:00
shaohuzhang1 a71c844ef4
fix: Copy and delete form information (#2778) 2025-04-02 16:36:14 +08:00
shaohuzhang1 74d10b61bc
fix: Unfiltered special characters in voice playback (#2777) 2025-04-02 16:12:17 +08:00
CaptainB 27bc01d442 fix: skip macOS specific metadata directories and files in zip parsing
--bug=1054264 --user=刘瑞斌 【知识库】QA问答对模式,导入在mac上压缩的zip文件,会出现2个乱码文档 https://www.tapd.cn/57709429/s/1679674
2025-04-02 16:06:36 +08:00
shaohuzhang1 bd900118f4
refactor: Dialogue logic optimization (#2776) 2025-04-02 15:51:15 +08:00
wxg0103 44c1d35b1f fix: siliconCloud rerank error
--bug=1054256 --user=王孝刚 【模型】添加硅基流动的重排序模型失败 https://www.tapd.cn/57709429/s/1679612
2025-04-02 15:17:39 +08:00
wangdan-fit2cloud f6994e16b9 fix: Fallback to fix issues 2025-04-02 14:56:30 +08:00
wxg0103 6d7b5eb219 fix: Increase log output 2025-04-02 14:17:48 +08:00
shaohuzhang1 27d4603b02
fix: Embedding dialogue asker parameter does not take effect (#2773)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-02 14:00:28 +08:00
shaohuzhang1 339e18d837
fix: After trying voice playback three times and sending an error message, an error message pops up (#2772) 2025-04-02 13:41:14 +08:00
CaptainB fb0fdb9c85 refactor: enhance MCP server configuration tooltips and validation messages
--bug=1054206 --user=刘瑞斌 【应用】MCP节点不配置Server config,不选择工具,可以发布应用,对话时报错 https://www.tapd.cn/57709429/s/1679457
2025-04-02 11:31:33 +08:00
wxg0103 5b2baaf04d fix: i18n error
--bug=1054232 --user=王孝刚 系统设置:操作菜单中的操作在中文模式下显示英文 https://www.tapd.cn/57709429/s/1679409
2025-04-02 10:46:39 +08:00
maninhill 739b977ce3
chore: Update README_CN.md (#2771) 2025-04-02 09:01:18 +08:00
maninhill 0315fe91df
chore: Update README.md (#2769) 2025-04-02 08:39:34 +08:00
wangdan-fit2cloud 02611588fc
fix: optimization chart operation style
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-01 19:41:01 +08:00
shaohuzhang1 2991f0b640
perf: Optimize word segmentation retrieval (#2767) 2025-04-01 19:11:16 +08:00
wxg0103 6fde8ec80f fix: openai need streaming 2025-04-01 16:58:08 +08:00
wangdan-fit2cloud 081fcab7eb
fix: Token conflict issue between two different applications embedded in the same system(#2753) 2025-04-01 16:19:13 +08:00
CaptainB 0e98c7783b refactor: add initialization parameters to function library update 2025-04-01 14:39:29 +08:00
wangdan-fit2cloud 812dc142c8
perf: Optimize some style issues 2025-04-01 14:21:44 +08:00
CaptainB 26946d0afb refactor: convert getList and getUserList functions to async for improved handling of asynchronous operations
--bug=1054152 --user=刘瑞斌 【函数库】筛选“我的”函数后刷新页面,还是显示的全部函数 https://www.tapd.cn/57709429/s/1678876
2025-04-01 13:03:22 +08:00
CaptainB ec6657177a refactor: conditionally render dropdown item based on init_field_list length
--bug=1054160 --user=刘瑞斌 【函数库】没有启用参数的函数,不展示启动参数按钮 https://www.tapd.cn/57709429/s/1678874
2025-04-01 13:03:22 +08:00
shaohuzhang1 4b9cecd4d1
feat: Knowledge base generation problem (#2760)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-04-01 12:46:30 +08:00
CaptainB 06867d33cb refactor: update MCP server config label for improved clarity 2025-04-01 10:55:48 +08:00
CaptainB b53a933327 refactor: update input source type from 'custom' to 'referencing'
--bug=1054147 --user=刘瑞斌 【MCP调用节点】工具参数默认方式是引用变量 https://www.tapd.cn/57709429/s/1678769
2025-04-01 10:33:02 +08:00
wangdan-fit2cloud 596b13711f
feat: function field form support sort
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-31 19:34:45 +08:00
shaohuzhang1 e7c3169898
feat: doc auth (#2752) 2025-03-31 19:02:56 +08:00
wxg0103 2612894557 refactor: oidc add state
--bug=1054135 --user=王孝刚 【认证】maxkb在调用okta的时候少个state参数,可以加上state参数 https://www.tapd.cn/57709429/s/1678654
2025-03-31 18:47:08 +08:00
CaptainB 7afc1da0af fix: handle non-string message types in function_lib_serializer 2025-03-31 18:29:01 +08:00
wxg0103 6aa0e9b5e4 refactor: i18n
--bug=1054121 --user=王孝刚 操作日志:修改应用的访问限制在操作日志中显示为修改应用的access token https://www.tapd.cn/57709429/s/1678535
2025-03-31 17:35:41 +08:00
CaptainB 24763427eb refactor: change mcp workflow node name 2025-03-31 17:29:00 +08:00
wangdan-fit2cloud da03442be2
fix: chat avatar display problem 2025-03-31 16:31:07 +08:00
shaohuzhang1 9750c6d605
fix: garbled zip import file names (#2747) 2025-03-31 16:22:39 +08:00
wxg0103 a2b6620b10 refactor: azure llm params 2025-03-31 15:31:52 +08:00
wangdan-fit2cloud b0a4e9e78f perf: Optimize copywriting 2025-03-31 15:23:02 +08:00
shaohuzhang1 36809b3314
fix: When uploading files during application dialogue, there are special whitespace characters and file name parsing errors #2738 (#2746) 2025-03-31 14:59:32 +08:00
wangdan-fit2cloud 165c27164e
fix: knowledge icon error 2025-03-31 14:40:52 +08:00
shaohuzhang1 1566ae7fbe
fix: Application deleted, workflow page error reported (#2743) 2025-03-31 14:31:53 +08:00
shaohuzhang1 b3feb243d3
fix: After setting the function library to private, the workflow used should report an error stating that the current function is not authorized to be used (#2742) 2025-03-31 13:58:38 +08:00
CaptainB cb104cc211 chore: fix typo
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-31 11:34:43 +08:00
shaohuzhang1 7a99c78840
fix: Processing for document export sheet with more than 32 characters (#2740) 2025-03-31 11:17:25 +08:00
shaohuzhang1 a07df46f9d
fix: Fix some knowledge base import errors (#2739) 2025-03-31 10:29:54 +08:00
CaptainB 4fa3fec103 ci: update some deps version
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-03-29 12:53:03 +08:00
CaptainB a2265cf357 chore: add tmp directory to .gitignore
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-28 18:45:35 +08:00
wxg0103 0b1990f8b3 refactor: lark 2025-03-28 18:25:36 +08:00
shaohuzhang1 bf91579b4e
fix: Fix some phones unable to play audio (#2728) 2025-03-28 18:24:00 +08:00
wxg0103 cc2789f37f fix: import add warning
--bug=1054090 --user=王孝刚 【知识库】飞书知识库-导入文档-未选择导入文件时,导入有提示信息 https://www.tapd.cn/57709429/s/1678039
2025-03-28 18:15:50 +08:00
wangdan-fit2cloud 3fe47e0fb3 perf: Optimize style 2025-03-28 18:09:19 +08:00
CaptainB 46e464b126 fix: conditionally render tooltip based on TTS availability in ChatOperationButton
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
--bug=1054047 --user=刘瑞斌 【应用】应用基本信息中未开启语音播放,演示应用时可以语音播放(播放有时候正确) https://www.tapd.cn/57709429/s/1678099
2025-03-28 17:22:00 +08:00
CaptainB 54c4293482 fix: ensure init_field_list defaults to an empty list in function_lib_serializer
--bug=1054092 --user=刘瑞斌 【函数库】导入历史版本的函数失败 https://www.tapd.cn/57709429/s/1678044
2025-03-28 15:53:06 +08:00
CaptainB a799026d52 fix: ensure init_field_list defaults to an empty list in function_lib_serializer
--bug=1054092 --user=刘瑞斌 【函数库】导入历史版本的函数失败 https://www.tapd.cn/57709429/s/1678044
2025-03-28 15:51:48 +08:00
wangdan-fit2cloud 5ceb8a7ff9
feat: Internal function can modify names 2025-03-28 15:33:44 +08:00
wxg0103 e7e4570aeb refactor: log add hover
--bug=1054088 --user=王孝刚 【操作日志】优化-操作详情建议一行展示,提供hover功能,hover后展示全部详情 https://www.tapd.cn/57709429/s/1678006
2025-03-28 15:01:27 +08:00
wxg0103 f69346f2bc refactor: i18n 2025-03-28 14:25:21 +08:00
shaohuzhang1 dcc80a4dca
fix: Every time a vectorized document is generated, the entire vectorized data of the document is deleted (#2721) 2025-03-28 14:15:48 +08:00
wxg0103 2fe4248785 refactor: qwq model 2025-03-28 14:05:33 +08:00
shaohuzhang1 bdaeb1bec4
perf: Optimization of recording function on mobile devices (#2719) 2025-03-28 13:52:54 +08:00
wangdan-fit2cloud 89be3e317d
fix: Optimize copywriting and style 2025-03-28 11:54:08 +08:00
CaptainB 2b079a4144 fix: update function names and descriptions for clarity in function_lib 2025-03-28 11:52:20 +08:00
wxg0103 4ffec85e9b refactor: Optimization of operation menu
--bug=1054069 --user=王孝刚 【操作日志】-操作菜单优化建议 https://www.tapd.cn/57709429/s/1677900
2025-03-28 11:45:51 +08:00
wangdan-fit2cloud bd098e68e5
fix: Fix markdown text 2025-03-28 10:28:51 +08:00
wxg0103 d282795644 refactor: i18n
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-28 09:31:48 +08:00
shaohuzhang1 5ba802482f
perf: Optimize voice recording (#2707)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-27 19:53:35 +08:00
wangdan-fit2cloud 378de21fa2
feat: User input optimization
* fix: Fix img 404

* feat: User input optimization
2025-03-27 19:42:01 +08:00
wxg0103 5eee6bfb6c fix: Modify download template prompt
--bug=1054044 --user=王孝刚 【知识库】上传文档-QA问答对-下载Excel/CSV模版-显示问题答案最长不超过4096个字符,实际上可以导入超过4096个字符的数据 https://www.tapd.cn/57709429/s/1677605
2025-03-27 18:50:12 +08:00
wxg0103 d47295aa36 fix: Defects that can be saved multiple times
--bug=1054039 --user=王孝刚 【飞书知识库】-导入文档时,快速点击开始导入按钮,会触发多次import请求,并且会跳离文档列表页 https://www.tapd.cn/57709429/s/1677588
2025-03-27 18:50:01 +08:00
CaptainB 0a3b9ee02b fix: refactor form item rendering for improved readability and structure
--bug=1054022 --user=刘瑞斌 【应用】MCP节点工具列表建议支持搜索 https://www.tapd.cn/57709429/s/1677517
--bug=1054029 --user=刘瑞斌 【应用】MCP工具参数必填时,参数置空可以发布应用 https://www.tapd.cn/57709429/s/1677571
2025-03-27 18:20:52 +08:00
CaptainB 97fb4a5cea fix: format MCP tool messages with detailed JSON representation
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-27 16:51:56 +08:00
wxg0103 80cd3ff7ec refactor: log menu 2025-03-27 16:24:45 +08:00
wxg0103 c66f79ad5a refactor: log menu 2025-03-27 16:14:24 +08:00
shaohuzhang1 3797613182
feat: Support converting text prompts (#2702) 2025-03-27 12:08:25 +08:00
CaptainB 7145f303da fix: update option list search logic to handle string and array form_data values
--bug=1053919 --user=刘瑞斌 【函数库】非必填启动参数开启启动参数后再次编辑多选置空 https://www.tapd.cn/57709429/s/1677136
2025-03-27 12:07:05 +08:00
wangdan-fit2cloud 754f7cb87c perf: dialog style 2025-03-27 11:42:59 +08:00
CaptainB 8a7e41be61 fix: enhance init_params check to ensure default values are present in init_field_list
--bug=1054001 --user=刘瑞斌 【函数库】创建时设置了默认启动参数,但依然弹出启动参数弹窗 https://www.tapd.cn/57709429/s/1677100
2025-03-27 11:12:21 +08:00
wangdan-fit2cloud 532ea4941a fix: radio style 2025-03-27 11:02:08 +08:00
CaptainB 286552d54b fix: update InitParamDrawer to handle is_active state in open method
--bug=1053998 --user=刘瑞斌 【函数库】首次开启函数并设置启动参数失败 https://www.tapd.cn/57709429/s/1677086
2025-03-27 11:00:23 +08:00
wxg0103 f82ba3c4b8 fix: qwq-plus only supported stream 2025-03-27 10:18:58 +08:00
CaptainB 0e29ce28cf chore: remove unused MCP response function from base_chat_node.py 2025-03-27 10:05:37 +08:00
wangdan-fit2cloud eed4d6857e
fix: Update lark icon and dialog style
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-26 20:46:14 +08:00
CaptainB 601b03d84e fix: ensure tool parameters are properly handled by reloading JSON
--bug=1053980 --user=刘瑞斌 【应用】MCP节点的工具参数引用参数时,对话过程中参数值发生变化,MCP节点没有获取最新的参数值 https://www.tapd.cn/57709429/s/1676830
2025-03-26 19:15:30 +08:00
wangdan-fit2cloud 8252febe22
fix: Fix style confusion 2025-03-26 18:56:11 +08:00
wangdan-fit2cloud 1a57e5897d
fix: Fix style confusion and update markdown file and icon
* fix: Fix style confusion

* fix: Update markdown file and icon
2025-03-26 18:55:39 +08:00
CaptainB 1e8e3a90aa fix: handle audio generation failure by raising an exception 2025-03-26 17:15:23 +08:00
CaptainB 2971406909 feat: add placeholder for MCP server configuration in form fields 2025-03-26 16:45:33 +08:00
shaohuzhang1 db772b1d1c
fix: Segmented filtering of paragraphs with empty parent title content (#2693)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-26 16:02:26 +08:00
wangdan-fit2cloud ca12d653a6
perf: Optimize pressing enter to line breaks on mobile 2025-03-26 16:00:19 +08:00
shaohuzhang1 a0ee5c9441
fix: Audit log login did not record user (#2691) 2025-03-26 15:41:11 +08:00
wxg0103 b1aa1f7a53 refactor: i18n 2025-03-26 15:35:03 +08:00
wangdan-fit2cloud 579604bd81
perf: Prompt word alignment issue(#2537) 2025-03-26 15:24:19 +08:00
wxg0103 a303f24974 refactor: i18n 2025-03-26 14:45:40 +08:00
wangdan-fit2cloud fadbd2fde0 fix: Update fx files 2025-03-26 14:25:34 +08:00
CaptainB bd7dbb13f3 fix: update icon paths in function library to use correct UI directory structure 2025-03-26 14:09:20 +08:00
shaohuzhang1 394d96980b
fix: Voice playback may cause playback issues (#2689) 2025-03-26 13:49:30 +08:00
CaptainB d27e5a4c01 chore: fix warning 2025-03-26 13:35:29 +08:00
CaptainB a6703c9889 fix: update icon paths in function library to use correct directory structure 2025-03-26 13:35:29 +08:00
wangdan-fit2cloud 545e80d655
fix: Static image loading path issue and Mobile setting information error 2025-03-26 12:55:59 +08:00
CaptainB 436e43dd04 feat: enhance form handling with dynamic input fields and reference content retrieval 2025-03-26 12:43:51 +08:00
CaptainB b8562ec736 fix: reset selectUserId on tab change in function library tabs
--bug=1053743 --user=刘瑞斌 【需求转缺陷】【内置函数】- 去掉按创建用户的搜索选项 https://www.tapd.cn/57709429/s/1676289
2025-03-26 11:00:30 +08:00
CaptainB 1b6b021226 fix: set is_active to False and update init_params handling in function_lib_serializer
--bug=1053923 --user=刘瑞斌 【函数库】创建函数后函数状态为启用,并非是禁用 https://www.tapd.cn/57709429/s/1676263

--bug=1053928 --user=刘瑞斌 【函数库】设置过启动参数的函数,删除启动参数,但实际依然有保留 https://www.tapd.cn/57709429/s/1676269
2025-03-26 10:55:35 +08:00
shaohuzhang1 55cdd0a708
fix: Zip with title cannot be parsed (#2683) 2025-03-26 10:31:31 +08:00
shaohuzhang1 f9d536f5a2
feat: Audit log add operation object (#2681)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-25 19:07:02 +08:00
wangdan-fit2cloud ba4e55d3e8
perf: refine user input style 2025-03-25 18:33:14 +08:00
CaptainB 3594fdadfa feat: add show_default_value attribute to input field in UserFieldFormDialog 2025-03-25 17:57:59 +08:00
CaptainB 83140b5f1d refactor: update label for API Key in input fields across function library
--bug=1053890 --user=刘瑞斌 【函数库】博查、Google、LangSearch查询函数的启动参数中,apikey建议改为 API Key https://www.tapd.cn/57709429/s/1675986
2025-03-25 17:34:15 +08:00
CaptainB 26cb55adfb fix: increase maximum length for input field in TextInputConstructor 2025-03-25 17:09:57 +08:00
wangdan-fit2cloud 440e2ba695
MCP node and internal function style
* feat: function

* feat: MCP node and internal  function style
2025-03-25 16:36:16 +08:00
CaptainB f19316639e chore: fix typo.
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-25 15:26:23 +08:00
CaptainB 564e781ba2 chore: fix typo. 2025-03-25 15:25:31 +08:00
wxg0103 9b46d29e73 refactor: lark document 2025-03-25 14:54:53 +08:00
CaptainB 6630589e8e fix: prevent default form submission in FunctionDebugDrawer
--bug=1053876 --user=刘瑞斌 【函数库】调试输入框回车后弹出新的浏览器tab页 https://www.tapd.cn/57709429/s/1675686
2025-03-25 13:23:02 +08:00
CaptainB 2f20868ca6 fix: handle empty message case in encryption function
--bug=1053881 --user=刘瑞斌 【函数库】-编辑函数,修改启动参数的必填选项后,保存函数报错 https://www.tapd.cn/57709429/s/1675682
2025-03-25 13:12:02 +08:00
CaptainB 64df9cf437 fix: improve required field validation for database connection parameters
--bug=1053849 --user=刘瑞斌 【函数库】- PgSQL 启动参数的先后顺序调整 https://www.tapd.cn/57709429/s/1675675
2025-03-25 12:53:46 +08:00
CaptainB 66b84a77b9 fix: conditionally display MCP Server Config field based on MCP enable status
--bug=1053873 --user=刘瑞斌 【AI对话节点】-工具设置中MCP关闭时,不显示server config配置 https://www.tapd.cn/57709429/s/1675665
2025-03-25 12:43:45 +08:00
wxg0103 d058d6d176 fix: function add required
--bug=1053859 --user=王孝刚 【函数库】函数内容给出必填提示 https://www.tapd.cn/57709429/s/1675529
2025-03-25 11:21:59 +08:00
wxg0103 5f289885f7 fix: lark document icon error
--bug=1053854 --user=王孝刚 【飞书】-创建完飞书知识库后,面板中飞书的icon未变 https://www.tapd.cn/57709429/s/1675539
2025-03-25 11:21:49 +08:00
wxg0103 82b06d130a fix: lark document i18n error
--bug=1053850 --user=王孝刚 [知识库]英文语言模式-添加飞书文档页面未国际化&提示的翻译错误 https://www.tapd.cn/57709429/s/1675482
2025-03-25 10:43:17 +08:00
CaptainB 0f0b6b976e feat: add MCP server config validation and user tips 2025-03-25 10:06:24 +08:00
ivy f681cb9b23 fix: Update characters 2025-03-24 20:53:46 +08:00
wangdan-fit2cloud ee5c8a455d feat: Setting avatar show or hide 2025-03-24 20:38:32 +08:00
wangdan-fit2cloud b01172b242 fix: lark document 2025-03-24 19:09:18 +08:00
shaohuzhang1 19b9e52a45
fix: Limit the number of retries for text to speech conversion (#2670) 2025-03-24 18:49:24 +08:00
CaptainB 563516f835 feat: AI chat node mcp server config 2025-03-24 18:38:54 +08:00
CaptainB 2d6ac806ff feat: add MCP server tools integration and UI components 2025-03-24 18:38:54 +08:00
CaptainB f9c4e96f97 fix: enhance validation for required form fields
--bug=1052705 --user=刘瑞斌 【工作流】添加”开关”按钮参数, 默认值为关闭,保存之后页面还会提示“请填写所填字段” https://www.tapd.cn/57709429/s/1675323
2025-03-24 18:10:42 +08:00
shaohuzhang1 66f674f651
fix: Voice playback is not played in order (#2668) 2025-03-24 17:54:45 +08:00
CaptainB 98ef6e5775 fix: improve error handling for MySQL connection
--bug=1053828 --user=刘瑞斌 【函数】内置的MySQL查询函数,在启动参数配错后,应用调用的返回结果为None,没有输出异常信息 https://www.tapd.cn/57709429/s/1675168
2025-03-24 17:50:02 +08:00
shaohuzhang1 ad452afa52
fix: When asker is empty, do not pass the current parameter (#2667) 2025-03-24 17:41:18 +08:00
CaptainB 0a148bff4b fix: always render subtitle for item username in template
--bug=1053754 --user=刘瑞斌 【内置函数】添加到函数库中后,未显示创建者 https://www.tapd.cn/57709429/s/1675159
2025-03-24 17:40:24 +08:00
CaptainB 9189a2ff2b fix: conditionally render user selection for public function type
--bug=1053743 --user=刘瑞斌 【需求转缺陷】【内置函数】- 去掉按创建用户的搜索选项 https://www.tapd.cn/57709429/s/1675143
2025-03-24 17:35:20 +08:00
liqiang-fit2cloud c0255beca2 build(deps): bump the pip group across 1 directory with 2 updates (#2654) 2025-03-24 16:50:23 +08:00
shaohuzhang1 c526a27796
build: build_error (#2662) 2025-03-24 16:02:43 +08:00
dependabot[bot] 13ce966caa
build(deps): bump the pip group across 1 directory with 2 updates (#2654)
Updates the requirements on [torch](https://github.com/pytorch/pytorch) and [gunicorn](https://github.com/benoitc/gunicorn) to permit the latest version.

Updates `torch` to 2.6.0
- [Release notes](https://github.com/pytorch/pytorch/releases)
- [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md)
- [Commits](https://github.com/pytorch/pytorch/compare/v2.2.1...v2.6.0)

Updates `gunicorn` to 23.0.0
- [Release notes](https://github.com/benoitc/gunicorn/releases)
- [Commits](https://github.com/benoitc/gunicorn/compare/22.0.0...23.0.0)

---
updated-dependencies:
- dependency-name: torch
  dependency-type: direct:production
  dependency-group: pip
- dependency-name: gunicorn
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 16:02:31 +08:00
shaohuzhang1 dcee1b6d55
feat: Text to speech support streaming playback (#2661) 2025-03-24 14:21:29 +08:00
wxg0103 0ce6dd0795 fix: Optimize the creation of models
--bug=1053742 --user=王孝刚 【模型管理】- 创建模型时,若在选择供应商列表选择了模型类型,进入供应商表单时,携带模型类型 https://www.tapd.cn/57709429/s/1674767
2025-03-24 11:50:38 +08:00
wxg0103 259d1c872b fix: type check error 2025-03-24 10:26:33 +08:00
ivy e7c2c9710a perf: Delete unnecessary files 2025-03-21 22:46:54 +08:00
wangdan-fit2cloud 0eebbb094c
feat: Optimize the mobile voice interaction experience
* fix: Optimize small screen dialogue style

* feat: Mobile voice conversation new UI

* feat: Optimize the mobile voice interaction experience

* feat: Optimize the mobile voice interaction experience
2025-03-21 16:57:04 +08:00
wxg0103 2faabbe392 refactor: bailian 2025-03-21 11:02:43 +08:00
wxg0103 e22fc95ee9 refactor: Optimization of Q&A page
--story=1018176 --user=王孝刚 【问答页面】优化对话页面显示 https://www.tapd.cn/57709429/s/1674146
2025-03-21 10:07:07 +08:00
CaptainB 72398408c5 ci: update langchain version 2025-03-20 18:55:43 +08:00
junjun 18e4647211 feat: add operate log ui 2025-03-20 16:53:03 +08:00
junjun 642a284b33 feat: export operate log 2025-03-20 16:41:59 +08:00
wxg0103 3951a15e9d refactor: i18n
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-03-20 14:49:23 +08:00
CaptainB 27561410e5 fix: handle param value is blank 2025-03-20 14:21:31 +08:00
CaptainB 47849fc1a5 fix: Add FlibInstance to allowed classes and use restricted_loads for deserialization 2025-03-20 13:53:37 +08:00
shaohuzhang1 f19ad24907
fix: Application import deserialization vulnerability #2604 (#2633) 2025-03-20 12:10:06 +08:00
shaohuzhang1 5baa141b89
fix: type-check (#2629) 2025-03-20 10:12:15 +08:00
wxg0103 ea0ab5e3d2 feat: support lark document
--story=1017908 --user=王孝刚 【知识库】 - X-Pack支持对接飞书文档 https://www.tapd.cn/57709429/s/1673069
2025-03-20 10:05:32 +08:00
junjun e995a663c8 feat: add operate log 2025-03-19 19:12:17 +08:00
shaohuzhang1 4a681297e0
fix: Simple application user did not successfully set up (#2625) 2025-03-19 18:23:05 +08:00
CaptainB a65fc39ae4 chore: mysql icon 2025-03-19 17:07:23 +08:00
junjun bcd41d0c19 feat: add operate log 2025-03-19 17:03:08 +08:00
wangdan-fit2cloud 96562b9f16
fix: Optimize small screen dialogue style 2025-03-19 16:07:02 +08:00
shaohuzhang1 470105f895
feat: Support application、setting、user log recorder (#2617) 2025-03-19 15:45:10 +08:00
wangdan-fit2cloud 7b51d08ea5
feat: Add mobile mode to the conversation, support ctrl/shift/cmd/opt+enter for new line (#2615) 2025-03-19 15:31:27 +08:00
CaptainB 9e62e81158 feat: Add functionality to create and manage internal functions with MySQL and PostgreSQL queries 2025-03-19 14:34:54 +08:00
shaohuzhang1 b7accc54e7
fix: Logging error for logged in user (#2613)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-19 12:07:46 +08:00
CaptainB 4b4b84a220 feat: Add functionality to create and manage internal functions with MySQL and PostgreSQL queries 2025-03-19 12:06:33 +08:00
shaohuzhang1 5ec94860b2
perf: Enhance Word parsing (#2612) 2025-03-19 12:04:43 +08:00
junjun 263c18ebca feat: Add Operate Log 2025-03-18 20:19:26 +08:00
junjun 4f06cfe1ab feat: Add Operate Log 2025-03-18 20:19:26 +08:00
shaohuzhang1 8b52927b4f
fix: The float values collected by the form will be treated as ints when the decimal part is 0, resulting in a type error (#2601)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-18 16:40:15 +08:00
shaohuzhang1 1a7f484a62
feat: User information in the application conversation log. (#2599) 2025-03-18 15:50:30 +08:00
CaptainB aab6a2cdf3 refactor: Improve parameter handling in function execution 2025-03-18 14:03:48 +08:00
wangdan-fit2cloud 3709d34343 fix: Fix chat question 2025-03-18 13:51:10 +08:00
CaptainB bf6cfface6 chore: add some encoder type 2025-03-18 13:16:28 +08:00
CaptainB 075646481d chore: fix some log recorder 2025-03-17 19:13:16 +08:00
CaptainB f6586a481b refactor: Add mysql function log recorder 2025-03-17 17:53:44 +08:00
CaptainB a3cd92c503 refactor: Add mysql function lib 2025-03-17 17:11:04 +08:00
CaptainB 11f63b4556 refactor: Add postgresql function lib 2025-03-17 17:11:04 +08:00
shaohuzhang1 000a3970e0
fix: Multi process startup document modification executed multiple times (#2581) 2025-03-17 17:00:51 +08:00
shaohuzhang1 be31989ab9
fix: Log annotation acquisition details file type data cannot be serialized (#2579) 2025-03-17 16:45:06 +08:00
wangdan-fit2cloud 460de60019
feat: Add import document view 2025-03-17 16:33:52 +08:00
dependabot[bot] 3a9780dc4f
build(deps): bump axios (#2553)
Bumps the npm_and_yarn group with 1 update in the /ui directory: [axios](https://github.com/axios/axios).


Updates `axios` from 0.28.1 to 1.8.3
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.28.1...v1.8.3)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-17 16:03:47 +08:00
CaptainB 9620817a8f feat: Support dataset log recorder 2025-03-17 15:34:41 +08:00
CaptainB 03a705fc93 feat: Support function lib log recorder 2025-03-17 15:34:41 +08:00
shaohuzhang1 d7ef3ba67b feat: Audit Log 2025-03-17 15:34:41 +08:00
wangdan-fit2cloud 80e7dac0c8
fix: Adjustment of workflow node name auto increment rule(#2562) 2025-03-17 14:41:18 +08:00
wangdan-fit2cloud 03274d9ee5
feat: Update function setting style 2025-03-17 14:04:25 +08:00
wxg0103 6a8d2c1f9c refactor: add enum 2025-03-17 10:33:51 +08:00
CaptainB 510fae6bf1 feat: Support internal functionlib
--story=1017939 --user=刘瑞斌 【函数库】- 支持 系统内置函数 https://www.tapd.cn/57709429/s/1665943
2025-03-17 10:30:25 +08:00
CaptainB ad0b032384 feat: Support functionlib icon and init_fields
--story=1017947 --user=刘瑞斌 【函数库】- 函数支持配置参数及操作优化 https://www.tapd.cn/57709429/s/1664936
2025-03-17 10:30:25 +08:00
wangdan-fit2cloud a09f5c0577
perf: User input interaction style optimization 2025-03-14 19:06:03 +08:00
wxg0103 b8960d57c8 fix: schema access failure to https 2025-03-14 16:26:15 +08:00
wxg0103 f6e089daee feat: support three-party password-free login
--story=1018017 --user=王孝刚 【登录认证】-X-Pack 支持三方应用(企业微信、钉钉、飞书)免密登录 https://www.tapd.cn/57709429/s/1669142
2025-03-13 11:49:05 +08:00
wangdan-fit2cloud 7bd1dfbdaa
fix: Optimization style and fit setting avatar save prompt error(#2523)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
* fix: Setting avatar save prompt error(#2523)

* perf: optimization style
2025-03-12 11:22:52 +08:00
wangdan-fit2cloud 175a80191e
fix: Setting avatar save prompt error(#2523) (#2535) 2025-03-11 15:55:14 +08:00
wangdan-fit2cloud 518202ae0e
perf: Form node support adjusting the order (#2533) 2025-03-11 14:51:57 +08:00
maninhill f1a1c40724
chore: Update USE-CASES.md (#2517)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-03-07 22:02:41 +08:00
shaohuzhang1 e11c550fc2
fix: Simple application model dialogue model error not returned (#2514)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
2025-03-07 14:15:26 +08:00
shaohuzhang1 82b566d580
fix: Having the same suffix for function node names can result in incorrect value retrieval (#2513) 2025-03-07 13:48:52 +08:00
shaohuzhang1 8b9998a53d
fix: When importing documents from a web knowledge base, if a failed document is encountered, the import will be interrupted (#2511) 2025-03-07 10:19:57 +08:00
wxg0103 96ab89adf3 fix: ui logo error 2025-03-06 18:35:37 +08:00
liqiang-fit2cloud d99c9ad1ee build: update build-and-push.yml. 2025-03-06 17:32:49 +08:00
liqiang-fit2cloud d918f96c66 build: update build-and-push.yml. 2025-03-06 17:31:15 +08:00
liqiang-fit2cloud 8b17afc6b3 build: update build-and-push.yml. 2025-03-06 16:36:51 +08:00
liqiang-fit2cloud c5e4bbf2ce build: update build-and-push.yml. 2025-03-06 16:15:43 +08:00
shaohuzhang1 bc3dcda1ec
fix: Team management remove button nationalization (#2504)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-03-05 18:40:09 +08:00
shaohuzhang1 b10147ea36
fix: Workflow embedding sub application thinking process switch cannot control thinking process output (#2502) 2025-03-05 15:18:41 +08:00
wangdan-fit2cloud a738932e68
perf: the form collection component optimized on the mobile(#2467) 2025-03-05 15:05:33 +08:00
shaohuzhang1 ea9c5e0ee8
fix: Model addition, error log printing (#2499) 2025-03-05 14:45:50 +08:00
wxg0103 83d51ea866 refactor: i18n 2025-03-05 13:58:27 +08:00
CaptainB e36f6e08f7 fix: simplify inputFieldConfig handling in UserInputFieldTable
--bug=1052676 --user=刘瑞斌 【应用】高级编排-设置-基本信息-修改用户输入标题后保存-未更新-再次进入后仍展示用户输入 https://www.tapd.cn/57709429/s/1662577
2025-03-05 13:48:09 +08:00
wangdan-fit2cloud 80d4442cd0 fix: workflow style 2025-03-05 12:40:31 +08:00
wxg0103 24f67a3c0f fix: csvTemplate error
--bug=1052711 --user=王孝刚 【知识库】英文模式-文档-QA问答对-下载的csv模版内容错误 https://www.tapd.cn/57709429/s/1661565
2025-03-05 12:19:30 +08:00
wangdan-fit2cloud 13f374e262
perf: Unified deletion confirm button 2025-03-05 10:56:04 +08:00
maninhill 2fc883dedc
chore: Update README_CN.md (#2493) 2025-03-04 21:51:28 +08:00
Yang Chen d60836798b
fix: rename isDisabledChart to isDisabledChat in chat-input-operate component (#2488) 2025-03-04 19:32:49 +08:00
shaohuzhang1 e420a01e0d
fix: Enterprise WeChat docking sub application cannot output thinking process (#2489) 2025-03-04 19:31:49 +08:00
wangdan-fit2cloud b6da5fb79b style: optimization table tooltip style 2025-03-04 19:06:12 +08:00
shaohuzhang1 2157f84c4f
fix: Reference segmentation limit (#2487) 2025-03-04 18:39:49 +08:00
CaptainB 801911d765 refactor: update QwenChatModel to use BaseChatOpenAI and remove unused methods
--bug=1052269 --user=刘瑞斌 【模型】对接千问模型,设置联网参数,maxkb对答不生效。通过python代码调用是可以的。 https://www.tapd.cn/57709429/s/1662132
2025-03-04 17:52:37 +08:00
shaohuzhang1 a75eb9fc86
fix: The newly created simple application dialogue process label is not effective (#2485) 2025-03-04 17:39:31 +08:00
maninhill ed8173e34d
chore: Update USE-CASES.md (#2484) 2025-03-04 16:20:50 +08:00
CaptainB 7e3f631dfc refactor: add model_name parameter to AzureChatModel initialization
--bug=1052553 --user=刘瑞斌 【模型】添加 Azure OpenAI 添加DeepSeek -R1 模型报错 https://www.tapd.cn/57709429/s/1661953
2025-03-04 16:08:20 +08:00
wxg0103 9deb3f6c49 fix: csvTemplate error
--bug=1052711 --user=王孝刚 【知识库】英文模式-文档-QA问答对-下载的csv模版内容错误 https://www.tapd.cn/57709429/s/1661565
2025-03-04 15:49:01 +08:00
wangdan-fit2cloud 1bfc005203
perf: optimization workflow interactive 2025-03-04 15:17:44 +08:00
liqiang-fit2cloud d413287ebc build: update pyproject.toml. 2025-03-04 15:12:02 +08:00
liqiang-fit2cloud d582507523 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-03-04 15:05:41 +08:00
shaohuzhang1 509055423a
fix: The page cannot continue streaming response when changing the conversation name in the conversation reply (#2480) 2025-03-04 14:58:29 +08:00
CaptainB 7faa79d361 refactor: disable buttons based on permission type
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
--bug=1052737 --user=刘瑞斌 【函数库】-非当前用户所有的函数,不应该支持复制和导出 https://www.tapd.cn/57709429/s/1661776
2025-03-04 14:15:10 +08:00
shaohuzhang1 9d5f9fde62
fix: Retrieve the dialogue record list document and confirm the parameters (#2479) 2025-03-04 14:13:36 +08:00
shaohuzhang1 c65ef97301
fix: Fixed the default value of not selecting 'select' after switching models (#2477) 2025-03-04 11:34:56 +08:00
CaptainB 13ce64e51a fix: Compatible with dashscope tts
--bug=1052708 --user=刘瑞斌 【应用】-使用阿里云语音合成模型sambert-zhichu-v1,对话报错 https://www.tapd.cn/57709429/s/1661551
2025-03-04 10:49:36 +08:00
CaptainB 5e563054f9 fix: Fix default_value not show when image model changed
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
--bug=1052695 --user=刘瑞斌 【应用】高级编排-设置-图片生成组件-切换模型后图片尺寸为空-未同步切换到当前模型的首个尺寸 https://www.tapd.cn/57709429/s/1661346
2025-03-03 18:49:09 +08:00
liqiang-fit2cloud 6805ebe38f Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-03-03 17:36:28 +08:00
wxg0103 e7f13871f8 fix: member list does not support ignoring case
--bug=1052688 --user=王孝刚 【团队成员】成员列表搜索不支持忽略大小写 https://www.tapd.cn/57709429/s/1661230
2025-03-03 16:19:30 +08:00
wxg0103 4fc429a8d1 fix: record log content error
--bug=1052670 --user=王孝刚 应用-企业微信对接应用后,在企业微信中与应用对话,对话日志中<think></think>思考过程输出了两次 https://www.tapd.cn/57709429/s/1661186
2025-03-03 16:12:43 +08:00
wangdan-fit2cloud 3c3f47bb5b fix: Similarity value error 2025-03-03 16:05:40 +08:00
wangdan-fit2cloud 685b01be01 fix: Optimization node rename dialog 2025-03-03 15:44:12 +08:00
wangdan-fit2cloud e8c559580a perf: Optimization codemirror title 2025-03-03 15:29:23 +08:00
liqiang-fit2cloud dea8c73cdd Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-03-03 15:06:11 +08:00
shaohuzhang1 dc79a22ba3
feat: Non streaming response answers_list (#2461) 2025-03-03 14:54:45 +08:00
CaptainB 7497c1b7cd fix: Fix user input table title not changed
--bug=1052676 --user=刘瑞斌 【应用】高级编排-设置-基本信息-修改用户输入标题后保存-未更新-再次进入后仍展示用户输入 https://www.tapd.cn/57709429/s/1661083
2025-03-03 14:42:39 +08:00
shaohuzhang1 7eff1c919b
fix: After generating the problem, jump to the first page (#2457) 2025-03-03 12:04:26 +08:00
CaptainB 7cdd188b06 refactor: rename function lib export subfix
--bug=1052638 --user=刘瑞斌 函数库导出后缀名优化 https://www.tapd.cn/57709429/s/1660837
2025-03-03 10:22:49 +08:00
maninhill bd5d129778
chore: Update README.md (#2452) 2025-03-01 09:31:24 +08:00
wangdan-fit2cloud 89792d5d3d
perf: optimization codemirror component 2025-02-28 22:42:23 +08:00
liqiang-fit2cloud 19ab61a1d4 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-02-28 18:04:19 +08:00
shaohuzhang1 aa4a834c85
fix: Document name sorting (#2448) 2025-02-28 17:56:12 +08:00
itaa ed8f8f8a3e
fix: ollama model provider can not set 'num_ctx' etc. parameter #2442 (#2444)
langchain-openai is not compatible with parameter Settings in ollama, such as num_ctx. Therefore, you need to create model instances using langchain-ollama

(cherry picked from commit 42ae7b443d)
2025-02-28 17:45:48 +08:00
wangdan-fit2cloud a64adc2504 perf: optimization style 2025-02-28 17:31:08 +08:00
liqiang-fit2cloud a528de752b Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-02-28 17:06:18 +08:00
wangdan-fit2cloud 8a02f62c70
perf: Optimization of chat input with line break when pressing enter 2025-02-28 16:47:48 +08:00
wangdan-fit2cloud 463ad49c9f perf: optimization translation and style 2025-02-28 15:34:45 +08:00
liqiang-fit2cloud 96b8898c05 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-02-28 15:21:49 +08:00
wangdan-fit2cloud ee7cc8058f
fix: [xpack]After replacing the logo,The original logo will flash(#2270)
* perf: Top N Segments increase to 10000

* fix: [xpack]After replacing the logo,The original logo will flash(#2270)
2025-02-28 15:21:24 +08:00
CaptainB dd84da4add fix: swagger show more args 2025-02-28 15:07:51 +08:00
liqiang-fit2cloud 2abf05f11a Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-02-28 12:28:40 +08:00
shaohuzhang1 72db45bcc0
fix: Changing the answer does not take effect (#2437) 2025-02-28 11:51:55 +08:00
shaohuzhang1 7d47f97354
fix: Sending error when extracting multiple forms and documents in the workflow (#2434) 2025-02-27 19:34:36 +08:00
wxg0103 badd722f9d refactor: i18n 2025-02-27 18:32:25 +08:00
wangdan-fit2cloud 880e171933
perf: Optimization of modifying node names in workflow 2025-02-27 18:18:18 +08:00
CaptainB 18fa06678c refactor: Cancel last autoplay audio
--story=1017953 --user=刘瑞斌 【优化】多轮对话,语音识别自动播放,会重叠播放 https://www.tapd.cn/57709429/s/1660001
2025-02-27 17:06:23 +08:00
wangdan-fit2cloud 54ebfc30f2 fix: Modify generate question page translation and bugs 2025-02-27 16:19:13 +08:00
CaptainB d5f867c76c refactor: Password input constractor 2025-02-27 16:12:10 +08:00
liqiang-fit2cloud f38af7f2e8 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-02-27 15:41:14 +08:00
wxg0103 51a29a997b fix: Knowledge source redirection failed 2025-02-27 15:16:34 +08:00
liqiang-fit2cloud b988d5abee Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-02-27 15:00:51 +08:00
shaohuzhang1 afaf3d6e26
fix: The workflow cannot function properly after the conversation flow is disconnected (#2428) 2025-02-27 14:21:47 +08:00
CaptainB c433c03fc0 fix: Fix variable assign num not working
--bug=1052497 --user=刘瑞斌 【变量赋值】-将全局变量赋值为num类型,输出时报错 https://www.tapd.cn/57709429/s/1659733
2025-02-27 12:08:24 +08:00
CaptainB c92ad06092 refactor: Support the generated images horizontal tiling display
--story=1017907 --user=刘瑞斌 【越秀农牧】图片生成节点,生成的图片,希望可以支持横向平铺展示 https://www.tapd.cn/57709429/s/1659636
2025-02-27 10:58:54 +08:00
liqiang-fit2cloud 01729157b7 Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-02-27 10:41:04 +08:00
shaohuzhang1 1811a80ecc
perf: enable gzip. (#2422) 2025-02-27 10:17:04 +08:00
wangdan-fit2cloud dc0ae4dc42 style: Optimize chat page style 2025-02-26 19:39:12 +08:00
CaptainB 32b7aa99c5 fix: Fix variable-assign-node cannot publish
--bug=1052464 --user=刘瑞斌 【应用】-编排应用中存在变量赋值节点,发布应用失败 https://www.tapd.cn/57709429/s/1659401
2025-02-26 17:43:53 +08:00
shaohuzhang1 9b93cca790
fix: Import zip report error (#2420) 2025-02-26 17:17:01 +08:00
shaohuzhang1 f65bfbe83c
fix: Left click to copy incomplete content (#2419) 2025-02-26 17:12:36 +08:00
wangdan-fit2cloud 66164e6cde fix: execution detail missing field 2025-02-26 16:48:31 +08:00
liqiang-fit2cloud 9a69d23e5a Merge branch 'main' of https://github.com/maxkb-dev/maxkb 2025-02-26 15:54:25 +08:00
liqiang-fit2cloud 68b9225818 perf: enable gzip. 2025-02-26 15:54:19 +08:00
wangdan-fit2cloud df3c0800f0 fix: generate question translate bugs 2025-02-26 15:27:26 +08:00
liqiang-fit2cloud 34cda84064 perf: enable gzip. 2025-02-26 15:20:07 +08:00
wangdan-fit2cloud c6a3024807
fix: Resolve the issue of a function returning 0 as a result (#2389) 2025-02-26 14:53:22 +08:00
wangdan-fit2cloud 62ab02ec0e fix: MsgConfirm distinguishCancelAndClose
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-02-26 12:55:06 +08:00
shaohuzhang1 eb6f4e8cb8
fix: VLLM Supplier Dialogue Verification (#2412) 2025-02-26 12:04:25 +08:00
shaohuzhang1 50dd8fae41
fix: Paragraph list unsorted (#2411) 2025-02-26 11:52:44 +08:00
shaohuzhang1 effe37fa6c
fix: URL encoding parameters not decompiled (#2409) 2025-02-26 11:30:04 +08:00
shaohuzhang1 413fa6f0c2
fix: When generating a problem, the generated data is incorrect (#2408) 2025-02-26 11:00:07 +08:00
shaohuzhang1 c6c3799d08
fix: the error in obtaining the document list (#2406) 2025-02-26 10:26:42 +08:00
wxg0103 62ae8d124b fix: The audio file does not display M4A
--bug=1052445 --user=王孝刚 【编排应用】基本信息的上传文件界面,音频文件中没有显示M4A格式 https://www.tapd.cn/57709429/s/1658969
2025-02-26 10:26:27 +08:00
shaohuzhang1 fdb2cbd9ab
fix: File upload failed (#2398) 2025-02-25 17:02:37 +08:00
shaohuzhang1 e1f0f39987
fix: When adding members in bulk, existing members will be automatically passed in, do not affect the joining of other members #2351 (#2397) 2025-02-25 16:54:29 +08:00
shaohuzhang1 218a247684
fix: Dialogue log export, question time does not match on the page (#2396) 2025-02-25 16:17:41 +08:00
CaptainB 5da758e8a1 fix: Fix import function lib permission
--bug=1052409 --user=刘瑞斌 【函数库】导入一个原来是公有的函数,函数的权限依然是公有 https://www.tapd.cn/57709429/s/1658705
2025-02-25 16:03:55 +08:00
CaptainB 666d58659e fix: Fix user cannot export public function lib
--bug=1052408 --user=刘瑞斌 【函数库】导出非当前用户创建的函数,报错 https://www.tapd.cn/57709429/s/1658696
2025-02-25 15:59:52 +08:00
CaptainB 9dbbe26b17 fix: Fix add function lib error
--bug=1052403 --user=刘瑞斌 【函数库】-创建函数失败 https://www.tapd.cn/57709429/s/1658691

--bug=1052404 --user=刘瑞斌 【函数库】新建函数,无法添加参数 https://www.tapd.cn/57709429/s/1658693
2025-02-25 15:46:22 +08:00
wxg0103 8dc5b94198 refactor: add i18n 2025-02-25 15:20:51 +08:00
wxg0103 09a80188ac refactor: add i18n 2025-02-25 15:20:01 +08:00
shaohuzhang1 8c45e92ee4
feat: The OpenAI interface supports the thought process (#2392) 2025-02-25 14:22:51 +08:00
CaptainB dfcb724502 refactor: Support string,json,num types 2025-02-25 14:05:58 +08:00
wxg0103 a01f21e3ac refactor: ollama reranker 2025-02-25 11:07:49 +08:00
CaptainB 52fb9be576 chore: Fix typo.
--bug=1052375 --user=刘瑞斌 【知识库】-生成问题的选择分段选项错误 https://www.tapd.cn/57709429/s/1657971
2025-02-25 10:59:01 +08:00
wxg0103 647c660476 fix: ui build error 2025-02-25 09:15:38 +08:00
shaohuzhang1 3aa5dd3694
feat: The demonstration page supports modifying dialogue summaries (#2348) 2025-02-24 18:53:21 +08:00
shaohuzhang1 5a3acc8649
fix: The locally loaded vector model is missing the libGL.so library (#2382) 2025-02-24 18:43:01 +08:00
wxg0103 9185515660 refactor: ollama support rerank 2025-02-24 18:35:37 +08:00
shaohuzhang1 f02b40b830
feat: Upgrade xinference client (#2381) 2025-02-24 18:23:26 +08:00
wangdan-fit2cloud 261915db29
perf: Add a tip before exiting the workflow 2025-02-24 17:53:06 +08:00
CaptainB 5eec0f7975 fix: Fix browser text to speech cannot resume and cancel
--bug=1052345 --user=刘瑞斌 【github#2352】【应用】使用浏览器内置TTS,切换历史记录内容播放会没有声音 https://www.tapd.cn/57709429/s/1657771
2025-02-24 17:37:12 +08:00
guqing 76e6b6e276
chore(i18n): optimize english translations for appearance settings 2025-02-24 17:30:57 +08:00
wangdan-fit2cloud 4367b1c650
feat: Create workflow applications that support template selection 2025-02-24 17:29:54 +08:00
shaohuzhang1 6b72611b72
fix: VLLM supplier recalculates token function (#2375) 2025-02-24 17:15:46 +08:00
shaohuzhang1 2d4deda6b4
docs: Create an application and add workflow parameters (#2374) 2025-02-24 16:33:35 +08:00
CaptainB 3c6b65baa1 fix: Remove vllm image cache
--bug=1052365 --user=刘瑞斌 【github#2353】vllm视觉模型修改最大tokens不生效 https://www.tapd.cn/57709429/s/1657667
2025-02-24 16:30:13 +08:00
shaohuzhang1 fa1886a17e
fix: Workflow execution decimal cannot be serialized (#2372) 2025-02-24 16:29:59 +08:00
maninhill f434dcfaf6
chore: Update README.md (#2371) 2025-02-24 16:12:35 +08:00
maninhill 8be93626c0
chore: Update README.md (#2369) 2025-02-24 16:09:06 +08:00
guqing 0ea642521c
chore(i18n): optimize english translations and standardize title case (#2367) 2025-02-24 15:52:40 +08:00
maninhill 9ed8155c95
chore: Update README.md (#2366) 2025-02-24 15:47:51 +08:00
maninhill 91dda3a84d
chore: Update README.md (#2365) 2025-02-24 15:42:15 +08:00
maninhill d6f999a5c8
chore: Update README.md (#2364) 2025-02-24 15:40:44 +08:00
maninhill 121c3e95c8
chore: Update README.md (#2363) 2025-02-24 15:39:53 +08:00
guqing d3902a51ca fix: drawer title for slack setting 2025-02-24 11:19:46 +08:00
shaohuzhang1 8477e957bd
fix: Function library execution role group (#2358) 2025-02-24 10:07:14 +08:00
wangdan-fit2cloud ad29a0d85b fix: type bugs 2025-02-21 15:07:16 +08:00
wangdan-fit2cloud a2e5180236
feat: User input parameters and interface parameters support adjusting the order(#2103)
* feat: table support sort(#2103)

* feat: User input parameters and interface parameters support adjusting the order(#2103)

* feat: User input parameters and interface parameters support adjusting the order(#2103)
2025-02-21 12:21:00 +08:00
刘瑞斌 df940686e9
Variable assign (#2346)
* feat: Support variable assign

* feat: Workfloat support variable assign(#2114)

* feat: Support variable assign save input output value

* feat: Execution detail support variable assign(#2114)

* feat: Support variable assign dict array value config

* chore: rename package

---------

Co-authored-by: wangdan-fit2cloud <dan.wang@fit2cloud.com>
2025-02-21 11:00:56 +08:00
shaohuzhang1 bb557fd187
fix: Child application node, input parameters too long, error reported during Q&A (#2347) 2025-02-21 10:28:02 +08:00
Harry f5155e7f7e
fix: bad code copy (#2341)
Co-authored-by: 杨树海 <yangshuhai@pdnews.cn>
2025-02-21 10:12:41 +08:00
Noven Zhen 66539a75dc
fix bug:when docker build image has bugs due to the multiple definition in django.po (#2337) 2025-02-21 10:10:06 +08:00
maninhill 6e4990167a
chore: Update README_CN.md (#2343) 2025-02-20 22:01:52 +08:00
wxg0103 cbc6fc4710 refactor: i18n 2025-02-20 17:59:45 +08:00
pq 4df183c5a3
perf: optimize embed mode (#2331) 2025-02-20 11:20:28 +08:00
guqing 097a24fbbd
feat(ui): add Slack app integration for MaxKB app
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-02-19 16:02:12 +08:00
wangdan-fit2cloud 33c07fdd33
fix: typos 2025-02-19 15:51:50 +08:00
wangdan-fit2cloud cc7f49fa8b
perf: Improving the conversation to a left-right layout(#2286)
* refactor: Optimize code

* feat: Improving the conversation to a left-right layout(#2286)
2025-02-19 15:48:04 +08:00
shaohuzhang1 2a84c58d4b
fix: Dialogue image cannot be enlarged #2296 (#2327) 2025-02-19 14:12:57 +08:00
shaohuzhang1 a06c5c097e fix: Tokens cannot be obtained from the model dialogue 2025-02-19 12:06:55 +08:00
wangdan-fit2cloud 0d96f797f1 feat: Multi channel recall execution details optimize style 2025-02-18 16:56:44 +08:00
shaohuzhang1 168f822416 refactor: Multi channel recall, adding display of knowledge base titles and other information 2025-02-18 16:56:44 +08:00
wangdan-fit2cloud ff3dec28d2
feat: Supports specified user input title(#2288) 2025-02-18 15:03:37 +08:00
wangdan-fit2cloud dd047747f6
perf: Close editor save content 2025-02-18 14:54:12 +08:00
shaohuzhang1 7f6c528291
feat: The discriminator supports variables (#2311) 2025-02-18 14:45:11 +08:00
shaohuzhang1 08c734b242
feat: Generate problem support for title variable (#2310) 2025-02-18 14:30:18 +08:00
CaptainB a16968d6e5 feat: Supports user input field type password
--story=1017895 --user=刘瑞斌 【用户输入】- 组件类型为文本框类型的支持密文显示#2134 https://www.tapd.cn/57709429/s/1655257
2025-02-18 12:57:06 +08:00
CaptainB c44fd8a40b feat: Supports specified user input title
--story=1017888 --user=刘瑞斌 【高级编排应用】“用户输入”可以自定义。 #2288 https://www.tapd.cn/57709429/s/1655214
2025-02-18 12:57:06 +08:00
wxg0103 f00c9ca611 fix: Complete document parameters
--bug=1052250 --user=王孝刚 【API】在swagger上调用对话API ,希望把参数给补充完整。(比如,上传文档的一些参数) https://www.tapd.cn/57709429/s/1655242
2025-02-18 11:19:59 +08:00
wxg0103 237dd8c209 refactor: oidc add scope
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-02-17 19:06:07 +08:00
wxg0103 7f597b6409 feat: support ollama rerank
--story=1017862 --user=王孝刚 希望支持在 Ollama 中添加 rerank 模型 issue#2243 https://www.tapd.cn/57709429/s/1655139
2025-02-17 18:38:49 +08:00
wxg0103 a071d7c89b fix: Alibaba Cloud full modal docking error
--bug=1052232 --user=王孝刚 【模型设置】-大语言模型/语音识别模型 对接不上阿里云的全模态模型 https://www.tapd.cn/57709429/s/1655034
2025-02-17 18:17:54 +08:00
wangdan-fit2cloud b917b72fe6
feat: Chat page retains the display of the last conversation 2025-02-17 17:14:45 +08:00
CaptainB 9249c1756f refactor: Support image_list, document_list while using swagger api
--story=1017867 --user=刘瑞斌 对话API支持上传文件、语音、图像和视频,用于实现maxkb的文件解析及多模态对话#2228 https://www.tapd.cn/57709429/s/1654842
2025-02-17 16:21:28 +08:00
shaohuzhang1 83cd69e5b7
feat: Generate problem support for generating unfinished paragraphs #2174 (#2299) 2025-02-17 15:49:41 +08:00
CaptainB f45855c34b feat: Import and Export function lib 2025-02-17 10:31:34 +08:00
wangdan-fit2cloud a1fca58864 perf: Optimize translation
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled
2025-02-14 18:02:50 +08:00
CaptainB 125e7b47e8 feat: Support switch input for application user params
--story=1017769 --user=刘瑞斌 【越秀农牧】希望用户输入可以添加一个开关组件 https://www.tapd.cn/57709429/s/1654429
2025-02-14 17:49:59 +08:00
CaptainB e2728ce8f7 feat: Support sorting feat for knowledge base documents
--story=1017634 --user=刘瑞斌 知识库文档列表排序功能,可以根据各列属性排序 https://www.tapd.cn/57709429/s/1654419
2025-02-14 17:49:48 +08:00
wxg0103 9c67f6bfe1 refactor: Search ignores capitalization
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
Typos Check / Spell Check with Typos (push) Waiting to run
--story=1017866 --user=王孝刚 【Bug转需求】【搜索】-函数库、模型、团队成员界面搜索不支持忽略大小写 #2126 https://www.tapd.cn/57709429/s/1654282
2025-02-14 10:56:02 +08:00
wxg0103 a8d79c5e60 fix: Defect of embedding application parameters as empty and reporting errors
--bug=1052184 --user=王孝刚 【github#2273】【应用】-有非必填接口传参的编排应用,作为节点加入新的编排应用后,接口传参为空时对话报错 https://www.tapd.cn/57709429/s/1654259
2025-02-14 10:39:28 +08:00
wxg0103 dd5db3eaa6 feat: audio support m4a
--story=1017864 --user=王孝刚 【应用】上传音频文件类型中支持m4a类型 #2276 https://www.tapd.cn/57709429/s/1654257
2025-02-14 10:39:17 +08:00
CaptainB c524fbc0e4 fix: Fix excel merge cells header 2025-02-14 10:26:18 +08:00
521 changed files with 26865 additions and 13654 deletions

17
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,17 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
timezone: "Asia/Shanghai"
day: "friday"
target-branch: "v2"
groups:
python-dependencies:
patterns:
- "*"
# ignore:
# - dependency-name: "pymupdf"
# versions: ["*"]

View File

@ -7,7 +7,7 @@ on:
inputs:
dockerImageTag:
description: 'Image Tag'
default: 'v1.10.0-dev'
default: 'v1.10.7-dev'
required: true
dockerImageTagWithLatest:
description: '是否发布latest tag正式发版时选择测试版本切勿选择'
@ -36,7 +36,7 @@ on:
jobs:
build-and-push-to-fit2cloud-registry:
if: ${{ contains(github.event.inputs.registry, 'fit2cloud') }}
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Check Disk Space
run: df -h
@ -64,18 +64,15 @@ jobs:
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest"
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*}"
else
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
fi
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=${GITHUB_SHA::8} --no-cache \
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \
${DOCKER_IMAGE_TAGS} .
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
# Until https://github.com/tonistiigi/binfmt/issues/215
image: tonistiigi/binfmt:qemu-v7.0.0-28
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
@ -92,11 +89,12 @@ jobs:
password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }}
- name: Docker Buildx (build-and-push)
run: |
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile
build-and-push-to-dockerhub:
if: ${{ contains(github.event.inputs.registry, 'dockerhub') }}
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Check Disk Space
run: df -h
@ -124,18 +122,15 @@ jobs:
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest"
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*}"
else
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
fi
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=${GITHUB_SHA::8} --no-cache \
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \
${DOCKER_IMAGE_TAGS} .
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
# Until https://github.com/tonistiigi/binfmt/issues/215
image: tonistiigi/binfmt:qemu-v7.0.0-28
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
@ -151,4 +146,5 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker Buildx (build-and-push)
run: |
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile

3
.gitignore vendored
View File

@ -183,4 +183,5 @@ apps/xpack
data
.dev
poetry.lock
apps/setting/models_provider/impl/*/icon/
apps/setting/models_provider/impl/*/icon/
tmp/

View File

@ -1,6 +1,6 @@
<p align="center"><img src= "https://github.com/1Panel-dev/maxkb/assets/52996290/c0694996-0eed-40d8-b369-322bf2a380bf" alt="MaxKB" width="300" /></p>
<h3 align="center">Ready-to-use, flexible RAG Chatbot</h3>
<h3 align="center">基于大模型和 RAG 的开源知识库问答系统</h3>
<h3 align="center">Open-source platform for building enterprise-grade agents</h3>
<h3 align="center">强大易用的企业级智能体平台</h3>
<p align="center"><a href="https://trendshift.io/repositories/9113" target="_blank"><img src="https://trendshift.io/api/badge/repositories/9113" alt="1Panel-dev%2FMaxKB | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a></p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html#license-text"><img src="https://img.shields.io/github/license/1Panel-dev/maxkb?color=%231890FF" alt="License: GPL v3"></a>
@ -11,12 +11,13 @@
</p>
<hr/>
MaxKB = Max Knowledge Base, it is a chatbot based on Large Language Models (LLM) and Retrieval-Augmented Generation (RAG). MaxKB is widely applied in scenarios such as intelligent customer service, corporate internal knowledge bases, academic research, and education.
MaxKB = Max Knowledge Brain, it is an open-source platform for building enterprise-grade agents. MaxKB integrates Retrieval-Augmented Generation (RAG) pipelines, supports robust workflows, and provides advanced MCP tool-use capabilities. MaxKB is widely applied in scenarios such as intelligent customer service, corporate internal knowledge bases, academic research, and education.
- **Ready-to-Use**: Supports direct uploading of documents / automatic crawling of online documents, with features for automatic text splitting, vectorization, and RAG (Retrieval-Augmented Generation). This effectively reduces hallucinations in large models, providing a superior smart Q&A interaction experience.
- **Flexible Orchestration**: Equipped with a powerful workflow engine and function library, enabling the orchestration of AI processes to meet the needs of complex business scenarios.
- **RAG Pipeline**: Supports direct uploading of documents / automatic crawling of online documents, with features for automatic text splitting, vectorization. This effectively reduces hallucinations in large models, providing a superior smart Q&A interaction experience.
- **Agentic Workflow**: Equipped with a powerful workflow engine, function library and MCP tool-use, enabling the orchestration of AI processes to meet the needs of complex business scenarios.
- **Seamless Integration**: Facilitates zero-coding rapid integration into third-party business systems, quickly equipping existing systems with intelligent Q&A capabilities to enhance user satisfaction.
- **Model-Agnostic**: Supports various large models, including private models (such as DeepSeek, Llama, Qwen, etc.) and public models (like OpenAI, Claude, Gemini, etc.).
- **Multi Modal**: Native support for input and output text, image, audio and video.
## Quick start
@ -53,6 +54,67 @@ Access MaxKB web interface at `http://your_server_ip:8080` with default admin cr
- LLM Framework[LangChain](https://www.langchain.com/)
- Database[PostgreSQL + pgvector](https://www.postgresql.org/)
## Feature Comparison
<table style="width: 100%;">
<tr>
<th align="center">Feature</th>
<th align="center">LangChain</th>
<th align="center">Dify.AI</th>
<th align="center">Flowise</th>
<th align="center">MaxKB <br>Built upon LangChain</th>
</tr>
<tr>
<td align="center">Supported LLMs</td>
<td align="center">Rich Variety</td>
<td align="center">Rich Variety</td>
<td align="center">Rich Variety</td>
<td align="center">Rich Variety</td>
</tr>
<tr>
<td align="center">RAG Engine</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">Agent</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">Workflow</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">Observability</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">SSO/Access control</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">✅ (Pro)</td>
</tr>
<tr>
<td align="center">On-premise Deployment</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</table>
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/MaxKB&type=Date)](https://star-history.com/#1Panel-dev/MaxKB&Date)

View File

@ -1,25 +1,25 @@
<p align="center"><img src= "https://github.com/1Panel-dev/maxkb/assets/52996290/c0694996-0eed-40d8-b369-322bf2a380bf" alt="MaxKB" width="300" /></p>
<h3 align="center">基于大模型和 RAG 的知识库问答系统</h3>
<h4 align="center">Ready-to-use, flexible RAG Chatbot</h4>
<h3 align="center">强大易用的企业级智能体平台</h3>
<p align="center">
<a href="https://trendshift.io/repositories/9113" target="_blank"><img src="https://trendshift.io/api/badge/repositories/9113" alt="1Panel-dev%2FMaxKB | Trendshift" style="width: 250px; height: auto;" /></a>
<a href="https://market.aliyun.com/products/53690006/cmjj00067609.html?userCode=kmemb8jp" target="_blank"><img src="https://img.alicdn.com/imgextra/i2/O1CN01H5JIwY1rZ0OobDjnJ_!!6000000005644-2-tps-1000-216.png" alt="1Panel-dev%2FMaxKB | Aliyun" style="width: 250px; height: auto;" /></a>
</p>
<p align="center">
<a href="README_EN.md"><img src="https://img.shields.io/badge/English_README-blue" alt="English README"></a>
<a href="https://www.gnu.org/licenses/gpl-3.0.html#license-text"><img src="https://img.shields.io/github/license/1Panel-dev/maxkb" alt="License: GPL v3"></a>
<a href="https://www.gnu.org/licenses/gpl-3.0.html#license-text"><img src="https://img.shields.io/github/license/1Panel-dev/maxkb?color=%231890FF" alt="License: GPL v3"></a>
<a href="https://github.com/1Panel-dev/maxkb/releases/latest"><img src="https://img.shields.io/github/v/release/1Panel-dev/maxkb" alt="Latest release"></a>
<a href="https://github.com/1Panel-dev/maxkb"><img src="https://img.shields.io/github/stars/1Panel-dev/maxkb?style=flat-square" alt="Stars"></a>
<a href="https://hub.docker.com/r/1panel/maxkb"><img src="https://img.shields.io/docker/pulls/1panel/maxkb?label=downloads" alt="Download"></a>
<a href="https://github.com/1Panel-dev/maxkb"><img src="https://img.shields.io/github/stars/1Panel-dev/maxkb?style=flat-square" alt="Stars"></a>
<a href="https://hub.docker.com/r/1panel/maxkb"><img src="https://img.shields.io/docker/pulls/1panel/maxkb?label=downloads" alt="Download"></a>
<a href="https://gitee.com/fit2cloud-feizhiyun/MaxKB"><img src="https://gitee.com/fit2cloud-feizhiyun/MaxKB/badge/star.svg?theme=gvp" alt="Gitee Stars"></a>
<a href="https://gitcode.com/feizhiyun/MaxKB"><img src="https://gitcode.com/feizhiyun/MaxKB/star/badge.svg" alt="GitCode Stars"></a>
</p>
<hr/>
MaxKB = Max Knowledge Base是一款基于大语言模型和 RAG 的开源知识库问答系统,广泛应用于智能客服、企业内部知识库、学术研究与教育等场景。
MaxKB = Max Knowledge Brain是一款强大易用的企业级智能体平台支持 RAG 检索增强生成、工作流编排、MCP 工具调用能力。MaxKB 支持对接各种主流大语言模型,广泛应用于智能客服、企业内部知识库问答、员工助手、学术研究与教育等场景。
- **开箱即用**:支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化和 RAG检索增强生成有效减少大模型幻觉智能问答交互体验好
- **模型中立**支持对接各种大模型包括本地私有大模型DeekSeek R1 / Llama 3 / Qwen 2 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等和国外公共大模型OpenAI / Claude / Gemini 等)
- **灵活编排**:内置强大的工作流引擎和函数库,支持编排 AI 工作过程,满足复杂业务场景下的需求
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度
- **RAG 检索增强生成**:高效搭建本地 AI 知识库,支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化,有效减少大模型幻觉,提升问答效果
- **灵活编排**:内置强大的工作流引擎、函数库和 MCP 工具调用能力,支持编排 AI 工作过程,满足复杂业务场景下的需求
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度
- **模型中立**支持对接各种大模型包括本地私有大模型DeepSeek R1 / Llama 3 / Qwen 2 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等和国外公共大模型OpenAI / Claude / Gemini 等)
MaxKB 三分钟视频介绍https://www.bilibili.com/video/BV18JypYeEkj/
@ -39,24 +39,17 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/po
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html)
- 如果您需要向团队介绍 MaxKB可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202501.pdf)。
- 如果您需要向团队介绍 MaxKB可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202503.pdf)。
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
- [案例展示](USE-CASES.md)
- [使用手册](https://maxkb.cn/docs/)
- [论坛求助](https://bbs.fit2cloud.com/c/mk/11)
- 技术交流群
<image height="150px" width="150px" src="https://github.com/1Panel-dev/MaxKB/assets/52996290/a083d214-02be-4178-a1db-4f428124153a"/>
## 案例展示
MaxKB 自发布以来,日均安装下载超过 1000 次,被广泛应用于智能客服、企业内部知识库、学术教育研究等场景。
- [华莱士智能客服](https://ai.cnhls.com/ui/chat/1fc0f6a9b5a6fb27)
- [JumpServer 小助手](https://maxkb.fit2cloud.com/ui/chat/b4e27a6e72d349a3)
- [重庆交通大学教务在线](http://jwc.anyquestion.cn/ui/chat/b75496390f7d935d)
## UI 展示
<table style="border-collapse: collapse; border: 1px solid black;">

View File

@ -1,12 +1,39 @@
<h3 align="center">MaxKB Use Cases</h3>
<h3 align="center">MaxKB 应用案例,持续更新中...</h3>
------------------------------
- [MaxKB 应用案例:深圳信用中心 AI 助手](https://www.bilibili.com/video/BV12H4y1c7bq)
- [MaxKB 应用案例:中国农业大学-小鹉哥](https://mp.weixin.qq.com/s/4g_gySMBQZCJ9OZ-yBkmvw)
- [MaxKB 应用案例:东北财经大学-小银杏](https://mp.weixin.qq.com/s/3BoxkY7EMomMmmvFYxvDIA)
- [MaxKB 应用案例:中铁水务](https://mp.weixin.qq.com/s/voNAddbK2CJOrJJs1ewZ8g)
- [MaxKB 应用案例:解放军总医院](https://mp.weixin.qq.com/s/ETrZC-vrA4Aap0eF-15EeQ)
- [MaxKB 应用案例:无锡市数据局](https://mp.weixin.qq.com/s/enfUFLevvL_La74PQ0kIXw)
- [MaxKB 应用案例:中核西仪研究院-西仪睿答](https://mp.weixin.qq.com/s/CbKr4mev8qahKLAtV6Dxdg)
- [MaxKB 应用案例:南京中医药大学](https://mp.weixin.qq.com/s/WUmAKYbZjp3272HIecpRFA)
- [MaxKB 应用案例:西北电力设计院-AI数字助理Memex](https://mp.weixin.qq.com/s/ezHFdB7C7AVL9MTtDwYGSA)
- [MaxKB 应用案例:西安国际医院中心医院-国医小助](https://mp.weixin.qq.com/s/DSOUvwrQrxbqQxKBilTCFQ)
- [MaxKB 应用案例华莱士智能AI客服助手上线啦](https://www.bilibili.com/video/BV1hQtVeXEBL)
- [把医疗行业知识转化为知识库问答助手!](https://www.bilibili.com/video/BV157wme9EgB)
- [MaxKB 应用案例:把医疗行业知识转化为知识库问答助手!](https://www.bilibili.com/video/BV157wme9EgB)
- [MaxKB 应用案例会展AI智能客服体验](https://www.bilibili.com/video/BV1J7BqY6EKA)
- [MaxKB 应用案例孩子要上幼儿园了AI 智能助手择校好帮手](https://www.bilibili.com/video/BV1wKrhYvEer)
- [MaxKB 应用案例产品使用指南AI助手新手小白也能轻松搞定](https://www.bilibili.com/video/BV1Yz6gYtEqX)
- [MaxKB 应用案例生物医药AI客服智能体验!](https://www.bilibili.com/video/BV13JzvYsE3e)
- [MaxKB 应用案例高校行政管理AI小助手](https://www.bilibili.com/video/BV1yvBMYvEdy)
- [MaxKB 应用案例:岳阳市人民医院-OA小助手](https://mp.weixin.qq.com/s/O94Qo3UH-MiUtDdWCVg8sQ)
- [MaxKB 应用案例:常熟市第一人民医院](https://mp.weixin.qq.com/s/s5XXGTR3_MUo41NbJ8WzZQ)
- [MaxKB 应用案例:华北水利水电大学](https://mp.weixin.qq.com/s/PoOFAcMCr9qJdvSj8c08qg)
- [MaxKB 应用案例:唐山海事局-“小海”AI语音助手](https://news.qq.com/rain/a/20250223A030BE00)
- [MaxKB 应用案例:湖南汉寿政务](http://hsds.hsdj.gov.cn:19999/ui/chat/a2c976736739aadc)
- [MaxKB 应用案例:广州市妇女儿童医疗中心-AI医疗数据分类分级小助手](https://mp.weixin.qq.com/s/YHUMkUOAaUomBV8bswpK3g)
- [MaxKB 应用案例:苏州热工研究院有限公司-维修大纲评估质量自查AI小助手](https://mp.weixin.qq.com/s/Ts5FQdnv7Tu9Jp7bvofCVA)
- [MaxKB 应用案例:国核自仪系统工程有限公司-NuCON AI帮](https://mp.weixin.qq.com/s/HNPc7u5xVfGLJr8IQz3vjQ)
- [MaxKB 应用案例深圳通开启Deep Seek智能应用新篇章](https://mp.weixin.qq.com/s/SILN0GSescH9LyeQqYP0VQ)
- [MaxKB 应用案例南通智慧出行领跑长三角首款接入DeepSeek的"畅行南通"APP上线AI新场景](https://mp.weixin.qq.com/s/WEC9UQ6msY0VS8LhTZh-Ew)
- [MaxKB 应用案例:中船动力人工智能"智慧动力云助手"及首批数字员工正式上线](https://mp.weixin.qq.com/s/OGcEkjh9DzGO1Tkc9nr7qg)
- [MaxKB 应用案例AI+矿山DeepSeek助力绿色智慧矿山智慧“升级”](https://mp.weixin.qq.com/s/SZstxTvVoLZg0ECbZbfpIA)
- [MaxKB 应用案例DeepSeek落地弘盛铜业国产大模型点亮"黑灯工厂"新引擎](https://mp.weixin.qq.com/s/Eczdx574MS5RMF7WfHN7_A)
- [MaxKB 应用案例:拥抱智能时代!中国五矿以 “AI+”赋能企业发展](https://mp.weixin.qq.com/s/D5vBtlX2E81pWE3_2OgWSw)
- [MaxKB 应用案例DeepSeek赋能中冶武勘AI智能体](https://mp.weixin.qq.com/s/8m0vxGcWXNdZazziQrLyxg)
- [MaxKB 应用案例重磅陕西广电网络“秦岭云”平台实现DeepSeek本地化部署](https://mp.weixin.qq.com/s/ZKmEU_wWShK1YDomKJHQeA)
- [MaxKB 应用案例粤海集团完成DeepSeek私有化部署助力集团智能化管理](https://mp.weixin.qq.com/s/2JbVp0-kr9Hfp-0whH4cvg)
- [MaxKB 应用案例建筑材料工业信息中心完成DeepSeek本地化部署推动行业数智化转型新发展](https://mp.weixin.qq.com/s/HThGSnND3qDF8ySEqiM4jw)
- [MaxKB 应用案例一起DeepSeek福建设计以AI大模型开启新篇章](https://mp.weixin.qq.com/s/m67e-H7iQBg3d24NM82UjA)

View File

@ -124,9 +124,11 @@ def event_content(response,
request_token = 0
response_token = 0
write_context(step, manage, request_token, response_token, all_text)
asker = manage.context.get('form_data', {}).get('asker', None)
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
all_text, manage, step, padding_problem_text, client_id,
reasoning_content=reasoning_content if reasoning_content_enable else '')
reasoning_content=reasoning_content if reasoning_content_enable else ''
, asker=asker)
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
[], '', True,
request_token, response_token,
@ -135,16 +137,21 @@ def event_content(response,
add_access_num(client_id, client_type, manage.context.get('application_id'))
except Exception as e:
logging.getLogger("max_kb_error").error(f'{str(e)}:{traceback.format_exc()}')
all_text = '异常' + str(e)
all_text = 'Exception:' + str(e)
write_context(step, manage, 0, 0, all_text)
asker = manage.context.get('form_data', {}).get('asker', None)
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
all_text, manage, step, padding_problem_text, client_id)
all_text, manage, step, padding_problem_text, client_id, reasoning_content='',
asker=asker)
add_access_num(client_id, client_type, manage.context.get('application_id'))
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), all_text,
'ai-chat-node',
[], True, 0, 0,
{'node_is_end': True, 'view_type': 'many_view',
'node_type': 'ai-chat-node'})
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
[], all_text,
False,
0, 0, {'node_is_end': False,
'view_type': 'many_view',
'node_type': 'ai-chat-node',
'real_node_id': 'ai-chat-node',
'reasoning_content': ''})
class BaseChatStep(IChatStep):
@ -300,19 +307,28 @@ class BaseChatStep(IChatStep):
else:
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get(
'reasoning_content')
asker = manage.context.get('form_data', {}).get('asker', None)
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
chat_result.content, manage, self, padding_problem_text, client_id,
reasoning_content=reasoning_content if reasoning_content_enable else '')
content, manage, self, padding_problem_text, client_id,
reasoning_content=reasoning_content if reasoning_content_enable else '',
asker=asker)
add_access_num(client_id, client_type, manage.context.get('application_id'))
return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id),
content, True,
request_token, response_token,
{'reasoning_content': reasoning_content})
{
'reasoning_content': reasoning_content if reasoning_content_enable else '',
'answer_list': [{
'content': content,
'reasoning_content': reasoning_content if reasoning_content_enable else ''
}]})
except Exception as e:
all_text = 'Exception:' + str(e)
write_context(self, manage, 0, 0, all_text)
asker = manage.context.get('form_data', {}).get('asker', None)
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
all_text, manage, self, padding_problem_text, client_id)
all_text, manage, self, padding_problem_text, client_id, reasoning_content='',
asker=asker)
add_access_num(client_id, client_type, manage.context.get('application_id'))
return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), all_text, True, 0,
0, _status=status.HTTP_500_INTERNAL_SERVER_ERROR)

View File

@ -43,7 +43,7 @@ class ISearchDatasetStep(IBaseChatPipelineStep):
error_messages=ErrMessage.float(_("Similarity")))
search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_("The type only supports register|reset_password"), code=500)
message=_("The type only supports embedding|keywords|blend"), code=500)
], error_messages=ErrMessage.char(_("Retrieval Mode")))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID")))

View File

@ -84,7 +84,8 @@ class WorkFlowPostHandler:
answer_text_list=answer_text_list,
run_time=time.time() - workflow.context['start_time'],
index=0)
self.chat_info.append_chat_record(chat_record, self.client_id)
asker = workflow.context.get('asker', None)
self.chat_info.append_chat_record(chat_record, self.client_id, asker)
# 重新设置缓存
chat_cache.set(chat_id,
self.chat_info, timeout=60 * 30)

View File

@ -24,11 +24,15 @@ from .search_dataset_node import *
from .speech_to_text_step_node import BaseSpeechToTextNode
from .start_node import *
from .text_to_speech_step_node.impl.base_text_to_speech_node import BaseTextToSpeechNode
from .variable_assign_node import BaseVariableAssignNode
from .mcp_node import BaseMcpNode
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode,
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode,
BaseConditionNode, BaseReplyNode,
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode,
BaseDocumentExtractNode,
BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,BaseImageGenerateNode]
BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,
BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode]
def get_node(node_type):

View File

@ -33,6 +33,9 @@ class ChatNodeSerializer(serializers.Serializer):
error_messages=ErrMessage.dict('Model settings'))
dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Context Type")))
mcp_enable = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean(_("Whether to enable MCP")))
mcp_servers = serializers.JSONField(required=False, error_messages=ErrMessage.list(_("MCP Server")))
class IChatNode(INode):
@ -49,5 +52,7 @@ class IChatNode(INode):
model_params_setting=None,
dialogue_type=None,
model_setting=None,
mcp_enable=False,
mcp_servers=None,
**kwargs) -> NodeResult:
pass

View File

@ -6,14 +6,19 @@
@date2024/6/4 14:30
@desc:
"""
import asyncio
import json
import re
import time
from functools import reduce
from types import AsyncGeneratorType
from typing import List, Dict
from django.db.models import QuerySet
from langchain.schema import HumanMessage, SystemMessage
from langchain_core.messages import BaseMessage, AIMessage
from langchain_core.messages import BaseMessage, AIMessage, AIMessageChunk, ToolMessage
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from application.flow.i_step_node import NodeResult, INode
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
@ -22,6 +27,19 @@ from setting.models import Model
from setting.models_provider import get_model_credential
from setting.models_provider.tools import get_model_instance_by_model_user_id
tool_message_template = """
<details>
<summary>
<strong>Called MCP Tool: <em>%s</em></strong>
</summary>
```json
%s
```
</details>
"""
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,
reasoning_content: str):
@ -56,6 +74,7 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
reasoning = Reasoning(model_setting.get('reasoning_content_start', '<think>'),
model_setting.get('reasoning_content_end', '</think>'))
response_reasoning_content = False
for chunk in response:
reasoning_chunk = reasoning.get_reasoning_content(chunk)
content_chunk = reasoning_chunk.get('content')
@ -84,6 +103,39 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
_write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)
async def _yield_mcp_response(chat_model, message_list, mcp_servers):
async with MultiServerMCPClient(json.loads(mcp_servers)) as client:
agent = create_react_agent(chat_model, client.get_tools())
response = agent.astream({"messages": message_list}, stream_mode='messages')
async for chunk in response:
if isinstance(chunk[0], ToolMessage):
content = tool_message_template % (chunk[0].name, chunk[0].content)
chunk[0].content = content
yield chunk[0]
if isinstance(chunk[0], AIMessageChunk):
yield chunk[0]
def mcp_response_generator(chat_model, message_list, mcp_servers):
loop = asyncio.new_event_loop()
try:
async_gen = _yield_mcp_response(chat_model, message_list, mcp_servers)
while True:
try:
chunk = loop.run_until_complete(anext_async(async_gen))
yield chunk
except StopAsyncIteration:
break
except Exception as e:
print(f'exception: {e}')
finally:
loop.close()
async def anext_async(agen):
return await agen.__anext__()
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
"""
写入上下文数据
@ -136,12 +188,15 @@ class BaseChatNode(IChatNode):
self.context['answer'] = details.get('answer')
self.context['question'] = details.get('question')
self.context['reasoning_content'] = details.get('reasoning_content')
self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,
model_params_setting=None,
dialogue_type=None,
model_setting=None,
mcp_enable=False,
mcp_servers=None,
**kwargs) -> NodeResult:
if dialogue_type is None:
dialogue_type = 'WORKFLOW'
@ -163,6 +218,14 @@ class BaseChatNode(IChatNode):
self.context['system'] = system
message_list = self.generate_message_list(system, prompt, history_message)
self.context['message_list'] = message_list
if mcp_enable and mcp_servers is not None and '"stdio"' not in mcp_servers:
r = mcp_response_generator(chat_model, message_list, mcp_servers)
return NodeResult(
{'result': r, 'chat_model': chat_model, 'message_list': message_list,
'history_message': history_message, 'question': question.content}, {},
_write_context=write_context_stream)
if stream:
r = chat_model.stream(message_list)
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,

View File

@ -11,13 +11,16 @@ from django.utils.translation import gettext_lazy as _
class ApplicationNodeSerializer(serializers.Serializer):
application_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Application ID")))
question_reference_address = serializers.ListField(required=True, error_messages=ErrMessage.list(_("User Questions")))
question_reference_address = serializers.ListField(required=True,
error_messages=ErrMessage.list(_("User Questions")))
api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("API Input Fields")))
user_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.uuid(_("User Input Fields")))
user_input_field_list = serializers.ListField(required=False,
error_messages=ErrMessage.uuid(_("User Input Fields")))
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture")))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document")))
audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio")))
child_node = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Child Nodes")))
child_node = serializers.DictField(required=False, allow_null=True,
error_messages=ErrMessage.dict(_("Child Nodes")))
node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data")))
@ -33,11 +36,16 @@ class IApplicationNode(INode):
self.node_params_serializer.data.get('question_reference_address')[1:])
kwargs = {}
for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):
kwargs[api_input_field['variable']] = self.workflow_manage.get_reference_field(api_input_field['value'][0],
api_input_field['value'][1:])
value = api_input_field.get('value', [''])[0] if api_input_field.get('value') else ''
kwargs[api_input_field['variable']] = self.workflow_manage.get_reference_field(value,
api_input_field['value'][
1:]) if value != '' else ''
for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):
kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(user_input_field['value'][0],
user_input_field['value'][1:])
value = user_input_field.get('value', [''])[0] if user_input_field.get('value') else ''
kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(value,
user_input_field['value'][
1:]) if value != '' else ''
# 判断是否包含这个属性
app_document_list = self.node_params_serializer.data.get('document_list', [])
if app_document_list and len(app_document_list) > 0:
@ -46,7 +54,8 @@ class IApplicationNode(INode):
app_document_list[1:])
for document in app_document_list:
if 'file_id' not in document:
raise ValueError(_("Parameter value error: The uploaded document lacks file_id, and the document upload fails"))
raise ValueError(
_("Parameter value error: The uploaded document lacks file_id, and the document upload fails"))
app_image_list = self.node_params_serializer.data.get('image_list', [])
if app_image_list and len(app_image_list) > 0:
app_image_list = self.workflow_manage.get_reference_field(
@ -54,7 +63,8 @@ class IApplicationNode(INode):
app_image_list[1:])
for image in app_image_list:
if 'file_id' not in image:
raise ValueError(_("Parameter value error: The uploaded image lacks file_id, and the image upload fails"))
raise ValueError(
_("Parameter value error: The uploaded image lacks file_id, and the image upload fails"))
app_audio_list = self.node_params_serializer.data.get('audio_list', [])
if app_audio_list and len(app_audio_list) > 0:
@ -63,7 +73,8 @@ class IApplicationNode(INode):
app_audio_list[1:])
for audio in app_audio_list:
if 'file_id' not in audio:
raise ValueError(_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails."))
raise ValueError(
_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails."))
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,
app_document_list=app_document_list, app_image_list=app_image_list,
app_audio_list=app_audio_list,

View File

@ -115,6 +115,10 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
'prompt_tokens': response.get('prompt_tokens')}}
answer = response.get('content', '') or "抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。"
reasoning_content = response.get('reasoning_content', '')
answer_list = response.get('answer_list', [])
node_variable['application_node_dict'] = {answer.get('real_node_id'): {**answer, 'index': index} for answer, index
in
zip(answer_list, range(len(answer_list)))}
_write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)
@ -164,7 +168,8 @@ class BaseApplicationNode(IApplicationNode):
self.context['question'] = details.get('question')
self.context['type'] = details.get('type')
self.context['reasoning_content'] = details.get('reasoning_content')
self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
app_document_list=None, app_image_list=None, app_audio_list=None, child_node=None, node_data=None,
@ -174,7 +179,8 @@ class BaseApplicationNode(IApplicationNode):
current_chat_id = string_to_uuid(chat_id + application_id)
Chat.objects.get_or_create(id=current_chat_id, defaults={
'application_id': application_id,
'abstract': message
'abstract': message[0:1024],
'client_id': client_id,
})
if app_document_list is None:
app_document_list = []
@ -220,20 +226,25 @@ class BaseApplicationNode(IApplicationNode):
def get_details(self, index: int, **kwargs):
global_fields = []
for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):
value = api_input_field.get('value', [''])[0] if api_input_field.get('value') else ''
global_fields.append({
'label': api_input_field['variable'],
'key': api_input_field['variable'],
'value': self.workflow_manage.get_reference_field(
api_input_field['value'][0],
api_input_field['value'][1:])
value,
api_input_field['value'][1:]
) if value != '' else ''
})
for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):
value = user_input_field.get('value', [''])[0] if user_input_field.get('value') else ''
global_fields.append({
'label': user_input_field['label'],
'key': user_input_field['field'],
'value': self.workflow_manage.get_reference_field(
user_input_field['value'][0],
user_input_field['value'][1:])
value,
user_input_field['value'][1:]
) if value != '' else ''
})
return {
'name': self.node.properties.get('stepName'),

View File

@ -9,20 +9,22 @@
from .contain_compare import *
from .equal_compare import *
from .gt_compare import *
from .ge_compare import *
from .gt_compare import *
from .is_not_null_compare import *
from .is_not_true import IsNotTrueCompare
from .is_null_compare import *
from .is_true import IsTrueCompare
from .le_compare import *
from .lt_compare import *
from .len_equal_compare import *
from .len_ge_compare import *
from .len_gt_compare import *
from .len_le_compare import *
from .len_lt_compare import *
from .len_equal_compare import *
from .is_not_null_compare import *
from .is_null_compare import *
from .lt_compare import *
from .not_contain_compare import *
compare_handle_list = [GECompare(), GTCompare(), ContainCompare(), EqualCompare(), LTCompare(), LECompare(),
LenLECompare(), LenGECompare(), LenEqualCompare(), LenGTCompare(), LenLTCompare(),
IsNullCompare(),
IsNotNullCompare(), NotContainCompare()]
IsNotNullCompare(), NotContainCompare(), IsTrueCompare(), IsNotTrueCompare()]

View File

@ -0,0 +1,24 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file is_not_true.py
@date2025/4/7 13:44
@desc:
"""
from typing import List
from application.flow.step_node.condition_node.compare import Compare
class IsNotTrueCompare(Compare):
def support(self, node_id, fields: List[str], source_value, compare, target_value):
if compare == 'is_not_true':
return True
def compare(self, source_value, compare, target_value):
try:
return source_value is False
except Exception as e:
return False

View File

@ -0,0 +1,24 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file IsTrue.py
@date2025/4/7 13:38
@desc:
"""
from typing import List
from application.flow.step_node.condition_node.compare import Compare
class IsTrueCompare(Compare):
def support(self, node_id, fields: List[str], source_value, compare, target_value):
if compare == 'is_true':
return True
def compare(self, source_value, compare, target_value):
try:
return source_value is True
except Exception as e:
return False

View File

@ -36,7 +36,15 @@ class BaseConditionNode(IConditionNode):
return all(condition_list) if condition == 'and' else any(condition_list)
def assertion(self, field_list: List[str], compare: str, value):
field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:])
try:
value = self.workflow_manage.generate_prompt(value)
except Exception as e:
pass
field_value = None
try:
field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:])
except Exception as e:
pass
for compare_handler in compare_handle_list:
if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value):
return compare_handler.compare(field_value, compare, value)

View File

@ -15,7 +15,9 @@ from application.flow.step_node.direct_reply_node.i_reply_node import IReplyNode
class BaseReplyNode(IReplyNode):
def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer')
self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult:
if reply_type == 'referencing':
result = self.get_reference_content(fields)

View File

@ -40,7 +40,6 @@ splitter = '\n`-----------------------------------`\n'
class BaseDocumentExtractNode(IDocumentExtractNode):
def save_context(self, details, workflow_manage):
self.context['content'] = details.get('content')
self.answer_text = details.get('content')
def execute(self, document, chat_id, **kwargs):

View File

@ -38,7 +38,8 @@ class BaseFormNode(IFormNode):
self.context['start_time'] = details.get('start_time')
self.context['form_data'] = form_data
self.context['is_submit'] = details.get('is_submit')
self.answer_text = details.get('result')
if self.node_params.get('is_result', False):
self.answer_text = details.get('result')
if form_data is not None:
for key in form_data:
self.context[key] = form_data[key]
@ -70,7 +71,7 @@ class BaseFormNode(IFormNode):
"chat_record_id": self.flow_params_serializer.data.get("chat_record_id"),
'form_data': self.context.get('form_data', {}),
"is_submit": self.context.get("is_submit", False)}
form = f'<form_rander>{json.dumps(form_setting,ensure_ascii=False)}</form_rander>'
form = f'<form_rander>{json.dumps(form_setting, ensure_ascii=False)}</form_rander>'
context = self.workflow_manage.get_workflow_content()
form_content_format = self.workflow_manage.reset_prompt(form_content_format)
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')
@ -85,7 +86,7 @@ class BaseFormNode(IFormNode):
"chat_record_id": self.flow_params_serializer.data.get("chat_record_id"),
'form_data': self.context.get('form_data', {}),
"is_submit": self.context.get("is_submit", False)}
form = f'<form_rander>{json.dumps(form_setting,ensure_ascii=False)}</form_rander>'
form = f'<form_rander>{json.dumps(form_setting, ensure_ascii=False)}</form_rander>'
context = self.workflow_manage.get_workflow_content()
form_content_format = self.workflow_manage.reset_prompt(form_content_format)
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')

View File

@ -11,11 +11,13 @@ import time
from typing import Dict
from django.db.models import QuerySet
from django.utils.translation import gettext as _
from application.flow.i_step_node import NodeResult
from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode
from common.exception.app_exception import AppApiException
from common.util.function_code import FunctionExecutor
from common.util.rsa_util import rsa_long_decrypt
from function_lib.models.function import FunctionLib
from smartdoc.const import CONFIG
@ -38,15 +40,15 @@ def get_field_value(debug_field_list, name, is_required):
if len(result) > 0:
return result[-1]['value']
if is_required:
raise AppApiException(500, f"{name}字段未设置值")
raise AppApiException(500, _('Field: {name} No value set').format(name=name))
return None
def valid_reference_value(_type, value, name):
if _type == 'int':
instance_type = int
instance_type = int | float
elif _type == 'float':
instance_type = float
instance_type = float | int
elif _type == 'dict':
instance_type = dict
elif _type == 'array':
@ -54,13 +56,16 @@ def valid_reference_value(_type, value, name):
elif _type == 'string':
instance_type = str
else:
raise Exception(500, f'字段:{name}类型:{_type} 不支持的类型')
raise Exception(_('Field: {name} Type: {_type} Value: {value} Unsupported types').format(name=name,
_type=_type))
if not isinstance(value, instance_type):
raise Exception(f'字段:{name}类型:{_type}值:{value}类型错误')
raise Exception(
_('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,
value=value))
def convert_value(name: str, value, _type, is_required, source, node):
if not is_required and value is None:
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)):
return None
if not is_required and source == 'reference' and (value is None or len(value) == 0):
return None
@ -69,6 +74,10 @@ def convert_value(name: str, value, _type, is_required, source, node):
value[0],
value[1:])
valid_reference_value(_type, value, name)
if _type == 'int':
return int(value)
if _type == 'float':
return float(value)
return value
try:
if _type == 'int':
@ -79,26 +88,37 @@ def convert_value(name: str, value, _type, is_required, source, node):
v = json.loads(value)
if isinstance(v, dict):
return v
raise Exception("类型错误")
raise Exception(_('type error'))
if _type == 'array':
v = json.loads(value)
if isinstance(v, list):
return v
raise Exception("类型错误")
raise Exception(_('type error'))
return value
except Exception as e:
raise Exception(f'字段:{name}类型:{_type}值:{value}类型错误')
raise Exception(
_('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,
value=value))
def valid_function(function_lib, user_id):
if function_lib is None:
raise Exception(_('Function does not exist'))
if function_lib.permission_type == 'PRIVATE' and str(function_lib.user_id) != str(user_id):
raise Exception(_('No permission to use this function {name}').format(name=function_lib.name))
if not function_lib.is_active:
raise Exception(_('Function {name} is unavailable').format(name=function_lib.name))
class BaseFunctionLibNodeNode(IFunctionLibNode):
def save_context(self, details, workflow_manage):
self.context['result'] = details.get('result')
self.answer_text = str(details.get('result'))
if self.node_params.get('is_result'):
self.answer_text = str(details.get('result'))
def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult:
function_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first()
if not function_lib.is_active:
raise Exception(f'函数:{function_lib.name} 不可用')
valid_function(function_lib, self.flow_params_serializer.data.get('user_id'))
params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),
field.get('is_required'),
field.get('source'), self)
@ -107,8 +127,14 @@ class BaseFunctionLibNodeNode(IFunctionLibNode):
), **field}
for field in
function_lib.input_field_list]}
self.context['params'] = params
result = function_executor.exec_code(function_lib.code, params)
# 合并初始化参数
if function_lib.init_params is not None:
all_params = json.loads(rsa_long_decrypt(function_lib.init_params)) | params
else:
all_params = params
result = function_executor.exec_code(function_lib.code, all_params)
return NodeResult({'result': result}, {}, _write_context=write_context)
def get_details(self, index: int, **kwargs):

View File

@ -33,9 +33,9 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
def valid_reference_value(_type, value, name):
if _type == 'int':
instance_type = int
instance_type = int | float
elif _type == 'float':
instance_type = float
instance_type = float | int
elif _type == 'dict':
instance_type = dict
elif _type == 'array':
@ -49,13 +49,17 @@ def valid_reference_value(_type, value, name):
def convert_value(name: str, value, _type, is_required, source, node):
if not is_required and value is None:
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)):
return None
if source == 'reference':
value = node.workflow_manage.get_reference_field(
value[0],
value[1:])
valid_reference_value(_type, value, name)
if _type == 'int':
return int(value)
if _type == 'float':
return float(value)
return value
try:
if _type == 'int':
@ -80,7 +84,8 @@ def convert_value(name: str, value, _type, is_required, source, node):
class BaseFunctionNodeNode(IFunctionNode):
def save_context(self, details, workflow_manage):
self.context['result'] = details.get('result')
self.answer_text = str(details.get('result'))
if self.node_params.get('is_result', False):
self.answer_text = str(details.get('result'))
def execute(self, input_field_list, code, **kwargs) -> NodeResult:
params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),

View File

@ -16,7 +16,8 @@ class BaseImageGenerateNode(IImageGenerateNode):
def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer')
self.context['question'] = details.get('question')
self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, chat_id,
model_params_setting,
@ -24,7 +25,8 @@ class BaseImageGenerateNode(IImageGenerateNode):
**kwargs) -> NodeResult:
print(model_params_setting)
application = self.workflow_manage.work_flow_post_handler.chat_info.application
tti_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'), **model_params_setting)
tti_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'),
**model_params_setting)
history_message = self.get_history_message(history_chat_record, dialogue_number)
self.context['history_message'] = history_message
question = self.generate_prompt_question(prompt)
@ -47,7 +49,7 @@ class BaseImageGenerateNode(IImageGenerateNode):
file_url = FileSerializer(data={'file': file, 'meta': meta}).upload()
file_urls.append(file_url)
self.context['image_list'] = [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls]
answer = '\n'.join([f"![Image]({path})" for path in file_urls])
answer = ' '.join([f"![Image]({path})" for path in file_urls])
return NodeResult({'answer': answer, 'chat_model': tti_model, 'message_list': message_list,
'image': [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls],
'history_message': history_message, 'question': question}, {})

View File

@ -69,7 +69,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer')
self.context['question'] = details.get('question')
self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream, chat_id,
model_params_setting,

View File

@ -0,0 +1,3 @@
# coding=utf-8
from .impl import *

View File

@ -0,0 +1,35 @@
# coding=utf-8
from typing import Type
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class McpNodeSerializer(serializers.Serializer):
mcp_servers = serializers.JSONField(required=True,
error_messages=ErrMessage.char(_("Mcp servers")))
mcp_server = serializers.CharField(required=True,
error_messages=ErrMessage.char(_("Mcp server")))
mcp_tool = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Mcp tool")))
tool_params = serializers.DictField(required=True,
error_messages=ErrMessage.char(_("Tool parameters")))
class IMcpNode(INode):
type = 'mcp-node'
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
return McpNodeSerializer
def _run(self):
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)
def execute(self, mcp_servers, mcp_server, mcp_tool, tool_params, **kwargs) -> NodeResult:
pass

View File

@ -0,0 +1,3 @@
# coding=utf-8
from .base_mcp_node import BaseMcpNode

View File

@ -0,0 +1,61 @@
# coding=utf-8
import asyncio
import json
from typing import List
from langchain_mcp_adapters.client import MultiServerMCPClient
from application.flow.i_step_node import NodeResult
from application.flow.step_node.mcp_node.i_mcp_node import IMcpNode
class BaseMcpNode(IMcpNode):
def save_context(self, details, workflow_manage):
self.context['result'] = details.get('result')
self.context['tool_params'] = details.get('tool_params')
self.context['mcp_tool'] = details.get('mcp_tool')
if self.node_params.get('is_result', False):
self.answer_text = details.get('result')
def execute(self, mcp_servers, mcp_server, mcp_tool, tool_params, **kwargs) -> NodeResult:
servers = json.loads(mcp_servers)
params = json.loads(json.dumps(tool_params))
params = self.handle_variables(params)
async def call_tool(s, session, t, a):
async with MultiServerMCPClient(s) as client:
s = await client.sessions[session].call_tool(t, a)
return s
res = asyncio.run(call_tool(servers, mcp_server, mcp_tool, params))
return NodeResult(
{'result': [content.text for content in res.content], 'tool_params': params, 'mcp_tool': mcp_tool}, {})
def handle_variables(self, tool_params):
# 处理参数中的变量
for k, v in tool_params.items():
if type(v) == str:
tool_params[k] = self.workflow_manage.generate_prompt(tool_params[k])
if type(v) == dict:
self.handle_variables(v)
if (type(v) == list) and (type(v[0]) == str):
tool_params[k] = self.get_reference_content(v)
return tool_params
def get_reference_content(self, fields: List[str]):
return str(self.workflow_manage.get_reference_field(
fields[0],
fields[1:]))
def get_details(self, index: int, **kwargs):
return {
'name': self.node.properties.get('stepName'),
"index": index,
'run_time': self.context.get('run_time'),
'status': self.status,
'err_message': self.err_message,
'type': self.node.type,
'mcp_tool': self.context.get('mcp_tool'),
'tool_params': self.context.get('tool_params'),
'result': self.context.get('result'),
}

View File

@ -80,7 +80,8 @@ class BaseQuestionNode(IQuestionNode):
self.context['answer'] = details.get('answer')
self.context['message_tokens'] = details.get('message_tokens')
self.context['answer_tokens'] = details.get('answer_tokens')
self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,
model_params_setting=None,

View File

@ -23,9 +23,14 @@ def merge_reranker_list(reranker_list, result=None):
merge_reranker_list(document, result)
elif isinstance(document, dict):
content = document.get('title', '') + document.get('content', '')
result.append(str(document) if len(content) == 0 else content)
title = document.get("title")
dataset_name = document.get("dataset_name")
document_name = document.get('document_name')
result.append(
Document(page_content=str(document) if len(content) == 0 else content,
metadata={'title': title, 'dataset_name': dataset_name, 'document_name': document_name}))
else:
result.append(str(document))
result.append(Document(page_content=str(document), metadata={}))
return result
@ -43,6 +48,21 @@ def filter_result(document_list: List[Document], max_paragraph_char_number, top_
return result
def reset_result_list(result_list: List[Document], document_list: List[Document]):
r = []
document_list = document_list.copy()
for result in result_list:
filter_result_list = [document for document in document_list if document.page_content == result.page_content]
if len(filter_result_list) > 0:
item = filter_result_list[0]
document_list.remove(item)
r.append(Document(page_content=item.page_content,
metadata={**item.metadata, 'relevance_score': result.metadata.get('relevance_score')}))
else:
r.append(result)
return r
class BaseRerankerNode(IRerankerNode):
def save_context(self, details, workflow_manage):
self.context['document_list'] = details.get('document_list', [])
@ -55,16 +75,18 @@ class BaseRerankerNode(IRerankerNode):
**kwargs) -> NodeResult:
documents = merge_reranker_list(reranker_list)
top_n = reranker_setting.get('top_n', 3)
self.context['document_list'] = documents
self.context['document_list'] = [{'page_content': document.page_content, 'metadata': document.metadata} for
document in documents]
self.context['question'] = question
reranker_model = get_model_instance_by_model_user_id(reranker_model_id,
self.flow_params_serializer.data.get('user_id'),
top_n=top_n)
result = reranker_model.compress_documents(
[Document(page_content=document) for document in documents if document is not None and len(document) > 0],
documents,
question)
similarity = reranker_setting.get('similarity', 0.6)
max_paragraph_char_number = reranker_setting.get('max_paragraph_char_number', 5000)
result = reset_result_list(result, documents)
r = filter_result(result, max_paragraph_char_number, top_n, similarity)
return NodeResult({'result_list': r, 'result': ''.join([item.get('page_content') for item in r])}, {})

View File

@ -27,7 +27,7 @@ class DatasetSettingSerializer(serializers.Serializer):
error_messages=ErrMessage.float(_('similarity')))
search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_("The type only supports register|reset_password"), code=500)
message=_("The type only supports embedding|keywords|blend"), code=500)
], error_messages=ErrMessage.char(_("Retrieval Mode")))
max_paragraph_char_number = serializers.IntegerField(required=True,
error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment")))
@ -66,7 +66,7 @@ class ISearchDatasetStepNode(INode):
if self.flow_params_serializer.data.get('re_chat', False):
history_chat_record = self.flow_params_serializer.data.get('history_chat_record', [])
paragraph_id_list = [p.get('id') for p in flat_map(
[get_paragraph_list(chat_record, self.node.id) for chat_record in history_chat_record if
[get_paragraph_list(chat_record, self.runtime_node_id) for chat_record in history_chat_record if
chat_record.problem_text == question])]
exclude_paragraph_id_list = list(set(paragraph_id_list))

View File

@ -88,7 +88,7 @@ class BaseSearchDatasetNode(ISearchDatasetStepNode):
'is_hit_handling_method_list': [row for row in result if row.get('is_hit_handling_method')],
'data': '\n'.join(
[f"{reset_title(paragraph.get('title', ''))}{paragraph.get('content')}" for paragraph in
paragraph_list])[0:dataset_setting.get('max_paragraph_char_number', 5000)],
result])[0:dataset_setting.get('max_paragraph_char_number', 5000)],
'directly_return': '\n'.join(
[paragraph.get('content') for paragraph in
result if

View File

@ -18,7 +18,8 @@ class BaseSpeechToTextNode(ISpeechToTextNode):
def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer')
self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, stt_model_id, chat_id, audio, **kwargs) -> NodeResult:
stt_model = get_model_instance_by_model_user_id(stt_model_id, self.flow_params_serializer.data.get('user_id'))

View File

@ -40,10 +40,13 @@ class BaseStartStepNode(IStarNode):
self.context['document'] = details.get('document_list')
self.context['image'] = details.get('image_list')
self.context['audio'] = details.get('audio_list')
self.context['other'] = details.get('other_list')
self.status = details.get('status')
self.err_message = details.get('err_message')
for key, value in workflow_variable.items():
workflow_manage.context[key] = value
for item in details.get('global_fields', []):
workflow_manage.context[item.get('key')] = item.get('value')
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
pass
@ -59,7 +62,8 @@ class BaseStartStepNode(IStarNode):
'question': question,
'image': self.workflow_manage.image_list,
'document': self.workflow_manage.document_list,
'audio': self.workflow_manage.audio_list
'audio': self.workflow_manage.audio_list,
'other': self.workflow_manage.other_list,
}
return NodeResult(node_variable, workflow_variable)
@ -83,5 +87,6 @@ class BaseStartStepNode(IStarNode):
'image_list': self.context.get('image'),
'document_list': self.context.get('document'),
'audio_list': self.context.get('audio'),
'other_list': self.context.get('other'),
'global_fields': global_fields
}

View File

@ -37,7 +37,8 @@ def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"):
class BaseTextToSpeechNode(ITextToSpeechNode):
def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer')
self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, tts_model_id, chat_id,
content, model_params_setting=None,

View File

@ -0,0 +1,3 @@
# coding=utf-8
from .impl import *

View File

@ -0,0 +1,27 @@
# coding=utf-8
from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
class VariableAssignNodeParamsSerializer(serializers.Serializer):
variable_list = serializers.ListField(required=True,
error_messages=ErrMessage.list(_("Reference Field")))
class IVariableAssignNode(INode):
type = 'variable-assign-node'
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
return VariableAssignNodeParamsSerializer
def _run(self):
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)
def execute(self, variable_list, stream, **kwargs) -> NodeResult:
pass

View File

@ -0,0 +1,9 @@
# coding=utf-8
"""
@project: maxkb
@Author
@file __init__.py
@date2024/6/11 17:49
@desc:
"""
from .base_variable_assign_node import *

View File

@ -0,0 +1,65 @@
# coding=utf-8
import json
from typing import List
from application.flow.i_step_node import NodeResult
from application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode
class BaseVariableAssignNode(IVariableAssignNode):
def save_context(self, details, workflow_manage):
self.context['variable_list'] = details.get('variable_list')
self.context['result_list'] = details.get('result_list')
def execute(self, variable_list, stream, **kwargs) -> NodeResult:
#
result_list = []
for variable in variable_list:
if 'fields' not in variable:
continue
if 'global' == variable['fields'][0]:
result = {
'name': variable['name'],
'input_value': self.get_reference_content(variable['fields']),
}
if variable['source'] == 'custom':
if variable['type'] == 'json':
if isinstance(variable['value'], dict) or isinstance(variable['value'], list):
val = variable['value']
else:
val = json.loads(variable['value'])
self.workflow_manage.context[variable['fields'][1]] = val
result['output_value'] = variable['value'] = val
elif variable['type'] == 'string':
# 变量解析 例如:{{global.xxx}}
val = self.workflow_manage.generate_prompt(variable['value'])
self.workflow_manage.context[variable['fields'][1]] = val
result['output_value'] = val
else:
val = variable['value']
self.workflow_manage.context[variable['fields'][1]] = val
result['output_value'] = val
else:
reference = self.get_reference_content(variable['reference'])
self.workflow_manage.context[variable['fields'][1]] = reference
result['output_value'] = reference
result_list.append(result)
return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {})
def get_reference_content(self, fields: List[str]):
return str(self.workflow_manage.get_reference_field(
fields[0],
fields[1:]))
def get_details(self, index: int, **kwargs):
return {
'name': self.node.properties.get('stepName'),
"index": index,
'run_time': self.context.get('run_time'),
'type': self.node.type,
'variable_list': self.context.get('variable_list'),
'result_list': self.context.get('result_list'),
'status': self.status,
'err_message': self.err_message
}

View File

@ -238,6 +238,7 @@ class WorkflowManage:
base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None,
document_list=None,
audio_list=None,
other_list=None,
start_node_id=None,
start_node_data=None, chat_record=None, child_node=None):
if form_data is None:
@ -248,12 +249,15 @@ class WorkflowManage:
document_list = []
if audio_list is None:
audio_list = []
if other_list is None:
other_list = []
self.start_node_id = start_node_id
self.start_node = None
self.form_data = form_data
self.image_list = image_list
self.document_list = document_list
self.audio_list = audio_list
self.other_list = other_list
self.params = params
self.flow = flow
self.context = {}
@ -269,11 +273,36 @@ class WorkflowManage:
self.child_node = child_node
self.future_list = []
self.lock = threading.Lock()
self.field_list = []
self.global_field_list = []
self.init_fields()
if start_node_id is not None:
self.load_node(chat_record, start_node_id, start_node_data)
else:
self.node_context = []
def init_fields(self):
field_list = []
global_field_list = []
for node in self.flow.nodes:
properties = node.properties
node_name = properties.get('stepName')
node_id = node.id
node_config = properties.get('config')
if node_config is not None:
fields = node_config.get('fields')
if fields is not None:
for field in fields:
field_list.append({**field, 'node_id': node_id, 'node_name': node_name})
global_fields = node_config.get('globalFields')
if global_fields is not None:
for global_field in global_fields:
global_field_list.append({**global_field, 'node_id': node_id, 'node_name': node_name})
field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
global_field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
self.field_list = field_list
self.global_field_list = global_field_list
def append_answer(self, content):
self.answer += content
self.answer_list[-1] += content
@ -337,13 +366,15 @@ class WorkflowManage:
answer_text = '\n\n'.join(
'\n\n'.join([a.get('content') for a in answer]) for answer in
answer_text_list)
answer_list = reduce(lambda pre, _n: [*pre, *_n], answer_text_list, [])
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
answer_text,
self)
return self.base_to_response.to_block_response(self.params['chat_id'],
self.params['chat_record_id'], answer_text, True
, message_tokens, answer_tokens,
_status=status.HTTP_200_OK if self.status == 200 else status.HTTP_500_INTERNAL_SERVER_ERROR)
_status=status.HTTP_200_OK if self.status == 200 else status.HTTP_500_INTERNAL_SERVER_ERROR,
other_params={'answer_list': answer_list})
def run_stream(self, current_node, node_result_future, language='zh'):
"""
@ -382,6 +413,8 @@ class WorkflowManage:
break
yield chunk
finally:
while self.is_run():
pass
details = self.get_runtime_details()
message_tokens = sum([row.get('message_tokens') for row in details.values() if
'message_tokens' in row and row.get('message_tokens') is not None])
@ -735,23 +768,15 @@ class WorkflowManage:
def reset_prompt(self, prompt: str):
placeholder = "{}"
for node in self.flow.nodes:
properties = node.properties
node_config = properties.get('config')
if node_config is not None:
fields = node_config.get('fields')
if fields is not None:
for field in fields:
globeLabel = f"{properties.get('stepName')}.{field.get('value')}"
globeValue = f"context.get('{node.id}',{placeholder}).get('{field.get('value', '')}','')"
prompt = prompt.replace(globeLabel, globeValue)
global_fields = node_config.get('globalFields')
if global_fields is not None:
for field in global_fields:
globeLabel = f"全局变量.{field.get('value')}"
globeLabelNew = f"global.{field.get('value')}"
globeValue = f"context.get('global').get('{field.get('value', '')}','')"
prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue)
for field in self.field_list:
globeLabel = f"{field.get('node_name')}.{field.get('value')}"
globeValue = f"context.get('{field.get('node_id')}',{placeholder}).get('{field.get('value', '')}','')"
prompt = prompt.replace(globeLabel, globeValue)
for field in self.global_field_list:
globeLabel = f"全局变量.{field.get('value')}"
globeLabelNew = f"global.{field.get('value')}"
globeValue = f"context.get('global').get('{field.get('value', '')}','')"
prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue)
return prompt
def generate_prompt(self, prompt: str):

View File

@ -1,11 +1,11 @@
# Generated by Django 4.2.13 on 2024-07-15 15:52
import application.models.application
from django.db import migrations, models
import common.encoder.encoder
class Migration(migrations.Migration):
dependencies = [
('application', '0009_application_type_application_work_flow_and_more'),
]
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='chatrecord',
name='details',
field=models.JSONField(default=dict, encoder=application.models.application.DateEncoder, verbose_name='对话详情'),
field=models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='对话详情'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.2.18 on 2025-03-18 06:05
import application.models.application
import common.encoder.encoder
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('application', '0025_alter_application_prologue'),
]
operations = [
migrations.AddField(
model_name='chat',
name='asker',
field=models.JSONField(default=application.models.application.default_asker, encoder=common.encoder.encoder.SystemEncoder, verbose_name='访问者'),
),
]

View File

@ -6,14 +6,13 @@
@date2023/9/25 14:24
@desc:
"""
import datetime
import json
import uuid
from django.contrib.postgres.fields import ArrayField
from django.db import models
from langchain.schema import HumanMessage, AIMessage
from django.utils.translation import gettext as _
from common.encoder.encoder import SystemEncoder
from common.mixins.app_model_mixin import AppModelMixin
from dataset.models.data_set import DataSet
from setting.models.model_management import Model
@ -116,10 +115,15 @@ class ApplicationDatasetMapping(AppModelMixin):
db_table = "application_dataset_mapping"
def default_asker():
return {'user_name': '游客'}
class Chat(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
application = models.ForeignKey(Application, on_delete=models.CASCADE)
abstract = models.CharField(max_length=1024, verbose_name="摘要")
asker = models.JSONField(verbose_name="访问者", default=default_asker, encoder=SystemEncoder)
client_id = models.UUIDField(verbose_name="客户端id", default=None, null=True)
is_deleted = models.BooleanField(verbose_name="", default=False)
@ -134,16 +138,6 @@ class VoteChoices(models.TextChoices):
TRAMPLE = 1, '反对'
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, uuid.UUID):
return str(obj)
if isinstance(obj, datetime.datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
else:
return json.JSONEncoder.default(self, obj)
class ChatRecord(AppModelMixin):
"""
对话日志 详情
@ -160,7 +154,7 @@ class ChatRecord(AppModelMixin):
message_tokens = models.IntegerField(verbose_name="请求token数量", default=0)
answer_tokens = models.IntegerField(verbose_name="响应token数量", default=0)
const = models.IntegerField(verbose_name="总费用", default=0)
details = models.JSONField(verbose_name="对话详情", default=dict, encoder=DateEncoder)
details = models.JSONField(verbose_name="对话详情", default=dict, encoder=SystemEncoder)
improve_paragraph_id_list = ArrayField(verbose_name="改进标注列表",
base_field=models.UUIDField(max_length=128, blank=True)
, default=list)
@ -173,7 +167,11 @@ class ChatRecord(AppModelMixin):
return HumanMessage(content=self.problem_text)
def get_ai_message(self):
return AIMessage(content=self.answer_text)
answer_text = self.answer_text
if answer_text is None or len(str(answer_text).strip()) == 0:
answer_text = _(
'Sorry, no relevant content was found. Please re-describe your problem or provide more information. ')
return AIMessage(content=answer_text)
def get_node_details_runtime_node_id(self, runtime_node_id):
return self.details.get(runtime_node_id, None)

View File

@ -6,6 +6,7 @@
@date2023/11/7 10:02
@desc:
"""
import asyncio
import datetime
import hashlib
import json
@ -23,6 +24,8 @@ from django.db.models import QuerySet
from django.db.models.expressions import RawSQL
from django.http import HttpResponse
from django.template import Template, Context
from langchain_mcp_adapters.client import MultiServerMCPClient
from mcp.client.sse import sse_client
from rest_framework import serializers, status
from rest_framework.utils.formatting import lazy_format
@ -39,13 +42,13 @@ from common.exception.app_exception import AppApiException, NotFound404, AppUnau
from common.field.common import UploadedImageField, UploadedFileField
from common.models.db_model_manage import DBModelManage
from common.response import result
from common.util.common import valid_license, password_encrypt
from common.util.common import valid_license, password_encrypt, restricted_loads
from common.util.field_message import ErrMessage
from common.util.file_util import get_file_content
from dataset.models import DataSet, Document, Image
from dataset.serializers.common_serializers import list_paragraph, get_embedding_model_by_dataset_id_list
from embedding.models import SearchMode
from function_lib.models.function import FunctionLib, PermissionType
from function_lib.models.function import FunctionLib, PermissionType, FunctionType
from function_lib.serializers.function_lib_serializer import FunctionLibSerializer, FunctionLibModelSerializer
from setting.models import AuthOperate, TeamMemberPermission
from setting.models.model_management import Model
@ -60,6 +63,7 @@ chat_cache = cache.caches['chat_cache']
class MKInstance:
def __init__(self, application: dict, function_lib_list: List[dict], version: str):
self.application = application
self.function_lib_list = function_lib_list
@ -117,7 +121,7 @@ def valid_model_params_setting(model_id, model_params_setting):
class DatasetSettingSerializer(serializers.Serializer):
top_n = serializers.FloatField(required=True, max_value=100, min_value=1,
top_n = serializers.FloatField(required=True, max_value=10000, min_value=1,
error_messages=ErrMessage.float(_("Reference segment number")))
similarity = serializers.FloatField(required=True, max_value=1, min_value=0,
error_messages=ErrMessage.float(_("Acquaintance")))
@ -126,7 +130,7 @@ class DatasetSettingSerializer(serializers.Serializer):
_("Maximum number of quoted characters")))
search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_("The type only supports register|reset_password"), code=500)
message=_("The type only supports embedding|keywords|blend"), code=500)
], error_messages=ErrMessage.char(_("Retrieval Mode")))
no_references_setting = NoReferencesSetting(required=True,
@ -142,10 +146,14 @@ class ModelSettingSerializer(serializers.Serializer):
error_messages=ErrMessage.char(_("No citation segmentation prompt")))
reasoning_content_enable = serializers.BooleanField(required=False,
error_messages=ErrMessage.char(_("Thinking process switch")))
reasoning_content_start = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=256,
reasoning_content_start = serializers.CharField(required=False, allow_null=True, default="<think>",
allow_blank=True, max_length=256,
trim_whitespace=False,
error_messages=ErrMessage.char(
_("The thinking process begins to mark")))
reasoning_content_end = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=256,
reasoning_content_end = serializers.CharField(required=False, allow_null=True, allow_blank=True, default="</think>",
max_length=256,
trim_whitespace=False,
error_messages=ErrMessage.char(_("End of thinking process marker")))
@ -156,7 +164,7 @@ class ApplicationWorkflowSerializer(serializers.Serializer):
max_length=256, min_length=1,
error_messages=ErrMessage.char(_("Application Description")))
work_flow = serializers.DictField(required=False, error_messages=ErrMessage.dict(_("Workflow Objects")))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096,
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
error_messages=ErrMessage.char(_("Opening remarks")))
@staticmethod
@ -219,7 +227,7 @@ class ApplicationSerializer(serializers.Serializer):
min_value=0,
max_value=1024,
error_messages=ErrMessage.integer(_("Historical chat records")))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096,
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
error_messages=ErrMessage.char(_("Opening remarks")))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
allow_null=True,
@ -326,7 +334,8 @@ class ApplicationSerializer(serializers.Serializer):
for field in input_field_list:
if field['assignment_method'] == 'api_input' and field['variable'] in params:
query += f"&{field['variable']}={params[field['variable']]}"
if 'asker' in params:
query += f"&asker={params.get('asker')}"
return query
class AccessTokenSerializer(serializers.Serializer):
@ -486,7 +495,7 @@ class ApplicationSerializer(serializers.Serializer):
min_value=0,
max_value=1024,
error_messages=ErrMessage.integer(_("Historical chat records")))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096,
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
error_messages=ErrMessage.char(_("Opening remarks")))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
error_messages=ErrMessage.list(_("Related Knowledge Base"))
@ -579,13 +588,13 @@ class ApplicationSerializer(serializers.Serializer):
id = serializers.CharField(required=True, error_messages=ErrMessage.uuid(_("Application ID")))
user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.uuid(_("User ID")))
query_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Query text")))
top_number = serializers.IntegerField(required=True, max_value=100, min_value=1,
top_number = serializers.IntegerField(required=True, max_value=10000, min_value=1,
error_messages=ErrMessage.integer(_("topN")))
similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
error_messages=ErrMessage.float(_("Relevance")))
search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_("The type only supports register|reset_password"), code=500)
message=_("The type only supports embedding|keywords|blend"), code=500)
], error_messages=ErrMessage.char(_("Retrieval Mode")))
def is_valid(self, *, raise_exception=False):
@ -725,7 +734,7 @@ class ApplicationSerializer(serializers.Serializer):
user_id = self.data.get('user_id')
mk_instance_bytes = self.data.get('file').read()
try:
mk_instance = pickle.loads(mk_instance_bytes)
mk_instance = restricted_loads(mk_instance_bytes)
except Exception as e:
raise AppApiException(1001, _("Unsupported file format"))
application = mk_instance.application
@ -808,8 +817,10 @@ class ApplicationSerializer(serializers.Serializer):
if with_valid:
self.is_valid(raise_exception=True)
application = QuerySet(Application).filter(id=self.data.get("application_id")).first()
return FunctionLibSerializer.Query(data={'user_id': application.user_id, 'is_active': True}).list(
with_valid=True)
return FunctionLibSerializer.Query(
data={'user_id': application.user_id, 'is_active': True,
'function_type': FunctionType.PUBLIC}
).list(with_valid=True)
def get_function_lib(self, function_lib_id, with_valid=True):
if with_valid:
@ -979,6 +990,7 @@ class ApplicationSerializer(serializers.Serializer):
'draggable': application_setting.draggable,
'show_guide': application_setting.show_guide,
'avatar': application_setting.avatar,
'show_avatar': application_setting.show_avatar,
'float_icon': application_setting.float_icon,
'authentication': application_setting.authentication,
'authentication_type': application_setting.authentication_value.get(
@ -987,6 +999,7 @@ class ApplicationSerializer(serializers.Serializer):
'disclaimer_value': application_setting.disclaimer_value,
'custom_theme': application_setting.custom_theme,
'user_avatar': application_setting.user_avatar,
'show_user_avatar': application_setting.show_user_avatar,
'float_location': application_setting.float_location}
return ApplicationSerializer.Query.reset_application(
{**ApplicationSerializer.ApplicationModel(application).data,
@ -999,7 +1012,8 @@ class ApplicationSerializer(serializers.Serializer):
'stt_autosend': application.stt_autosend,
'file_upload_enable': application.file_upload_enable,
'file_upload_setting': application.file_upload_setting,
'work_flow': application.work_flow,
'work_flow': {'nodes': [node for node in ((application.work_flow or {}).get('nodes', []) or []) if
node.get('id') == 'base-node']},
'show_source': application_access_token.show_source,
'language': application_access_token.language,
**application_setting_dict})
@ -1060,6 +1074,7 @@ class ApplicationSerializer(serializers.Serializer):
for update_key in update_keys:
if update_key in instance and instance.get(update_key) is not None:
application.__setattr__(update_key, instance.get(update_key))
print(application.name)
application.save()
if 'dataset_id_list' in instance:
@ -1078,6 +1093,7 @@ class ApplicationSerializer(serializers.Serializer):
chat_cache.clear_by_application_id(application_id)
application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application_id).first()
# 更新缓存数据
print(application.name)
get_application_access_token(application_access_token.access_token, False)
return self.one(with_valid=False)
@ -1130,6 +1146,8 @@ class ApplicationSerializer(serializers.Serializer):
instance['file_upload_enable'] = node_data['file_upload_enable']
if 'file_upload_setting' in node_data:
instance['file_upload_setting'] = node_data['file_upload_setting']
if 'name' in node_data:
instance['name'] = node_data['name']
break
def speech_to_text(self, file, with_valid=True):
@ -1199,7 +1217,9 @@ class ApplicationSerializer(serializers.Serializer):
self.is_valid(raise_exception=True)
if with_valid:
self.is_valid()
embed_application = QuerySet(Application).get(id=app_id)
embed_application = QuerySet(Application).filter(id=app_id).first()
if embed_application is None:
raise AppApiException(500, _('Application does not exist'))
if embed_application.type == ApplicationTypeChoices.WORK_FLOW:
work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=embed_application.id).order_by(
'-create_time')[0:1].first()
@ -1298,3 +1318,29 @@ class ApplicationSerializer(serializers.Serializer):
application_api_key.save()
# 写入缓存
get_application_api_key(application_api_key.secret_key, False)
class McpServers(serializers.Serializer):
mcp_servers = serializers.JSONField(required=True)
def get_mcp_servers(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
if '"stdio"' in self.data.get('mcp_servers'):
raise AppApiException(500, _('stdio is not supported'))
servers = json.loads(self.data.get('mcp_servers'))
async def get_mcp_tools(servers):
async with MultiServerMCPClient(servers) as client:
return client.get_tools()
tools = []
for server in servers:
tools += [
{
'server': server,
'name': tool.name,
'description': tool.description,
'args_schema': tool.args_schema,
}
for tool in asyncio.run(get_mcp_tools({server: servers[server]}))]
return tools

View File

@ -116,13 +116,15 @@ class ChatInfo:
}
def to_pipeline_manage_params(self, problem_text: str, post_response_handler: PostResponseHandler,
exclude_paragraph_id_list, client_id: str, client_type, stream=True):
exclude_paragraph_id_list, client_id: str, client_type, stream=True, form_data=None):
if form_data is None:
form_data = {}
params = self.to_base_pipeline_manage_params()
return {**params, 'problem_text': problem_text, 'post_response_handler': post_response_handler,
'exclude_paragraph_id_list': exclude_paragraph_id_list, 'stream': stream, 'client_id': client_id,
'client_type': client_type}
'client_type': client_type, 'form_data': form_data}
def append_chat_record(self, chat_record: ChatRecord, client_id=None):
def append_chat_record(self, chat_record: ChatRecord, client_id=None, asker=None):
chat_record.problem_text = chat_record.problem_text[0:10240] if chat_record.problem_text is not None else ""
chat_record.answer_text = chat_record.answer_text[0:40960] if chat_record.problem_text is not None else ""
is_save = True
@ -137,8 +139,17 @@ class ChatInfo:
if self.application.id is not None:
# 插入数据库
if not QuerySet(Chat).filter(id=self.chat_id).exists():
asker_dict = {'user_name': '游客'}
if asker is not None:
if isinstance(asker, str):
asker_dict = {
'user_name': asker
}
elif isinstance(asker, dict):
asker_dict = asker
Chat(id=self.chat_id, application_id=self.application.id, abstract=chat_record.problem_text[0:1024],
client_id=client_id, update_time=datetime.now()).save()
client_id=client_id, asker=asker_dict, update_time=datetime.now()).save()
else:
Chat.objects.filter(id=self.chat_id).update(update_time=datetime.now())
# 插入会话记录
@ -171,7 +182,8 @@ def get_post_handler(chat_info: ChatInfo):
answer_text_list=answer_list,
run_time=manage.context['run_time'],
index=len(chat_info.chat_record_list) + 1)
chat_info.append_chat_record(chat_record, client_id)
asker = kwargs.get("asker", None)
chat_info.append_chat_record(chat_record, client_id, asker=asker)
# 重新设置缓存
chat_cache.set(chat_id,
chat_info, timeout=60 * 30)
@ -201,12 +213,21 @@ class OpenAIChatSerializer(serializers.Serializer):
return instance.get('messages')[-1].get('content')
@staticmethod
def generate_chat(chat_id, application_id, message, client_id):
def generate_chat(chat_id, application_id, message, client_id, asker=None):
if chat_id is None:
chat_id = str(uuid.uuid1())
chat = QuerySet(Chat).filter(id=chat_id).first()
if chat is None:
Chat(id=chat_id, application_id=application_id, abstract=message[0:1024], client_id=client_id).save()
asker_dict = {'user_name': '游客'}
if asker is not None:
if isinstance(asker, str):
asker_dict = {
'user_name': asker
}
elif isinstance(asker, dict):
asker_dict = asker
Chat(id=chat_id, application_id=application_id, abstract=message[0:1024], client_id=client_id,
asker=asker_dict).save()
return chat_id
def chat(self, instance: Dict, with_valid=True):
@ -220,15 +241,23 @@ class OpenAIChatSerializer(serializers.Serializer):
application_id = self.data.get('application_id')
client_id = self.data.get('client_id')
client_type = self.data.get('client_type')
chat_id = self.generate_chat(chat_id, application_id, message, client_id)
chat_id = self.generate_chat(chat_id, application_id, message, client_id,
asker=instance.get('form_data', {}).get("asker"))
return ChatMessageSerializer(
data={'chat_id': chat_id, 'message': message,
're_chat': re_chat,
'stream': stream,
'application_id': application_id,
'client_id': client_id,
'client_type': client_type, 'form_data': instance.get('form_data', {})}).chat(
base_to_response=OpenaiToResponse())
data={
'chat_id': chat_id, 'message': message,
're_chat': re_chat,
'stream': stream,
'application_id': application_id,
'client_id': client_id,
'client_type': client_type,
'form_data': instance.get('form_data', {}),
'image_list': instance.get('image_list', []),
'document_list': instance.get('document_list', []),
'audio_list': instance.get('audio_list', []),
'other_list': instance.get('other_list', []),
}
).chat(base_to_response=OpenaiToResponse())
class ChatMessageSerializer(serializers.Serializer):
@ -256,6 +285,7 @@ class ChatMessageSerializer(serializers.Serializer):
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture")))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document")))
audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio")))
other_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Other")))
child_node = serializers.DictField(required=False, allow_null=True,
error_messages=ErrMessage.dict(_("Child Nodes")))
@ -304,6 +334,7 @@ class ChatMessageSerializer(serializers.Serializer):
stream = self.data.get('stream')
client_id = self.data.get('client_id')
client_type = self.data.get('client_type')
form_data = self.data.get("form_data")
pipeline_manage_builder = PipelineManage.builder()
# 如果开启了问题优化,则添加上问题优化步骤
if chat_info.application.problem_optimization:
@ -325,7 +356,7 @@ class ChatMessageSerializer(serializers.Serializer):
exclude_paragraph_id_list = list(set(paragraph_id_list))
# 构建运行参数
params = chat_info.to_pipeline_manage_params(message, get_post_handler(chat_info), exclude_paragraph_id_list,
client_id, client_type, stream)
client_id, client_type, stream, form_data)
# 运行流水线作业
pipeline_message.run(params)
return pipeline_message.context['chat_result']
@ -353,6 +384,7 @@ class ChatMessageSerializer(serializers.Serializer):
image_list = self.data.get('image_list')
document_list = self.data.get('document_list')
audio_list = self.data.get('audio_list')
other_list = self.data.get('other_list')
user_id = chat_info.application.user_id
chat_record_id = self.data.get('chat_record_id')
chat_record = None
@ -369,7 +401,7 @@ class ChatMessageSerializer(serializers.Serializer):
'client_id': client_id,
'client_type': client_type,
'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type),
base_to_response, form_data, image_list, document_list, audio_list,
base_to_response, form_data, image_list, document_list, audio_list, other_list,
self.data.get('runtime_node_id'),
self.data.get('node_data'), chat_record, self.data.get('child_node'))
r = work_flow_manage.run()

View File

@ -13,7 +13,7 @@ import uuid
from functools import reduce
from io import BytesIO
from typing import Dict
import pytz
import openpyxl
from django.core import validators
from django.core.cache import caches
@ -46,6 +46,7 @@ from embedding.task import embedding_by_paragraph, embedding_by_paragraph_list
from setting.models import Model
from setting.models_provider import get_model_credential
from smartdoc.conf import PROJECT_DIR
from smartdoc.settings import TIME_ZONE
chat_cache = caches['chat_cache']
@ -66,6 +67,10 @@ def valid_model_params_setting(model_id, model_params_setting):
credential.get_model_params_setting_form(model.model_name).valid_form(model_params_setting)
class ReAbstractInstanceSerializers(serializers.Serializer):
abstract = serializers.CharField(required=True, error_messages=ErrMessage.char(_("abstract")))
class ChatSerializers(serializers.Serializer):
class Operate(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID")))
@ -78,6 +83,15 @@ class ChatSerializers(serializers.Serializer):
is_deleted=True)
return True
def re_abstract(self, instance, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
ReAbstractInstanceSerializers(data=instance).is_valid(raise_exception=True)
QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id')).update(
abstract=instance.get('abstract'))
return True
def delete(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
@ -160,7 +174,14 @@ class ChatSerializers(serializers.Serializer):
condition = base_condition & min_trample_query
else:
condition = base_condition
return query_set.filter(condition).order_by("-application_chat.update_time")
inner_queryset = QuerySet(Chat).filter(application_id=self.data.get("application_id"))
if 'abstract' in self.data and self.data.get('abstract') is not None:
inner_queryset = inner_queryset.filter(abstract__icontains=self.data.get('abstract'))
return {
'inner_queryset': inner_queryset,
'default_queryset': query_set.filter(condition).order_by("-application_chat.update_time")
}
def list(self, with_valid=True):
if with_valid:
@ -195,7 +216,6 @@ class ChatSerializers(serializers.Serializer):
[])) for
key, node in search_dataset_node_list])
improve_paragraph_list = row.get('improve_paragraph_list')
vote_status_map = {'-1': '未投票', '0': '赞同', '1': '反对'}
return [str(row.get('chat_id')), row.get('abstract'), row.get('problem_text'), padding_problem_text,
row.get('answer_text'), vote_status_map.get(row.get('vote_status')), reference_paragraph_len,
@ -203,8 +223,9 @@ class ChatSerializers(serializers.Serializer):
"\n".join([
f"{improve_paragraph_list[index].get('title')}\n{improve_paragraph_list[index].get('content')}"
for index in range(len(improve_paragraph_list))]),
row.get('asker').get('user_name'),
row.get('message_tokens') + row.get('answer_tokens'), row.get('run_time'),
str(row.get('create_time').strftime('%Y-%m-%d %H:%M:%S')
str(row.get('create_time').astimezone(pytz.timezone(TIME_ZONE)).strftime('%Y-%m-%d %H:%M:%S')
)]
def export(self, data, with_valid=True):
@ -229,7 +250,8 @@ class ChatSerializers(serializers.Serializer):
gettext('answer'), gettext('User feedback'),
gettext('Reference segment number'),
gettext('Section title + content'),
gettext('Annotation'), gettext('Consuming tokens'), gettext('Time consumed (s)'),
gettext('Annotation'), gettext('USER'), gettext('Consuming tokens'),
gettext('Time consumed (s)'),
gettext('Question Time')]
for col_idx, header in enumerate(headers, 1):
cell = worksheet.cell(row=1, column=col_idx)
@ -243,6 +265,10 @@ class ChatSerializers(serializers.Serializer):
cell = worksheet.cell(row=row_idx, column=col_idx)
if isinstance(value, str):
value = re.sub(ILLEGAL_CHARACTERS_RE, '', value)
if isinstance(value, datetime.datetime):
eastern = pytz.timezone(TIME_ZONE)
c = datetime.timezone(eastern._utcoffset)
value = value.astimezone(c)
cell.value = value
output = BytesIO()
@ -455,7 +481,7 @@ class ChatRecordSerializer(serializers.Serializer):
class Query(serializers.Serializer):
application_id = serializers.UUIDField(required=True)
chat_id = serializers.UUIDField(required=True)
order_asc = serializers.BooleanField(required=False)
order_asc = serializers.BooleanField(required=False, allow_null=True)
def list(self, with_valid=True):
if with_valid:

View File

@ -10,7 +10,8 @@ SELECT
application_chat_record_temp."index" as "index",
application_chat_record_temp.improve_paragraph_list as improve_paragraph_list,
application_chat_record_temp.vote_status as vote_status,
application_chat_record_temp.create_time as create_time
application_chat_record_temp.create_time as create_time,
to_json(application_chat.asker) as asker
FROM
application_chat application_chat
LEFT JOIN (
@ -22,6 +23,8 @@ FROM
chat_id
FROM
application_chat_record
WHERE chat_id IN (
SELECT id FROM application_chat ${inner_queryset})
GROUP BY
application_chat_record.chat_id
) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id
@ -34,4 +37,5 @@ FROM
END as improve_paragraph_list
FROM
application_chat_record application_chat_record
) application_chat_record_temp ON application_chat_record_temp.chat_id = application_chat."id"
) application_chat_record_temp ON application_chat_record_temp.chat_id = application_chat."id"
${default_queryset}

View File

@ -1,5 +1,5 @@
SELECT
*
*,to_json(asker) as asker
FROM
application_chat application_chat
LEFT JOIN (
@ -11,6 +11,9 @@ FROM
chat_id
FROM
application_chat_record
WHERE chat_id IN (
SELECT id FROM application_chat ${inner_queryset})
GROUP BY
application_chat_record.chat_id
) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id
) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id
${default_queryset}

View File

@ -38,6 +38,15 @@ class ApplicationApi(ApiMixin):
}
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_STRING,
title=_("Application authentication token"),
description=_("Application authentication token"),
default="token"
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
@ -133,6 +142,27 @@ class ApplicationApi(ApiMixin):
}
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Primary key id"),
description=_("Primary key id")),
'secret_key': openapi.Schema(type=openapi.TYPE_STRING, title=_("Secret key"),
description=_("Secret key")),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is activation"),
description=_("Is activation")),
'application_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application ID"),
description=_("Application ID")),
'allow_cross_domain': openapi.Schema(type=openapi.TYPE_BOOLEAN,
title=_("Is cross-domain allowed"),
description=_("Is cross-domain allowed")),
'cross_domain_list': openapi.Schema(type=openapi.TYPE_ARRAY, title=_('Cross-domain list'),
items=openapi.Schema(type=openapi.TYPE_STRING))
}
)
class AccessToken(ApiMixin):
@staticmethod
def get_request_params_api():
@ -171,6 +201,37 @@ class ApplicationApi(ApiMixin):
}
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[],
properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Primary key id"),
description=_("Primary key id")),
'access_token': openapi.Schema(type=openapi.TYPE_STRING, title=_("Access Token"),
description=_("Access Token")),
'access_token_reset': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Reset Token"),
description=_("Reset Token")),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is activation"),
description=_("Is activation")),
'access_num': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of visits"),
description=_("Number of visits")),
'white_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Whether to enable whitelist"),
description=_("Whether to enable whitelist")),
'white_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), title=_("Whitelist"),
description=_("Whitelist")),
'show_source': openapi.Schema(type=openapi.TYPE_BOOLEAN,
title=_("Whether to display knowledge sources"),
description=_("Whether to display knowledge sources")),
'language': openapi.Schema(type=openapi.TYPE_STRING,
title=_("language"),
description=_("language"))
}
)
class Edit(ApiMixin):
@staticmethod
def get_request_body_api():
@ -234,7 +295,7 @@ class ApplicationApi(ApiMixin):
default=[]),
'edges': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT),
title=_('Connection List'), description=_("Connection List"),
default={}),
default=[]),
}
)
@ -324,7 +385,8 @@ class ApplicationApi(ApiMixin):
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['name', 'desc', 'model_id', 'dialogue_number', 'dataset_setting', 'model_setting',
'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type'],
'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type',
'work_flow'],
properties={
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Name"),
description=_("Application Name")),
@ -361,7 +423,58 @@ class ApplicationApi(ApiMixin):
'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is text-to-speech enabled"),
description=_("Is text-to-speech enabled")),
'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech type"),
description=_("Text-to-speech type"))
description=_("Text-to-speech type")),
'work_flow': ApplicationApi.WorkFlow.get_request_body_api(),
}
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['id', 'name', 'desc', 'model_id', 'dialogue_number', 'dataset_setting', 'model_setting',
'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type',
'work_flow'],
properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Primary key id"),
description=_("Primary key id")),
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Name"),
description=_("Application Name")),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Description"),
description=_("Application Description")),
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Model id"),
description=_("Model id")),
"dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER,
title=_("Number of multi-round conversations"),
description=_("Number of multi-round conversations")),
'prologue': openapi.Schema(type=openapi.TYPE_STRING, title=_("Opening remarks"),
description=_("Opening remarks")),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
title=_("List of associated knowledge base IDs"),
description=_("List of associated knowledge base IDs")),
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Problem Optimization"),
description=_("Problem Optimization"), default=True),
'type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Type"),
description=_("Application Type SIMPLE | WORK_FLOW")),
'problem_optimization_prompt': openapi.Schema(type=openapi.TYPE_STRING,
title=_('Question optimization tips'),
description=_("Question optimization tips"),
default=_(
"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag")),
'tts_model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech model ID"),
description=_("Text-to-speech model ID")),
'stt_model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Speech-to-text model id"),
description=_("Speech-to-text model id")),
'stt_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is speech-to-text enabled"),
description=_("Is speech-to-text enabled")),
'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is text-to-speech enabled"),
description=_("Is text-to-speech enabled")),
'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech type"),
description=_("Text-to-speech type")),
'work_flow': ApplicationApi.WorkFlow.get_request_body_api(),
}
)
@ -432,4 +545,4 @@ class ApplicationApi(ApiMixin):
'text': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text"),
description=_("Text")),
}
)
)

View File

@ -23,8 +23,115 @@ class ChatClientHistoryApi(ApiMixin):
description=_('Application ID'))
]
class Operate(ApiMixin):
@staticmethod
def get_request_params_api():
return [openapi.Parameter(name='application_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Application ID')),
openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Conversation ID')),
]
class ReAbstract(ApiMixin):
@staticmethod
def get_request_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['abstract'],
properties={
'abstract': openapi.Schema(type=openapi.TYPE_STRING, title=_("abstract"),
description=_("abstract"))
}
)
class OpenAIChatApi(ApiMixin):
@staticmethod
def get_response_body_api():
return openapi.Responses(responses={
200: openapi.Response(description=_('response parameters'),
schema=openapi.Schema(type=openapi.TYPE_OBJECT,
required=['id',
'choices'],
properties={
'id': openapi.Schema(
type=openapi.TYPE_STRING,
title=_(
"Conversation ID")),
'choices': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[
'message'],
properties={
'finish_reason': openapi.Schema(
type=openapi.TYPE_STRING, ),
'index': openapi.Schema(
type=openapi.TYPE_INTEGER),
'answer_list': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[
'content'],
properties={
'content': openapi.Schema(
type=openapi.TYPE_STRING),
'view_type': openapi.Schema(
type=openapi.TYPE_STRING),
'runtime_node_id': openapi.Schema(
type=openapi.TYPE_STRING),
'chat_record_id': openapi.Schema(
type=openapi.TYPE_STRING),
'reasoning_content': openapi.Schema(
type=openapi.TYPE_STRING),
}
)),
'message': openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[
'content'],
properties={
'content': openapi.Schema(
type=openapi.TYPE_STRING),
'role': openapi.Schema(
type=openapi.TYPE_STRING)
}),
}
)),
'created': openapi.Schema(
type=openapi.TYPE_INTEGER),
'model': openapi.Schema(
type=openapi.TYPE_STRING),
'object': openapi.Schema(
type=openapi.TYPE_STRING),
'usage': openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[
'completion_tokens',
'prompt_tokens',
'total_tokens'],
properties={
'completion_tokens': openapi.Schema(
type=openapi.TYPE_INTEGER),
'prompt_tokens': openapi.Schema(
type=openapi.TYPE_INTEGER),
'total_tokens': openapi.Schema(
type=openapi.TYPE_INTEGER)
})
}))})
@staticmethod
def get_request_body_api():
return openapi.Schema(type=openapi.TYPE_OBJECT,
@ -48,7 +155,8 @@ class OpenAIChatApi(ApiMixin):
'chat_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Conversation ID")),
're_chat': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("regenerate"),
default=False),
'stream': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Stream Output"), default=True)
'stream': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Stream Output"),
default=True)
})
@ -62,7 +170,66 @@ class ChatApi(ApiMixin):
properties={
'message': openapi.Schema(type=openapi.TYPE_STRING, title=_("problem"), description=_("problem")),
're_chat': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("regenerate"), default=False),
'stream': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is it streaming output"), default=True)
'stream': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is it streaming output"), default=True),
'form_data': openapi.Schema(type=openapi.TYPE_OBJECT, title=_("Form data"),
description=_("Form data"),
default={}),
'image_list': openapi.Schema(
type=openapi.TYPE_ARRAY,
title=_("Image list"),
description=_("Image list"),
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'name': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Image name")),
'url': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Image URL")),
'file_id': openapi.Schema(type=openapi.TYPE_STRING),
}
),
default=[]
),
'document_list': openapi.Schema(type=openapi.TYPE_ARRAY, title=_("Document list"),
description=_("Document list"),
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
# 定义对象的具体属性
'name': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Document name")),
'url': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Document URL")),
'file_id': openapi.Schema(type=openapi.TYPE_STRING),
}
),
default=[]),
'audio_list': openapi.Schema(type=openapi.TYPE_ARRAY, title=_("Audio list"),
description=_("Audio list"),
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'name': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Audio name")),
'url': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Audio URL")),
'file_id': openapi.Schema(type=openapi.TYPE_STRING),
}
),
default=[]),
'runtime_node_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Runtime node id"),
description=_("Runtime node id"),
default=""),
'node_data': openapi.Schema(type=openapi.TYPE_OBJECT, title=_("Node data"),
description=_("Node data"),
default={}),
'chat_record_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Conversation record id"),
description=_("Conversation record id"),
default=""),
'child_node': openapi.Schema(type=openapi.TYPE_STRING, title=_("Child node"),
description=_("Child node"),
default={}),
}
)
@ -132,20 +299,35 @@ class ChatApi(ApiMixin):
'problem_optimization'],
properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application ID"),
description=_("Application ID, pass when modifying, do not pass when creating")),
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Model ID"), description=_("Model ID")),
description=_(
"Application ID, pass when modifying, do not pass when creating")),
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Model ID"),
description=_("Model ID")),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
title=_("List of associated knowledge base IDs"), description=_("List of associated knowledge base IDs")),
'multiple_rounds_dialogue': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Do you want to initiate multiple sessions"),
description=_("Do you want to initiate multiple sessions")),
title=_("List of associated knowledge base IDs"),
description=_("List of associated knowledge base IDs")),
'multiple_rounds_dialogue': openapi.Schema(type=openapi.TYPE_BOOLEAN,
title=_("Do you want to initiate multiple sessions"),
description=_(
"Do you want to initiate multiple sessions")),
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Problem optimization"),
description=_("Do you want to enable problem optimization"), default=True)
description=_("Do you want to enable problem optimization"),
default=True)
}
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_STRING,
title=_("Conversation ID"),
description=_("Conversation ID"),
default="chat_id"
)
@staticmethod
def get_request_params_api():
return [openapi.Parameter(name='application_id',
@ -165,7 +347,15 @@ class ChatApi(ApiMixin):
openapi.Parameter(name='min_trample', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False,
description=_("Minimum number of clicks")),
openapi.Parameter(name='comparer', in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False,
description=_("or|and comparator"))
description=_("or|and comparator")),
openapi.Parameter(name='start_time', in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING,
required=True,
description=_('start time')),
openapi.Parameter(name='end_time', in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING,
required=True,
description=_('End time')),
]
@ -182,6 +372,11 @@ class ChatRecordApi(ApiMixin):
type=openapi.TYPE_STRING,
required=True,
description=_('Conversation ID')),
openapi.Parameter(name='order_asc',
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
required=False,
description=_('Is it ascending order')),
]
@staticmethod
@ -206,14 +401,19 @@ class ChatRecordApi(ApiMixin):
description=_("Resource ID"), default=1),
'source_type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Resource Type"),
description=_("Resource Type"), default='xxx'),
'message_tokens': openapi.Schema(type=openapi.TYPE_INTEGER, title=_("Number of tokens consumed by the question"),
'message_tokens': openapi.Schema(type=openapi.TYPE_INTEGER,
title=_("Number of tokens consumed by the question"),
description=_("Number of tokens consumed by the question"), default=0),
'answer_tokens': openapi.Schema(type=openapi.TYPE_INTEGER, title=_("The number of tokens consumed by the answer"),
description=_("The number of tokens consumed by the answer"), default=0),
'improve_paragraph_id_list': openapi.Schema(type=openapi.TYPE_STRING, title=_("Improved annotation list"),
'answer_tokens': openapi.Schema(type=openapi.TYPE_INTEGER,
title=_("The number of tokens consumed by the answer"),
description=_("The number of tokens consumed by the answer"),
default=0),
'improve_paragraph_id_list': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Improved annotation list"),
description=_("Improved annotation list"),
default=[]),
'index': openapi.Schema(type=openapi.TYPE_STRING, title=_("Corresponding session Corresponding subscript"),
'index': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Corresponding session Corresponding subscript"),
description=_("Corresponding session id corresponding subscript"),
default=0
),
@ -372,7 +572,8 @@ class ChatRecordImproveApi(ApiMixin):
description=_("Paragraph content"), default=_('Paragraph content')),
'title': openapi.Schema(type=openapi.TYPE_STRING, title=_("title"),
description=_("title"), default=_("Description of xxx")),
'hit_num': openapi.Schema(type=openapi.TYPE_INTEGER, title=_("Number of hits"), description=_("Number of hits"),
'hit_num': openapi.Schema(type=openapi.TYPE_INTEGER, title=_("Number of hits"),
description=_("Number of hits"),
default=1),
'star_num': openapi.Schema(type=openapi.TYPE_INTEGER, title=_("Number of Likes"),
description=_("Number of Likes"), default=1),

View File

@ -9,6 +9,7 @@ urlpatterns = [
path('application/profile', views.Application.Profile.as_view(), name='application/profile'),
path('application/embed', views.Application.Embed.as_view()),
path('application/authentication', views.Application.Authentication.as_view()),
path('application/mcp_servers', views.Application.McpServers.as_view()),
path('application/<str:application_id>/publish', views.Application.Publish.as_view()),
path('application/<str:application_id>/edit_icon', views.Application.EditIcon.as_view()),
path('application/<str:application_id>/export', views.Application.Export.as_view()),

View File

@ -13,9 +13,11 @@ from rest_framework.views import APIView
from application.serializers.application_version_serializers import ApplicationVersionSerializer
from application.swagger_api.application_version_api import ApplicationVersionApi
from application.views import get_application_operation_object
from common.auth import has_permissions, TokenAuth
from common.constants.permission_constants import PermissionConstants, CompareConstants, ViewPermission, RoleConstants, \
Permission, Group, Operate
from common.log.log import log
from common.response import result
from django.utils.translation import gettext_lazy as _
@ -82,6 +84,8 @@ class ApplicationVersionView(APIView):
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND))
@log(menu='Application', operate="Modify application version information",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, work_flow_version_id: str):
return result.success(
ApplicationVersionSerializer.Operate(

View File

@ -9,8 +9,9 @@
from django.core import cache
from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_lazy as _, gettext
from drf_yasg.utils import swagger_auto_schema
from langchain_core.prompts import PromptTemplate
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
@ -20,10 +21,12 @@ from application.serializers.application_serializers import ApplicationSerialize
from application.serializers.application_statistics_serializers import ApplicationStatisticsSerializer
from application.swagger_api.application_api import ApplicationApi
from application.swagger_api.application_statistics_api import ApplicationStatisticsApi
from application.views.common import get_application_operation_object
from common.auth import TokenAuth, has_permissions
from common.constants.permission_constants import CompareConstants, PermissionConstants, Permission, Group, Operate, \
ViewPermission, RoleConstants
from common.exception.app_exception import AppAuthenticationFailed
from common.log.log import log
from common.response import result
from common.swagger_api.common_api import CommonApi
from common.util.common import query_params_to_single_dict
@ -152,6 +155,8 @@ class Application(APIView):
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), PermissionConstants.APPLICATION_EDIT,
compare=CompareConstants.AND)
@log(menu='Application', operate="Modify application icon",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str):
return result.success(
ApplicationSerializer.IconOperate(
@ -168,6 +173,7 @@ class Application(APIView):
tags=[_("Application")]
)
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
@log(menu='Application', operate="Import Application")
def post(self, request: Request):
return result.success(ApplicationSerializer.Import(
data={'user_id': request.user.id, 'file': request.FILES.get('file')}).import_())
@ -182,6 +188,8 @@ class Application(APIView):
)
@has_permissions(lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id')))
@log(menu='Application', operate="Export Application",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def get(self, request: Request, application_id: str):
return ApplicationSerializer.Operate(
data={'application_id': application_id, 'user_id': request.user.id}).export()
@ -335,6 +343,8 @@ class Application(APIView):
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND))
@log(menu='Application', operate="Add ApiKey",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def post(self, request: Request, application_id: str):
return result.success(
ApplicationSerializer.ApplicationKeySerializer(
@ -363,13 +373,16 @@ class Application(APIView):
operation_id=_("Modify application API_KEY"),
tags=[_('Application/API_KEY')],
manual_parameters=ApplicationApi.ApiKey.Operate.get_request_params_api(),
request_body=ApplicationApi.ApiKey.Operate.get_request_body_api())
request_body=ApplicationApi.ApiKey.Operate.get_request_body_api(),
responses=result.get_api_response(ApplicationApi.ApiKey.Operate.get_response_body_api()))
@has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), PermissionConstants.APPLICATION_EDIT,
compare=CompareConstants.AND)
@log(menu='Application', operate="Modify application API_KEY",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, api_key_id: str):
return result.success(
ApplicationSerializer.ApplicationKeySerializer.Operate(
@ -387,6 +400,8 @@ class Application(APIView):
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), PermissionConstants.APPLICATION_DELETE,
compare=CompareConstants.AND)
@log(menu='Application', operate="Delete Application API_KEY",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str, api_key_id: str):
return result.success(
ApplicationSerializer.ApplicationKeySerializer.Operate(
@ -401,12 +416,15 @@ class Application(APIView):
operation_id=_("Modify Application AccessToken"),
tags=[_('Application/Public Access')],
manual_parameters=ApplicationApi.AccessToken.get_request_params_api(),
request_body=ApplicationApi.AccessToken.get_request_body_api())
request_body=ApplicationApi.AccessToken.get_request_body_api(),
responses=result.get_api_response(ApplicationApi.AccessToken.get_response_body_api()))
@has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND))
@log(menu='Application', operate="Modify Application AccessToken",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str):
return result.success(
ApplicationSerializer.AccessTokenSerializer(data={'application_id': application_id}).edit(
@ -439,6 +457,7 @@ class Application(APIView):
@swagger_auto_schema(operation_summary=_("Application Certification"),
operation_id=_("Application Certification"),
request_body=ApplicationApi.Authentication.get_request_body_api(),
responses=result.get_api_response(ApplicationApi.Authentication.get_response_body_api()),
tags=[_("Application/Certification")],
security=[])
def post(self, request: Request):
@ -456,8 +475,11 @@ class Application(APIView):
@swagger_auto_schema(operation_summary=_("Create an application"),
operation_id=_("Create an application"),
request_body=ApplicationApi.Create.get_request_body_api(),
responses=result.get_api_response(ApplicationApi.Create.get_response_body_api()),
tags=[_('Application')])
@has_permissions(PermissionConstants.APPLICATION_CREATE, compare=CompareConstants.AND)
@log(menu='Application', operate="Create an application",
get_operation_object=lambda r, k: {'name': r.data.get('name')})
def post(self, request: Request):
return result.success(ApplicationSerializer.Create(data={'user_id': request.user.id}).insert(request.data))
@ -512,6 +534,8 @@ class Application(APIView):
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND))
@log(menu='Application', operate="Publishing an application",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str):
return result.success(
ApplicationSerializer.Operate(
@ -533,6 +557,8 @@ class Application(APIView):
compare=CompareConstants.AND),
lambda r, k: Permission(group=Group.APPLICATION, operate=Operate.DELETE,
dynamic_tag=k.get('application_id')), compare=CompareConstants.AND)
@log(menu='Application', operate="Deleting application",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str):
return result.success(ApplicationSerializer.Operate(
data={'application_id': application_id, 'user_id': request.user.id}).delete(
@ -550,6 +576,8 @@ class Application(APIView):
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND))
@log(menu='Application', operate="Modify the application",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str):
return result.success(
ApplicationSerializer.Operate(
@ -660,8 +688,19 @@ class Application(APIView):
dynamic_tag=keywords.get(
'application_id'))],
compare=CompareConstants.AND))
@log(menu='Application', operate="trial listening",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def post(self, request: Request, application_id: str):
byte_data = ApplicationSerializer.Operate(
data={'application_id': application_id, 'user_id': request.user.id}).play_demo_text(request.data)
return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
'Content-Disposition': 'attachment; filename="abc.mp3"'})
class McpServers(APIView):
authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False)
@has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND)
def get(self, request: Request):
return result.success(ApplicationSerializer.McpServers(
data={'mcp_servers': request.query_params.get('mcp_servers')}).get_mcp_servers())

View File

@ -8,8 +8,10 @@
"""
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
from rest_framework.views import APIView
@ -17,10 +19,12 @@ from application.serializers.chat_message_serializers import ChatMessageSerializ
from application.serializers.chat_serializers import ChatSerializers, ChatRecordSerializer
from application.swagger_api.chat_api import ChatApi, VoteApi, ChatRecordApi, ImproveApi, ChatRecordImproveApi, \
ChatClientHistoryApi, OpenAIChatApi
from application.views import get_application_operation_object
from common.auth import TokenAuth, has_permissions, OpenAIKeyAuth
from common.constants.authentication_type import AuthenticationType
from common.constants.permission_constants import Permission, Group, Operate, \
RoleConstants, ViewPermission, CompareConstants
from common.log.log import log
from common.response import result
from common.util.common import query_params_to_single_dict
from dataset.serializers.file_serializers import FileSerializer
@ -33,6 +37,7 @@ class Openai(APIView):
@swagger_auto_schema(operation_summary=_("OpenAI Interface Dialogue"),
operation_id=_("OpenAI Interface Dialogue"),
request_body=OpenAIChatApi.get_request_body_api(),
responses=OpenAIChatApi.get_response_body_api(),
tags=[_("OpenAI Dialogue")])
def post(self, request: Request, application_id: str):
return OpenAIChatSerializer(data={'application_id': application_id, 'client_id': request.auth.client_id,
@ -56,6 +61,8 @@ class ChatView(APIView):
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))])
)
@log(menu='Conversation Log', operate="Export conversation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def post(self, request: Request, application_id: str):
return ChatSerializers.Query(
data={**query_params_to_single_dict(request.query_params), 'application_id': application_id,
@ -87,10 +94,11 @@ class ChatView(APIView):
@swagger_auto_schema(operation_summary=_("Get the workflow temporary session id"),
operation_id=_("Get the workflow temporary session id"),
request_body=ChatApi.OpenWorkFlowTemp.get_request_body_api(),
responses=result.get_api_response(ChatApi.OpenTempChat.get_response_body_api()),
tags=[_("Application/Chat")])
def post(self, request: Request):
return result.success(ChatSerializers.OpenWorkFlowChat(
data={**request.data, 'user_id': request.user.id}).open())
data={'user_id': request.user.id, **request.data}).open())
class OpenTemp(APIView):
authentication_classes = [TokenAuth]
@ -99,6 +107,7 @@ class ChatView(APIView):
@swagger_auto_schema(operation_summary=_("Get a temporary session id"),
operation_id=_("Get a temporary session id"),
request_body=ChatApi.OpenTempChat.get_request_body_api(),
responses=result.get_api_response(ChatApi.OpenTempChat.get_response_body_api()),
tags=[_("Application/Chat")])
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
def post(self, request: Request):
@ -137,6 +146,8 @@ class ChatView(APIView):
'document_list') if 'document_list' in request.data else [],
'audio_list': request.data.get(
'audio_list') if 'audio_list' in request.data else [],
'other_list': request.data.get(
'other_list') if 'other_list' in request.data else [],
'client_type': request.auth.client_type,
'node_id': request.data.get('node_id', None),
'runtime_node_id': request.data.get('runtime_node_id', None),
@ -150,7 +161,7 @@ class ChatView(APIView):
operation_id=_("Get the conversation list"),
manual_parameters=ChatApi.get_request_params_api(),
responses=result.get_api_array_response(ChatApi.get_response_body_api()),
tags=[_("Application/Conversation Log")]
tags=[_("Application/Conversation Log")]
)
@has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY],
@ -175,6 +186,8 @@ class ChatView(APIView):
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND),
compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Delete a conversation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str, chat_id: str):
return result.success(
ChatSerializers.Operate(
@ -216,12 +229,34 @@ class ChatView(APIView):
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND),
compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Client deletes conversation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str, chat_id: str):
return result.success(
ChatSerializers.Operate(
data={'application_id': application_id, 'user_id': request.user.id,
'chat_id': chat_id}).logic_delete())
@action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Client modifies dialogue summary"),
operation_id=_("Client modifies dialogue summary"),
request_body=ChatClientHistoryApi.Operate.ReAbstract.get_request_body_api(),
responses=result.get_default_response(),
tags=[_("Application/Conversation Log")])
@has_permissions(ViewPermission(
[RoleConstants.APPLICATION_ACCESS_TOKEN, RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND),
compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Client modifies dialogue summary",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, chat_id: str):
return result.success(
ChatSerializers.Operate(
data={'application_id': application_id, 'user_id': request.user.id,
'chat_id': chat_id}).re_abstract(request.data))
class Page(APIView):
authentication_classes = [TokenAuth]
@ -324,6 +359,8 @@ class ChatView(APIView):
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))])
)
@log(menu='Conversation Log', operate="Like, Dislike",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, chat_id: str, chat_record_id: str):
return result.success(ChatRecordSerializer.Vote(
data={'vote_status': request.data.get('vote_status'), 'chat_id': chat_id,
@ -371,6 +408,8 @@ class ChatView(APIView):
'dataset_id'))],
compare=CompareConstants.AND
), compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Annotation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, chat_id: str, chat_record_id: str, dataset_id: str,
document_id: str):
return result.success(ChatRecordSerializer.Improve(
@ -382,6 +421,7 @@ class ChatView(APIView):
operation_id=_("Add to Knowledge Base"),
manual_parameters=ImproveApi.get_request_params_api_post(),
request_body=ImproveApi.get_request_body_api_post(),
responses=result.get_default_response(),
tags=[_("Application/Conversation Log/Add to Knowledge Base")]
)
@has_permissions(
@ -396,6 +436,8 @@ class ChatView(APIView):
'dataset_id'))],
compare=CompareConstants.AND
), compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Add to Knowledge Base",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def post(self, request: Request, application_id: str, dataset_id: str):
return result.success(ChatRecordSerializer.PostImprove().post_improve(request.data))
@ -421,6 +463,8 @@ class ChatView(APIView):
'dataset_id'))],
compare=CompareConstants.AND
), compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Delete a Annotation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str, chat_id: str, chat_record_id: str,
dataset_id: str,
document_id: str, paragraph_id: str):
@ -431,11 +475,28 @@ class ChatView(APIView):
class UploadFile(APIView):
authentication_classes = [TokenAuth]
parser_classes = [MultiPartParser]
@action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Upload files"),
operation_id=_("Upload files"),
manual_parameters=ChatRecordApi.get_request_params_api(),
manual_parameters=[
openapi.Parameter(name='application_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Application ID')),
openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Conversation ID')),
openapi.Parameter(name='file',
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
required=True,
description=_('Upload file'))
],
tags=[_("Application/Conversation Log")]
)
@has_permissions(

View File

@ -0,0 +1,21 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file common.py
@date2025/3/25 16:56
@desc:
"""
from django.db.models import QuerySet
from application.models import Application
def get_application_operation_object(application_id):
application_model = QuerySet(model=Application).filter(id=application_id).first()
if application_model is not None:
return {
"name": application_model.name
}
return {}

View File

@ -11,35 +11,50 @@ import time
from common.cache.mem_cache import MemCache
lock = threading.Lock()
_lock = threading.Lock()
locks = {}
class ModelManage:
cache = MemCache('model', {})
up_clear_time = time.time()
@staticmethod
def _get_lock(_id):
lock = locks.get(_id)
if lock is None:
with _lock:
lock = locks.get(_id)
if lock is None:
lock = threading.Lock()
locks[_id] = lock
return lock
@staticmethod
def get_model(_id, get_model):
# 获取锁
lock.acquire()
try:
model_instance = ModelManage.cache.get(_id)
if model_instance is None or not model_instance.is_cache_model():
model_instance = ModelManage.cache.get(_id)
if model_instance is None:
lock = ModelManage._get_lock(_id)
with lock:
model_instance = ModelManage.cache.get(_id)
if model_instance is None:
model_instance = get_model(_id)
ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8)
else:
if model_instance.is_cache_model():
ModelManage.cache.touch(_id, timeout=60 * 60 * 8)
else:
model_instance = get_model(_id)
ModelManage.cache.set(_id, model_instance, timeout=60 * 30)
return model_instance
# 续期
ModelManage.cache.touch(_id, timeout=60 * 30)
ModelManage.clear_timeout_cache()
return model_instance
finally:
# 释放锁
lock.release()
ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8)
ModelManage.clear_timeout_cache()
return model_instance
@staticmethod
def clear_timeout_cache():
if time.time() - ModelManage.up_clear_time > 60:
ModelManage.cache.clear_timeout_data()
if time.time() - ModelManage.up_clear_time > 60 * 60:
threading.Thread(target=lambda: ModelManage.cache.clear_timeout_data()).start()
ModelManage.up_clear_time = time.time()
@staticmethod
def delete_key(_id):

View File

@ -6,7 +6,7 @@
@date2023/9/5 14:01
@desc: 用于swagger 分组
"""
from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.inspectors import SwaggerAutoSchema
tags_dict = {
@ -20,10 +20,10 @@ class CustomSwaggerAutoSchema(SwaggerAutoSchema):
if "api" in tags and operation_keys:
return [tags_dict.get(operation_keys[1]) if operation_keys[1] in tags_dict else operation_keys[1]]
return tags
class CustomOpenAPISchemaGenerator(OpenAPISchemaGenerator):
def get_schema(self, request=None, public=False):
schema = super().get_schema(request, public)
if request.is_secure():
schema.schemes = ['https']
else:
schema.schemes = ['http']
return schema
schema.schemes = ['https', 'http']
return schema

View File

@ -0,0 +1,30 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file SystemEncoder.py
@date2025/3/17 16:38
@desc:
"""
import datetime
import decimal
import json
import uuid
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile
class SystemEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, uuid.UUID):
return str(obj)
if isinstance(obj, datetime.datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
if isinstance(obj, decimal.Decimal):
return float(obj)
if isinstance(obj, InMemoryUploadedFile):
return {'name': obj.name, 'size': obj.size}
if isinstance(obj, TemporaryUploadedFile):
return {'name': obj.name, 'size': obj.size}
else:
return json.JSONEncoder.default(self, obj)

View File

@ -12,15 +12,22 @@ from .listener_manage import *
from django.utils.translation import gettext as _
from ..db.sql_execute import update_execute
from common.lock.impl.file_lock import FileLock
lock = FileLock()
update_document_status_sql = """
UPDATE "public"."document"
SET status ="replace"("replace"("replace"(status, '1', '3'), '0', '3'), '4', '3')
WHERE status ~ '1|0|4'
"""
def run():
# QuerySet(Document).filter(status__in=[Status.embedding, Status.queue_up]).update(**{'status': Status.error})
QuerySet(Model).filter(status=setting.models.Status.DOWNLOAD).update(status=setting.models.Status.ERROR,
meta={'message': _('The download process was interrupted, please try again')})
update_execute(update_document_status_sql, [])
if lock.try_lock('event_init', 30 * 30):
try:
QuerySet(Model).filter(status=setting.models.Status.DOWNLOAD).update(status=setting.models.Status.ERROR,
meta={'message': _(
'The download process was interrupted, please try again')})
update_execute(update_document_status_sql, [])
finally:
lock.un_lock('event_init')

View File

@ -238,11 +238,8 @@ class ListenerManagement:
for key in params_dict:
_value_ = params_dict[key]
exec_sql = exec_sql.replace(key, str(_value_))
lock.acquire()
try:
with lock:
native_update(query_set, exec_sql)
finally:
lock.release()
@staticmethod
def embedding_by_document(document_id, embedding_model: Embeddings, state_list=None):
@ -272,9 +269,6 @@ class ListenerManagement:
ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING,
State.STARTED)
# 删除文档向量数据
VectorStore.get_embedding_vector().delete_by_document_id(document_id)
# 根据段落进行向量化处理
page_desc(QuerySet(Paragraph)
.annotate(

View File

@ -22,3 +22,4 @@ from .table_checkbox import *
from .radio_card_field import *
from .label import *
from .slider_field import *
from .switch_field import *

View File

@ -28,6 +28,6 @@ class SwitchField(BaseField):
@param props_info:
"""
super().__init__('Switch', label, required, default_value, relation_show_field_dict,
super().__init__('SwitchInput', label, required, default_value, relation_show_field_dict,
{},
TriggerType.OPTION_LIST, attrs, props_info)

View File

@ -110,24 +110,47 @@ def get_image_id_func():
return get_image_id
title_font_list = [
[36, 100],
[30, 36]
]
def get_title_level(paragraph: Paragraph):
try:
if paragraph.style is not None:
psn = paragraph.style.name
if psn.startswith('Heading') or psn.startswith('TOC 标题') or psn.startswith('标题'):
return int(psn.replace("Heading ", '').replace('TOC 标题', '').replace('标题',
''))
if len(paragraph.runs) == 1:
font_size = paragraph.runs[0].font.size
pt = font_size.pt
if pt >= 30:
for _value, index in zip(title_font_list, range(len(title_font_list))):
if pt >= _value[0] and pt < _value[1]:
return index + 1
except Exception as e:
pass
return None
class DocSplitHandle(BaseSplitHandle):
@staticmethod
def paragraph_to_md(paragraph: Paragraph, doc: Document, images_list, get_image_id):
try:
psn = paragraph.style.name
if psn.startswith('Heading') or psn.startswith('TOC 标题') or psn.startswith('标题'):
title = "".join(["#" for i in range(
int(psn.replace("Heading ", '').replace('TOC 标题', '').replace('标题',
'')))]) + " " + paragraph.text
title_level = get_title_level(paragraph)
if title_level is not None:
title = "".join(["#" for i in range(title_level)]) + " " + paragraph.text
images = reduce(lambda x, y: [*x, *y],
[get_paragraph_element_images(e, doc, images_list, get_image_id) for e in
paragraph._element],
[])
if len(images) > 0:
return title + '\n' + images_to_string(images, doc, images_list, get_image_id) if len(
paragraph.text) > 0 else images_to_string(images, doc, images_list, get_image_id)
return title
except Exception as e:
traceback.print_exc()
return paragraph.text

View File

@ -173,14 +173,15 @@ class PdfSplitHandle(BaseSplitHandle):
# Null characters are not allowed.
chapter_text = chapter_text.replace('\0', '')
# 限制标题长度
real_chapter_title = chapter_title[:256]
# 限制章节内容长度
if 0 < limit < len(chapter_text):
split_text = PdfSplitHandle.split_text(chapter_text, limit)
for text in split_text:
chapters.append({"title": chapter_title, "content": text})
chapters.append({"title": real_chapter_title, "content": text})
else:
chapters.append({"title": chapter_title, "content": chapter_text if chapter_text else chapter_title})
chapters.append({"title": real_chapter_title, "content": chapter_text if chapter_text else real_chapter_title})
# 保存章节内容和章节标题
return chapters

View File

@ -132,7 +132,8 @@ class ZipParseQAHandle(BaseParseQAHandle):
files = zip_ref.namelist()
# 读取压缩包中的文件内容
for file in files:
if file.endswith('/'):
# 跳过 macOS 特有的元数据目录和文件
if file.endswith('/') or file.startswith('__MACOSX'):
continue
with zip_ref.open(file) as f:
# 对文件内容进行处理

View File

@ -22,8 +22,11 @@ class OpenaiToResponse(BaseToResponse):
def to_block_response(self, chat_id, chat_record_id, content, is_end, completion_tokens, prompt_tokens,
other_params: dict = None,
_status=status.HTTP_200_OK):
if other_params is None:
other_params = {}
data = ChatCompletion(id=chat_record_id, choices=[
BlockChoice(finish_reason='stop', index=0, chat_id=chat_id,
answer_list=other_params.get('answer_list', ""),
message=ChatCompletionMessage(role='assistant', content=content))],
created=datetime.datetime.now().second, model='', object='chat.completion',
usage=CompletionUsage(completion_tokens=completion_tokens,
@ -32,11 +35,16 @@ class OpenaiToResponse(BaseToResponse):
).dict()
return JsonResponse(data=data, status=_status)
def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end, completion_tokens,
def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end,
completion_tokens,
prompt_tokens, other_params: dict = None):
if other_params is None:
other_params = {}
chunk = ChatCompletionChunk(id=chat_record_id, model='', object='chat.completion.chunk',
created=datetime.datetime.now().second,choices=[
Choice(delta=ChoiceDelta(content=content, chat_id=chat_id), finish_reason='stop' if is_end else None,
created=datetime.datetime.now().second, choices=[
Choice(delta=ChoiceDelta(content=content, reasoning_content=other_params.get('reasoning_content', ""),
chat_id=chat_id),
finish_reason='stop' if is_end else None,
index=0)],
usage=CompletionUsage(completion_tokens=completion_tokens,
prompt_tokens=prompt_tokens,

View File

@ -82,7 +82,10 @@ class XlsSplitHandle(BaseParseTableHandle):
for row in data:
# 将每个单元格中的内容替换换行符为 <br> 以保留原始格式
md_table += '| ' + ' | '.join(
[str(cell).replace('\n', '<br>') if cell else '' for cell in row]) + ' |\n'
[str(cell)
.replace('\r\n', '<br>')
.replace('\n', '<br>')
if cell else '' for cell in row]) + ' |\n'
md_tables += md_table + '\n\n'
return md_tables

View File

@ -19,31 +19,24 @@ class XlsxSplitHandle(BaseParseTableHandle):
def fill_merged_cells(self, sheet, image_dict):
data = []
# 获取第一行作为标题行
headers = [cell.value for cell in sheet[1]]
# 从第二行开始遍历每一行
for row in sheet.iter_rows(min_row=2, values_only=False):
row_data = {}
for row in sheet.iter_rows(values_only=False):
row_data = []
for col_idx, cell in enumerate(row):
cell_value = cell.value
# 如果单元格为空,并且该单元格在合并单元格内,获取合并单元格的值
if cell_value is None:
for merged_range in sheet.merged_cells.ranges:
if cell.coordinate in merged_range:
cell_value = sheet[merged_range.min_row][merged_range.min_col - 1].value
break
image = image_dict.get(cell_value, None)
if image is not None:
cell_value = f'![](/api/image/{image.id})'
# 使用标题作为键,单元格的值作为值存入字典
row_data[headers[col_idx]] = cell_value
row_data.insert(col_idx, cell_value)
data.append(row_data)
for merged_range in sheet.merged_cells.ranges:
cell_value = data[merged_range.min_row - 1][merged_range.min_col - 1]
for row_index in range(merged_range.min_row, merged_range.max_row + 1):
for col_index in range(merged_range.min_col, merged_range.max_col + 1):
data[row_index - 1][col_index - 1] = cell_value
return data
def handle(self, file, get_buffer, save_image):
@ -60,11 +53,13 @@ class XlsxSplitHandle(BaseParseTableHandle):
paragraphs = []
ws = wb[sheetname]
data = self.fill_merged_cells(ws, image_dict)
for row in data:
row_output = "; ".join([f"{key}: {value}" for key, value in row.items()])
# print(row_output)
paragraphs.append({'title': '', 'content': row_output})
if len(data) >= 2:
head_list = data[0]
for row_index in range(1, len(data)):
row_output = "; ".join(
[f"{head_list[col_index]}: {data[row_index][col_index]}" for col_index in
range(0, len(data[row_index]))])
paragraphs.append({'title': '', 'content': row_output})
result.append({'name': sheetname, 'paragraphs': paragraphs})
@ -73,7 +68,6 @@ class XlsxSplitHandle(BaseParseTableHandle):
return [{'name': file.name, 'paragraphs': []}]
return result
def get_content(self, file, save_image):
try:
# 加载 Excel 文件
@ -89,18 +83,18 @@ class XlsxSplitHandle(BaseParseTableHandle):
# 如果未指定 sheet_name则使用第一个工作表
for sheetname in workbook.sheetnames:
sheet = workbook[sheetname] if sheetname else workbook.active
rows = self.fill_merged_cells(sheet, image_dict)
if len(rows) == 0:
data = self.fill_merged_cells(sheet, image_dict)
if len(data) == 0:
continue
# 提取表头和内容
headers = [f"{key}" for key, value in rows[0].items()]
headers = [f"{value}" for value in data[0]]
# 构建 Markdown 表格
md_table = '| ' + ' | '.join(headers) + ' |\n'
md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\n'
for row in rows:
r = [f'{value}' for key, value in row.items()]
for row_index in range(1, len(data)):
r = [f'{value}' for value in data[row_index]]
md_table += '| ' + ' | '.join(
[str(cell).replace('\n', '<br>') if cell is not None else '' for cell in r]) + ' |\n'

View File

@ -14,7 +14,7 @@ from common.handle.base_split_handle import BaseSplitHandle
def post_cell(cell_value):
return cell_value.replace('\n', '<br>').replace('|', '&#124;')
return cell_value.replace('\r\n', '<br>').replace('\n', '<br>').replace('|', '&#124;')
def row_to_md(row):

View File

@ -14,6 +14,7 @@ import zipfile
from typing import List
from urllib.parse import urljoin
from charset_normalizer import detect
from django.db.models import QuerySet
from common.handle.base_split_handle import BaseSplitHandle
@ -28,6 +29,7 @@ from common.util.common import parse_md_image
from dataset.models import Image
from django.utils.translation import gettext_lazy as _
class FileBufferHandle:
buffer = None
@ -75,6 +77,7 @@ def get_image_list(result_list: list, zip_files: List[str]):
if search:
new_image_id = str(uuid.uuid1())
source_image_path = search.group().replace('(', '').replace(')', '')
source_image_path = source_image_path.strip().split(" ")[0]
image_path = urljoin(result.get('name'), '.' + source_image_path if source_image_path.startswith(
'/') else source_image_path)
if not zip_files.__contains__(image_path):
@ -98,6 +101,15 @@ def get_image_list(result_list: list, zip_files: List[str]):
return image_file_list
def get_file_name(file_name):
try:
file_name_code = file_name.encode('cp437')
charset = detect(file_name_code)['encoding']
return file_name_code.decode(charset)
except Exception as e:
return file_name
def filter_image_file(result_list: list, image_list):
image_source_file_list = [image.get('source_file') for image in image_list]
return [r for r in result_list if not image_source_file_list.__contains__(r.get('name', ''))]
@ -114,11 +126,13 @@ class ZipSplitHandle(BaseSplitHandle):
files = zip_ref.namelist()
# 读取压缩包中的文件内容
for file in files:
if file.endswith('/'):
if file.endswith('/') or file.startswith('__MACOSX'):
continue
with zip_ref.open(file) as f:
# 对文件内容进行处理
try:
# 处理一下文件名
f.name = get_file_name(f.name)
value = file_to_paragraph(f, pattern_list, with_filter, limit)
if isinstance(value, list):
result = [*result, *value]

100
apps/common/log/log.py Normal file
View File

@ -0,0 +1,100 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file log.py
@date2025/3/14 16:09
@desc:
"""
from gettext import gettext
from setting.models.log_management import Log
def _get_ip_address(request):
"""
获取ip地址
@param request:
@return:
"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def _get_user(request):
"""
获取用户
@param request:
@return:
"""
user = request.user
if user is None:
return {
}
return {
"id": str(user.id),
"email": user.email,
"phone": user.phone,
"nick_name": user.nick_name,
"username": user.username,
"role": user.role,
}
def _get_details(request):
path = request.path
body = request.data
query = request.query_params
return {
'path': path,
'body': body,
'query': query
}
def log(menu: str, operate, get_user=_get_user, get_ip_address=_get_ip_address, get_details=_get_details,
get_operation_object=None):
"""
记录审计日志
@param menu: 操作菜单 str
@param operate: 操作 str|func 如果是一个函数 入参将是一个request 响应为str def operate(request): return "操作菜单"
@param get_user: 获取用户
@param get_ip_address:获取IP地址
@param get_details: 获取执行详情
@param get_operation_object: 获取操作对象
@return:
"""
def inner(func):
def run(view, request, **kwargs):
status = 200
operation_object = {}
try:
if get_operation_object is not None:
operation_object = get_operation_object(request, kwargs)
except Exception as e:
pass
try:
return func(view, request, **kwargs)
except Exception as e:
status = 500
raise e
finally:
ip = get_ip_address(request)
user = get_user(request)
details = get_details(request)
_operate = operate
if callable(operate):
_operate = operate(request)
# 插入审计日志
Log(menu=menu, operate=_operate, user=user, status=status, ip_address=ip, details=details,
operation_object=operation_object).save()
return run
return inner

View File

@ -24,12 +24,13 @@ class GunicornLocalModelService(BaseService):
os.environ.setdefault('SERVER_NAME', 'local_model')
log_format = '%(h)s %(t)s %(L)ss "%(r)s" %(s)s %(b)s '
bind = f'{CONFIG.get("LOCAL_MODEL_HOST")}:{CONFIG.get("LOCAL_MODEL_PORT")}'
worker = CONFIG.get("LOCAL_MODEL_HOST_WORKER", 1)
cmd = [
'gunicorn', 'smartdoc.wsgi:application',
'-b', bind,
'-k', 'gthread',
'--threads', '200',
'-w', "1",
'-w', str(worker),
'--max-requests', '10240',
'--max-requests-jitter', '2048',
'--access-logformat', log_format,

View File

@ -0,0 +1,62 @@
# coding=utf-8
"""
@project: maxkb
@Author
@file static_headers_middleware.py
@date2024/3/13 18:26
@desc:
"""
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
content = """
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script>
window.onload = () => {
var xhr = new XMLHttpRequest()
xhr.open('GET', '/api/user', true)
xhr.setRequestHeader('Content-Type', 'application/json')
const token = localStorage.getItem('token')
const pathname = window.location.pathname
if (token) {
xhr.setRequestHeader('Authorization', token)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
window.location.href = pathname
}
if (xhr.status === 401) {
window.location.href = '/ui/login'
}
}
}
xhr.send()
} else {
window.location.href = '/ui/login'
}
}
</script>
</head>
<body></body>
</html>
"""
class DocHeadersMiddleware(MiddlewareMixin):
def process_response(self, request, response):
if request.path.startswith('/doc/') or request.path.startswith('/doc/chat/'):
HTTP_REFERER = request.META.get('HTTP_REFERER')
if HTTP_REFERER is None:
return HttpResponse(content)
if HTTP_REFERER == request._current_scheme_host + request.path:
return response
return response

View File

@ -0,0 +1,84 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file gzip.py
@date2025/2/27 10:03
@desc:
"""
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.regex_helper import _lazy_re_compile
from django.utils.text import compress_sequence, compress_string
re_accepts_gzip = _lazy_re_compile(r"\bgzip\b")
class GZipMiddleware(MiddlewareMixin):
"""
Compress content if the browser allows gzip compression.
Set the Vary header accordingly, so that caches will base their storage
on the Accept-Encoding header.
"""
max_random_bytes = 100
def process_response(self, request, response):
if request.method != 'GET' or request.path.startswith('/api'):
return response
# It's not worth attempting to compress really short responses.
if not response.streaming and len(response.content) < 200:
return response
# Avoid gzipping if we've already got a content-encoding.
if response.has_header("Content-Encoding"):
return response
patch_vary_headers(response, ("Accept-Encoding",))
ae = request.META.get("HTTP_ACCEPT_ENCODING", "")
if not re_accepts_gzip.search(ae):
return response
if response.streaming:
if response.is_async:
# pull to lexical scope to capture fixed reference in case
# streaming_content is set again later.
original_iterator = response.streaming_content
async def gzip_wrapper():
async for chunk in original_iterator:
yield compress_string(
chunk,
max_random_bytes=self.max_random_bytes,
)
response.streaming_content = gzip_wrapper()
else:
response.streaming_content = compress_sequence(
response.streaming_content,
max_random_bytes=self.max_random_bytes,
)
# Delete the `Content-Length` header for streaming content, because
# we won't know the compressed size until we stream it.
del response.headers["Content-Length"]
else:
# Return the compressed content only if it's actually shorter.
compressed_content = compress_string(
response.content,
max_random_bytes=self.max_random_bytes,
)
if len(compressed_content) >= len(response.content):
return response
response.content = compressed_content
response.headers["Content-Length"] = str(len(response.content))
# If there is a strong ETag, make it weak to fulfill the requirements
# of RFC 9110 Section 8.8.1 while also allowing conditional request
# matches on ETags.
etag = response.get("ETag")
if etag and etag.startswith('"'):
response.headers["ETag"] = "W/" + etag
response.headers["Content-Encoding"] = "gzip"
return response

View File

@ -10,6 +10,8 @@ import hashlib
import importlib
import io
import mimetypes
import pickle
import random
import re
import shutil
from functools import reduce
@ -23,6 +25,51 @@ from pydub import AudioSegment
from ..exception.app_exception import AppApiException
from ..models.db_model_manage import DBModelManage
safe_builtins = {
'MKInstance'
}
ALLOWED_CLASSES = {
("builtins", "dict"),
('uuid', 'UUID'),
("application.serializers.application_serializers", "MKInstance"),
("function_lib.serializers.function_lib_serializer", "FlibInstance")
}
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if (module, name) in ALLOWED_CLASSES:
return super().find_class(module, name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" %
(module, name))
def restricted_loads(s):
"""Helper function analogous to pickle.loads()."""
return RestrictedUnpickler(io.BytesIO(s)).load()
def encryption(message: str):
"""
加密敏感字段数据 加密方式是 如果密码是 1234567890 那么给前端则是 123******890
:param message:
:return:
"""
max_pre_len = 8
max_post_len = 4
message_len = len(message)
pre_len = int(message_len / 5 * 2)
post_len = int(message_len / 5 * 1)
pre_str = "".join([message[index] for index in
range(0, max_pre_len if pre_len > max_pre_len else 1 if pre_len <= 0 else int(pre_len))])
end_str = "".join(
[message[index] for index in
range(message_len - (int(post_len) if pre_len < max_post_len else max_post_len), message_len)])
content = "***************"
return pre_str + content + end_str
def sub_array(array: List, item_num=10):
result = []
@ -251,3 +298,14 @@ def markdown_to_plain_text(md: str) -> str:
# 去除首尾空格
text = text.strip()
return text
SAFE_CHAR_SET = (
[chr(i) for i in range(65, 91) if chr(i) not in {'I', 'O'}] + # 大写字母 A-H, J-N, P-Z
[chr(i) for i in range(97, 123) if chr(i) not in {'i', 'l', 'o'}] + # 小写字母 a-h, j-n, p-z
[str(i) for i in range(10) if str(i) not in {'0', '1', '7'}] # 数字 2-6, 8-9
)
def get_random_chars(number=4):
return ''.join(random.choices(SAFE_CHAR_SET, k=number))

View File

@ -7,13 +7,12 @@
@desc:
"""
import os
import pickle
import subprocess
import sys
import uuid
from textwrap import dedent
from diskcache import Cache
from smartdoc.const import BASE_DIR
from smartdoc.const import PROJECT_DIR
@ -31,12 +30,14 @@ class FunctionExecutor:
self.user = None
self._createdir()
if self.sandbox:
os.system(f"chown -R {self.user}:{self.user} {self.sandbox_path}")
os.system(f"chown -R {self.user}:root {self.sandbox_path}")
def _createdir(self):
old_mask = os.umask(0o077)
try:
os.makedirs(self.sandbox_path, 0o700, exist_ok=True)
os.makedirs(os.path.join(self.sandbox_path, 'execute'), 0o700, exist_ok=True)
os.makedirs(os.path.join(self.sandbox_path, 'result'), 0o700, exist_ok=True)
finally:
os.umask(old_mask)
@ -44,10 +45,11 @@ class FunctionExecutor:
_id = str(uuid.uuid1())
success = '{"code":200,"msg":"成功","data":exec_result}'
err = '{"code":500,"msg":str(e),"data":None}'
path = r'' + self.sandbox_path + ''
result_path = f'{self.sandbox_path}/result/{_id}.result'
_exec_code = f"""
try:
import os
import pickle
env = dict(os.environ)
for key in list(env.keys()):
if key in os.environ and (key.startswith('MAXKB') or key.startswith('POSTGRES') or key.startswith('PG')):
@ -60,13 +62,11 @@ try:
for local in locals_v:
globals_v[local] = locals_v[local]
exec_result=f(**keywords)
from diskcache import Cache
cache = Cache({path!a})
cache.set({_id!a},{success})
with open({result_path!a}, 'wb') as file:
file.write(pickle.dumps({success}))
except Exception as e:
from diskcache import Cache
cache = Cache({path!a})
cache.set({_id!a},{err})
with open({result_path!a}, 'wb') as file:
file.write(pickle.dumps({err}))
"""
if self.sandbox:
subprocess_result = self._exec_sandbox(_exec_code, _id)
@ -74,21 +74,21 @@ except Exception as e:
subprocess_result = self._exec(_exec_code)
if subprocess_result.returncode == 1:
raise Exception(subprocess_result.stderr)
cache = Cache(self.sandbox_path)
result = cache.get(_id)
cache.delete(_id)
with open(result_path, 'rb') as file:
result = pickle.loads(file.read())
os.remove(result_path)
if result.get('code') == 200:
return result.get('data')
raise Exception(result.get('msg'))
def _exec_sandbox(self, _code, _id):
exec_python_file = f'{self.sandbox_path}/{_id}.py'
exec_python_file = f'{self.sandbox_path}/execute/{_id}.py'
with open(exec_python_file, 'w') as file:
file.write(_code)
os.system(f"chown {self.user}:{self.user} {exec_python_file}")
os.system(f"chown {self.user}:root {exec_python_file}")
kwargs = {'cwd': BASE_DIR}
subprocess_result = subprocess.run(
['su', '-c', python_directory + ' ' + exec_python_file, self.user],
['su', '-s', python_directory, '-c', "exec(open('" + exec_python_file + "').read())", self.user],
text=True,
capture_output=True, **kwargs)
os.remove(exec_python_file)

View File

@ -40,15 +40,12 @@ def generate():
def get_key_pair():
rsa_value = rsa_cache.get(cache_key)
if rsa_value is None:
lock.acquire()
rsa_value = rsa_cache.get(cache_key)
if rsa_value is not None:
return rsa_value
try:
with lock:
rsa_value = rsa_cache.get(cache_key)
if rsa_value is not None:
return rsa_value
rsa_value = get_key_pair_by_sql()
rsa_cache.set(cache_key, rsa_value)
finally:
lock.release()
return rsa_value

View File

@ -339,13 +339,14 @@ class SplitModel:
for e in result:
if len(e['content']) > 4096:
pass
return [item for item in [self.post_reset_paragraph(row) for row in result] if
title_list = list(set([row.get('title') for row in result]))
return [item for item in [self.post_reset_paragraph(row, title_list) for row in result] if
'content' in item and len(item.get('content').strip()) > 0]
def post_reset_paragraph(self, paragraph: Dict):
result = self.filter_title_special_characters(paragraph)
def post_reset_paragraph(self, paragraph: Dict, title_list: List[str]):
result = self.content_is_null(paragraph, title_list)
result = self.filter_title_special_characters(result)
result = self.sub_title(result)
result = self.content_is_null(result)
return result
@staticmethod
@ -357,11 +358,14 @@ class SplitModel:
return paragraph
@staticmethod
def content_is_null(paragraph: Dict):
def content_is_null(paragraph: Dict, title_list: List[str]):
if 'title' in paragraph:
title = paragraph.get('title')
content = paragraph.get('content')
if (content is None or len(content.strip()) == 0) and (title is not None and len(title) > 0):
find = [t for t in title_list if t.__contains__(title) and t != title]
if find:
return {'title': '', 'content': ''}
return {'title': '', 'content': title}
return paragraph

View File

@ -13,7 +13,7 @@ from django.core import signing
from django.core.cache import cache
# alg使用的算法
HEADER = {'typ': 'JWP', 'alg': 'default'}
HEADER = {'type': 'JWP', 'alg': 'default'}
TOKEN_KEY = 'solomon_world_token'
TOKEN_SALT = 'solomonwanc@gmail.com'
TIME_OUT = 30 * 60

View File

@ -12,9 +12,6 @@ from typing import List
import jieba
import jieba.posseg
from jieba import analyse
from common.util.split_model import group_by
jieba_word_list_cache = [chr(item) for item in range(38, 84)]
@ -80,37 +77,12 @@ def get_key_by_word_dict(key, word_dict):
def to_ts_vector(text: str):
# 获取不分词的数据
word_list = get_word_list(text)
# 获取关键词关系
word_dict = to_word_dict(word_list, text)
# 替换字符串
text = replace_word(word_dict, text)
# 分词
filter_word = jieba.analyse.extract_tags(text, topK=100)
result = jieba.lcut(text, HMM=True, use_paddle=True)
# 过滤标点符号
result = [item for item in result if filter_word.__contains__(item) and len(item) < 10]
result_ = [{'word': get_key_by_word_dict(result[index], word_dict), 'index': index} for index in
range(len(result))]
result_group = group_by(result_, lambda r: r['word'])
return " ".join(
[f"{key.lower()}:{','.join([str(item['index'] + 1) for item in result_group[key]][:20])}" for key in
result_group if
not remove_chars.__contains__(key) and len(key.strip()) >= 0])
result = jieba.lcut(text, cut_all=True)
return " ".join(result)
def to_query(text: str):
# 获取不分词的数据
word_list = get_word_list(text)
# 获取关键词关系
word_dict = to_word_dict(word_list, text)
# 替换字符串
text = replace_word(word_dict, text)
extract_tags = analyse.extract_tags(text, topK=5, withWeight=True, allowPOS=('ns', 'n', 'vn', 'v', 'eng'))
result = " ".join([get_key_by_word_dict(word, word_dict) for word, score in extract_tags if
not remove_chars.__contains__(word)])
# 删除词库
for word in word_list:
jieba.del_word(word)
extract_tags = jieba.lcut(text, cut_all=True)
result = " ".join(extract_tags)
return result

View File

@ -82,6 +82,9 @@ class Type(models.TextChoices):
web = 1, 'web站点类型'
lark = 2, '飞书类型'
yuque = 3, '语雀类型'
class HitHandlingMethod(models.TextChoices):
optimization = 'optimization', '模型优化'

View File

@ -40,6 +40,14 @@ def zip_dir(zip_path, output=None):
zip.close()
def is_valid_uuid(s):
try:
uuid.UUID(s)
return True
except ValueError:
return False
def write_image(zip_path: str, image_list: List[str]):
for image in image_list:
search = re.search("\(.*\)", image)
@ -47,6 +55,9 @@ def write_image(zip_path: str, image_list: List[str]):
text = search.group()
if text.startswith('(/api/file/'):
r = text.replace('(/api/file/', '').replace(')', '')
r = r.strip().split(" ")[0]
if not is_valid_uuid(r):
break
file = QuerySet(File).filter(id=r).first()
if file is None:
break
@ -58,6 +69,9 @@ def write_image(zip_path: str, image_list: List[str]):
f.write(file.get_byte())
else:
r = text.replace('(/api/image/', '').replace(')', '')
r = r.strip().split(" ")[0]
if not is_valid_uuid(r):
break
image_model = QuerySet(Image).filter(id=r).first()
if image_model is None:
break
@ -208,3 +222,26 @@ def get_embedding_model_id_by_dataset_id_list(dataset_id_list: List):
if len(dataset_list) == 0:
raise Exception(_('Knowledge base setting error, please reset the knowledge base'))
return str(dataset_list[0].embedding_mode_id)
class GenerateRelatedSerializer(ApiMixin, serializers.Serializer):
model_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_('Model id')))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.uuid(_('Prompt word')))
state_list = serializers.ListField(required=False, child=serializers.CharField(required=True),
error_messages=ErrMessage.list("state list"))
@staticmethod
def get_request_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'model_id': openapi.Schema(type=openapi.TYPE_STRING,
title=_('Model id'),
description=_('Model id')),
'prompt': openapi.Schema(type=openapi.TYPE_STRING, title=_('Prompt word'),
description=_("Prompt word")),
'state_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
title=_('state list'))
}
)

View File

@ -23,6 +23,7 @@ from django.contrib.postgres.fields import ArrayField
from django.core import validators
from django.db import transaction, models
from django.db.models import QuerySet
from django.db.models.functions import Reverse, Substr
from django.http import HttpResponse
from drf_yasg import openapi
from rest_framework import serializers
@ -42,9 +43,10 @@ from common.util.split_model import get_split_model
from dataset.models.data_set import DataSet, Document, Paragraph, Problem, Type, ProblemParagraphMapping, TaskType, \
State, File, Image
from dataset.serializers.common_serializers import list_paragraph, MetaSerializer, ProblemParagraphManage, \
get_embedding_model_by_dataset_id, get_embedding_model_id_by_dataset_id, write_image, zip_dir
get_embedding_model_by_dataset_id, get_embedding_model_id_by_dataset_id, write_image, zip_dir, \
GenerateRelatedSerializer
from dataset.serializers.document_serializers import DocumentSerializers, DocumentInstanceSerializer
from dataset.task import sync_web_dataset, sync_replace_web_dataset
from dataset.task import sync_web_dataset, sync_replace_web_dataset, generate_related_by_dataset_id
from embedding.models import SearchMode
from embedding.task import embedding_by_dataset, delete_embedding_by_dataset
from setting.models import AuthOperate, Model
@ -453,11 +455,13 @@ class DataSetSerializers(serializers.ModelSerializer):
# 批量插入关联问题
QuerySet(ProblemParagraphMapping).bulk_create(problem_paragraph_mapping_list) if len(
problem_paragraph_mapping_list) > 0 else None
# 响应数据
return {**DataSetSerializers(dataset).data,
'document_list': DocumentSerializers.Query(data={'dataset_id': dataset_id}).list(
with_valid=True)}, dataset_id
'user_id': user_id,
'document_list': document_model_list,
"document_count": len(document_model_list),
"char_length": reduce(lambda x, y: x + y, [d.char_length for d in document_model_list],
0)}, dataset_id
@staticmethod
def get_last_url_path(url):
@ -567,13 +571,13 @@ class DataSetSerializers(serializers.ModelSerializer):
id = serializers.CharField(required=True, error_messages=ErrMessage.char("id"))
user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.char(_('user id')))
query_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_('query text')))
top_number = serializers.IntegerField(required=True, max_value=100, min_value=1,
top_number = serializers.IntegerField(required=True, max_value=10000, min_value=1,
error_messages=ErrMessage.char("top number"))
similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
error_messages=ErrMessage.char(_('similarity')))
search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_('The type only supports register|reset_password'), code=500)
message=_('The type only supports embedding|keywords|blend'), code=500)
], error_messages=ErrMessage.char(_('search mode')))
def is_valid(self, *, raise_exception=True):
@ -814,6 +818,31 @@ class DataSetSerializers(serializers.ModelSerializer):
except AlreadyQueued as e:
raise AppApiException(500, _('Failed to send the vectorization task, please try again later!'))
def generate_related(self, instance: Dict, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
GenerateRelatedSerializer(data=instance).is_valid(raise_exception=True)
dataset_id = self.data.get('id')
model_id = instance.get("model_id")
prompt = instance.get("prompt")
state_list = instance.get('state_list')
ListenerManagement.update_status(QuerySet(Document).filter(dataset_id=dataset_id),
TaskType.GENERATE_PROBLEM,
State.PENDING)
ListenerManagement.update_status(QuerySet(Paragraph).annotate(
reversed_status=Reverse('status'),
task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value,
1),
).filter(task_type_status__in=state_list, dataset_id=dataset_id)
.values('id'),
TaskType.GENERATE_PROBLEM,
State.PENDING)
ListenerManagement.get_aggregation_document_status_by_dataset_id(dataset_id)()
try:
generate_related_by_dataset_id.delay(dataset_id, model_id, prompt, state_list)
except AlreadyQueued as e:
raise AppApiException(500, _('Failed to send the vectorization task, please try again later!'))
def list_application(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)

View File

@ -19,16 +19,18 @@ from typing import List, Dict
import openpyxl
from celery_once import AlreadyQueued
from django.core import validators
from django.db import transaction
from django.db import transaction, models
from django.db.models import QuerySet, Count
from django.db.models.functions import Substr, Reverse
from django.http import HttpResponse
from django.utils.translation import get_language
from django.utils.translation import gettext_lazy as _, gettext, to_locale
from drf_yasg import openapi
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
from rest_framework import serializers
from xlwt import Utils
from common.db.search import native_search, native_page_search
from common.db.search import native_search, native_page_search, get_dynamics_model
from common.event import ListenerManagement
from common.event.common import work_thread_pool
from common.exception.app_exception import AppApiException
@ -64,8 +66,6 @@ from embedding.task.embedding import embedding_by_document, delete_embedding_by_
embedding_by_document_list
from setting.models import Model
from smartdoc.conf import PROJECT_DIR
from django.utils.translation import gettext_lazy as _, gettext, to_locale
from django.utils.translation import get_language
parse_qa_handle_list = [XlsParseQAHandle(), CsvParseQAHandle(), XlsxParseQAHandle(), ZipParseQAHandle()]
parse_table_handle_list = [CsvSplitTableHandle(), XlsSplitTableHandle(), XlsxSplitTableHandle()]
@ -243,13 +243,16 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
self.is_valid(raise_exception=True)
language = get_language()
if self.data.get('type') == 'csv':
file = open(os.path.join(PROJECT_DIR, "apps", "dataset", 'template', f'csv_template_{to_locale(language)}.csv'), "rb")
file = open(
os.path.join(PROJECT_DIR, "apps", "dataset", 'template', f'csv_template_{to_locale(language)}.csv'),
"rb")
content = file.read()
file.close()
return HttpResponse(content, status=200, headers={'Content-Type': 'text/cxv',
return HttpResponse(content, status=200, headers={'Content-Type': 'text/csv',
'Content-Disposition': 'attachment; filename="csv_template.csv"'})
elif self.data.get('type') == 'excel':
file = open(os.path.join(PROJECT_DIR, "apps", "dataset", 'template', f'excel_template_{to_locale(language)}.xlsx'), "rb")
file = open(os.path.join(PROJECT_DIR, "apps", "dataset", 'template',
f'excel_template_{to_locale(language)}.xlsx'), "rb")
content = file.read()
file.close()
return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel',
@ -261,7 +264,8 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
language = get_language()
if self.data.get('type') == 'csv':
file = open(
os.path.join(PROJECT_DIR, "apps", "dataset", 'template', f'table_template_{to_locale(language)}.csv'),
os.path.join(PROJECT_DIR, "apps", "dataset", 'template',
f'table_template_{to_locale(language)}.csv'),
"rb")
content = file.read()
file.close()
@ -411,6 +415,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('document is active')))
task_type = serializers.IntegerField(required=False, error_messages=ErrMessage.integer(_('task type')))
status = serializers.CharField(required=False, error_messages=ErrMessage.char(_('status')))
order_by = serializers.CharField(required=False, error_messages=ErrMessage.char(_('order by')))
def get_query_set(self):
query_set = QuerySet(model=Document)
@ -437,8 +442,18 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
query_set = query_set.filter(status__icontains=status)
else:
query_set = query_set.filter(status__iregex='^[2n]*$')
query_set = query_set.order_by('-create_time', 'id')
return query_set
order_by = self.data.get('order_by', '')
order_by_query_set = QuerySet(model=get_dynamics_model(
{'char_length': models.CharField(), 'paragraph_count': models.IntegerField(),
"update_time": models.IntegerField(), 'create_time': models.DateTimeField()}))
if order_by:
order_by_query_set = order_by_query_set.order_by(order_by)
else:
order_by_query_set = order_by_query_set.order_by('-create_time', 'id')
return {
'document_custom_sql': query_set,
'order_by_query': order_by_query_set
}
def list(self, with_valid=False):
if with_valid:
@ -646,6 +661,8 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
cell = worksheet.cell(row=row_idx + 1, column=col_idx + 1)
if isinstance(col, str):
col = re.sub(ILLEGAL_CHARACTERS_RE, '', col)
if col.startswith(('=', '+', '-', '@')):
col = '\ufeff' + col
cell.value = col
# 创建HttpResponse对象返回Excel文件
return workbook
@ -688,6 +705,8 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
@staticmethod
def reset_document_name(document_name):
if document_name is not None:
document_name = document_name.strip()[0:29]
if document_name is None or not Utils.valid_sheet_name(document_name):
return "Sheet"
return document_name.strip()
@ -697,7 +716,10 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
self.is_valid(raise_exception=True)
query_set = QuerySet(model=Document)
query_set = query_set.filter(**{'id': self.data.get("document_id")})
return native_search(query_set, select_string=get_file_content(
return native_search({
'document_custom_sql': query_set,
'order_by_query': QuerySet(Document).order_by('-create_time', 'id')
}, select_string=get_file_content(
os.path.join(PROJECT_DIR, "apps", "dataset", 'sql', 'list_document.sql')), with_search_one=True)
def edit(self, instance: Dict, with_valid=False):
@ -1083,9 +1105,12 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
if len(document_model_list) == 0:
return [], dataset_id
query_set = query_set.filter(**{'id__in': [d.id for d in document_model_list]})
return native_search(query_set, select_string=get_file_content(
return native_search({
'document_custom_sql': query_set,
'order_by_query': QuerySet(Document).order_by('-create_time', 'id')
}, select_string=get_file_content(
os.path.join(PROJECT_DIR, "apps", "dataset", 'sql', 'list_document.sql')),
with_search_one=False), dataset_id
with_search_one=False), dataset_id
@staticmethod
def _batch_sync(document_id_list: List[str]):
@ -1175,7 +1200,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
if not QuerySet(Document).filter(id=document_id).exists():
raise AppApiException(500, _('document id not exist'))
def generate_related(self, model_id, prompt, with_valid=True):
def generate_related(self, model_id, prompt, state_list=None, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
document_id = self.data.get('document_id')
@ -1187,7 +1212,7 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
State.PENDING)
ListenerManagement.get_aggregation_document_status(document_id)()
try:
generate_related_by_document_id.delay(document_id, model_id, prompt)
generate_related_by_document_id.delay(document_id, model_id, prompt, state_list)
except AlreadyQueued as e:
raise AppApiException(500, _('The task is being executed, please do not send it again.'))
@ -1200,17 +1225,23 @@ class DocumentSerializers(ApiMixin, serializers.Serializer):
document_id_list = instance.get("document_id_list")
model_id = instance.get("model_id")
prompt = instance.get("prompt")
state_list = instance.get('state_list')
ListenerManagement.update_status(QuerySet(Document).filter(id__in=document_id_list),
TaskType.GENERATE_PROBLEM,
State.PENDING)
ListenerManagement.update_status(QuerySet(Paragraph).filter(document_id__in=document_id_list),
ListenerManagement.update_status(QuerySet(Paragraph).annotate(
reversed_status=Reverse('status'),
task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value,
1),
).filter(task_type_status__in=state_list, document_id__in=document_id_list)
.values('id'),
TaskType.GENERATE_PROBLEM,
State.PENDING)
ListenerManagement.get_aggregation_document_status_by_query_set(
QuerySet(Document).filter(id__in=document_id_list))()
try:
for document_id in document_id_list:
generate_related_by_document_id.delay(document_id, model_id, prompt)
generate_related_by_document_id.delay(document_id, model_id, prompt, state_list)
except AlreadyQueued as e:
pass
@ -1236,6 +1267,7 @@ def save_image(image_list):
exist_image_list = [str(i.get('id')) for i in
QuerySet(Image).filter(id__in=[i.id for i in image_list]).values('id')]
save_image_list = [image for image in image_list if not exist_image_list.__contains__(str(image.id))]
save_image_list = list({img.id: img for img in save_image_list}.values())
if len(save_image_list) > 0:
QuerySet(Image).bulk_create(save_image_list)

View File

@ -28,6 +28,9 @@ mime_types = {"html": "text/html", "htm": "text/html", "shtml": "text/html", "cs
"woff2": "font/woff2", "jar": "application/java-archive", "war": "application/java-archive",
"ear": "application/java-archive", "json": "application/json", "hqx": "application/mac-binhex40",
"doc": "application/msword", "pdf": "application/pdf", "ps": "application/postscript",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"eps": "application/postscript", "ai": "application/postscript", "rtf": "application/rtf",
"m3u8": "application/vnd.apple.mpegurl", "kml": "application/vnd.google-earth.kml+xml",
"kmz": "application/vnd.google-earth.kmz", "xls": "application/vnd.ms-excel",
@ -57,12 +60,14 @@ mime_types = {"html": "text/html", "htm": "text/html", "shtml": "text/html", "cs
class FileSerializer(serializers.Serializer):
file = UploadedFileField(required=True, error_messages=ErrMessage.image(_('file')))
meta = serializers.JSONField(required=False)
meta = serializers.JSONField(required=False, allow_null=True)
def upload(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
meta = self.data.get('meta')
meta = self.data.get('meta', None)
if not meta:
meta = {'debug': True}
file_id = meta.get('file_id', uuid.uuid1())
file = File(id=file_id, file_name=self.data.get('file').name, meta=meta)
file.save(self.data.get('file').read())
@ -85,4 +90,4 @@ class FileSerializer(serializers.Serializer):
'Content-Disposition': 'attachment; filename="{}"'.format(
file.file_name)})
return HttpResponse(file.get_byte(), status=200,
headers={'Content-Type': mime_types.get(file.file_name.split(".")[-1], 'text/plain')})
headers={'Content-Type': mime_types.get(file_type, 'text/plain')})

View File

@ -663,6 +663,7 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
**{'title__icontains': self.data.get('title')})
if 'content' in self.data:
query_set = query_set.filter(**{'content__icontains': self.data.get('content')})
query_set.order_by('-create_time', 'id')
return query_set
def list(self):

View File

@ -1,3 +1,4 @@
SELECT * from (
SELECT
"document".* ,
to_json("document"."meta") as meta,
@ -5,3 +6,6 @@ SELECT
(SELECT "count"("id") FROM "paragraph" WHERE document_id="document"."id") as "paragraph_count"
FROM
"document" "document"
${document_custom_sql}
) temp
${order_by_query}

View File

@ -2,6 +2,7 @@ UPDATE "document"
SET "char_length" = ( SELECT CASE WHEN
"sum" ( "char_length" ( "content" ) ) IS NULL THEN
0 ELSE "sum" ( "char_length" ( "content" ) )
END FROM paragraph WHERE "document_id" = %s )
END FROM paragraph WHERE "document_id" = %s ),
"update_time" = CURRENT_TIMESTAMP
WHERE
"id" = %s

View File

@ -3,11 +3,12 @@ import traceback
from celery_once import QueueOnce
from django.db.models import QuerySet
from django.db.models.functions import Reverse, Substr
from langchain_core.messages import HumanMessage
from common.config.embedding_config import ModelManage
from common.event import ListenerManagement
from common.util.page_utils import page
from common.util.page_utils import page, page_desc
from dataset.models import Paragraph, Document, Status, TaskType, State
from dataset.task.tools import save_problem
from ops import celery_app
@ -28,7 +29,8 @@ def generate_problem_by_paragraph(paragraph, llm_model, prompt):
try:
ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph.id), TaskType.GENERATE_PROBLEM,
State.STARTED)
res = llm_model.invoke([HumanMessage(content=prompt.replace('{data}', paragraph.content))])
res = llm_model.invoke(
[HumanMessage(content=prompt.replace('{data}', paragraph.content).replace('{title}', paragraph.title))])
if (res.content is None) or (len(res.content) == 0):
return
problems = res.content.split('\n')
@ -62,9 +64,24 @@ def get_is_the_task_interrupted(document_id):
return is_the_task_interrupted
@celery_app.task(base=QueueOnce, once={'keys': ['dataset_id']},
name='celery:generate_related_by_dataset')
def generate_related_by_dataset_id(dataset_id, model_id, prompt, state_list=None):
document_list = QuerySet(Document).filter(dataset_id=dataset_id)
for document in document_list:
try:
generate_related_by_document_id.delay(document.id, model_id, prompt, state_list)
except Exception as e:
pass
@celery_app.task(base=QueueOnce, once={'keys': ['document_id']},
name='celery:generate_related_by_document')
def generate_related_by_document_id(document_id, model_id, prompt):
def generate_related_by_document_id(document_id, model_id, prompt, state_list=None):
if state_list is None:
state_list = [State.PENDING.value, State.STARTED.value, State.SUCCESS.value, State.FAILURE.value,
State.REVOKE.value,
State.REVOKED.value, State.IGNORED.value]
try:
is_the_task_interrupted = get_is_the_task_interrupted(document_id)
if is_the_task_interrupted():
@ -78,7 +95,12 @@ def generate_related_by_document_id(document_id, model_id, prompt):
generate_problem = get_generate_problem(llm_model, prompt,
ListenerManagement.get_aggregation_document_status(
document_id), is_the_task_interrupted)
page(QuerySet(Paragraph).filter(document_id=document_id), 10, generate_problem, is_the_task_interrupted)
query_set = QuerySet(Paragraph).annotate(
reversed_status=Reverse('status'),
task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value,
1),
).filter(task_type_status__in=state_list, document_id=document_id)
page_desc(query_set, 10, generate_problem, is_the_task_interrupted)
except Exception as e:
max_kb_error.error(f'根据文档生成问题:{document_id}出现错误{str(e)}{traceback.format_exc()}')
max_kb_error.error(_('Generate issue based on document: {document_id} error {error}{traceback}').format(

View File

@ -54,5 +54,8 @@ def sync_replace_web_dataset(dataset_id: str, url: str, selector: str):
def sync_web_document(dataset_id, source_url_list: List[str], selector: str):
handler = get_sync_web_document_handler(dataset_id)
for source_url in source_url_list:
result = Fork(base_fork_url=source_url, selector_list=selector.split(' ')).fork()
handler(source_url, selector, result)
try:
result = Fork(base_fork_url=source_url, selector_list=selector.split(' ')).fork()
handler(source_url, selector, result)
except Exception as e:
pass

View File

@ -1,8 +1,5 @@
Section title (optional), Section content (required, question answer, no more than 4096 characters)), question (optional, one per line in the cell)
MaxKB product introduction, "MaxKB is a knowledge base question and answer system based on the LLM large language model. MaxKB = Max Knowledge Base aims to become the most powerful brain of the enterprise.
Out-of-the-box: Supports direct uploading of documents, automatic crawling of online documents, automatic text splitting and vectorization, and a good intelligent Q&A interactive experience;
Seamless embedding: Supports rapid embedding into third-party business systems with zero coding;
Multi-model support: Supports docking with mainstream large models, including Ollama local private large models (such as Llama 2, Llama 3, qwen), Tongyi Qianwen, OpenAI, Azure OpenAI, Kimi, Zhipu AI, iFlytek Spark and Baidu Qianfan Large models etc. ","What is MaxKB?
Section title (optional), Section content (requiredquestion answer), Question (optionalone per line in the cell)
MaxKB product introduction,"MaxKB is a knowledge base question-answering system based on the LLM large language model. MaxKB = Max Knowledge Baseaims to become the most powerful brain of the enterprise。Out-of-the-box: supports direct document upload、automatic crawling of online documents、automatic text splitting and vectorization、and good intelligent question-answering interactive experienceSeamless embedding: supports zero-coding and rapid embedding into third-party business systemsMulti-model support: supports docking with mainstream large modelsincluding Ollama local private large models (such as Llama 2、Llama 3、qwen)、Tongyi Qianwen、OpenAI、Azure OpenAI、Kimi、Zhipu AI、iFlytek Spark and Baidu Qianfan large models、etc.","What is MaxKB?
MaxKB product introduction
Large language model supported by MaxKB
MaxKB advantages"
Can't render this file because it has a wrong number of fields in line 2.

View File

@ -1,4 +1,4 @@
分段标题(选填),分段内容(必填,问题答案最长不超过4096个字符,问题(选填,单元格内一行一个)
分段标题(选填),分段内容(必填,问题答案)),问题(选填,单元格内一行一个)
MaxKB产品介绍,"MaxKB 是一款基于 LLM 大语言模型的知识库问答系统。MaxKB = Max Knowledge Base旨在成为企业的最强大脑。
开箱即用:支持直接上传文档、自动爬取在线文档,支持文本自动拆分、向量化,智能问答交互体验好;
无缝嵌入:支持零编码快速嵌入到第三方业务系统;

1 分段标题(选填) 分段内容(必填,问题答案,最长不超过4096个字符)) 分段内容(必填,问题答案)) 问题(选填,单元格内一行一个)
2 MaxKB产品介绍 MaxKB 是一款基于 LLM 大语言模型的知识库问答系统。MaxKB = Max Knowledge Base,旨在成为企业的最强大脑。 开箱即用:支持直接上传文档、自动爬取在线文档,支持文本自动拆分、向量化,智能问答交互体验好; 无缝嵌入:支持零编码快速嵌入到第三方业务系统; 多模型支持:支持对接主流的大模型,包括 Ollama 本地私有大模型(如 Llama 2、Llama 3、qwen)、通义千问、OpenAI、Azure OpenAI、Kimi、智谱 AI、讯飞星火和百度千帆大模型等。 MaxKB 是一款基于 LLM 大语言模型的知识库问答系统。MaxKB = Max Knowledge Base,旨在成为企业的最强大脑。 开箱即用:支持直接上传文档、自动爬取在线文档,支持文本自动拆分、向量化,智能问答交互体验好; 无缝嵌入:支持零编码快速嵌入到第三方业务系统; 多模型支持:支持对接主流的大模型,包括 Ollama 本地私有大模型(如 Llama 2、Llama 3、qwen)、通义千问、OpenAI、Azure OpenAI、Kimi、智谱 AI、讯飞星火和百度千帆大模型等。 MaxKB是什么? MaxKB产品介绍 MaxKB支持的大语言模型 MaxKB优势
3
4

View File

@ -1,4 +1,4 @@
分段標題(選填),分段內容(必填,問題答案最長不超過4096個字元,問題(選填,單元格內一行一個)
分段標題(選填),分段內容(必填,問題答案)),問題(選填,單元格內一行一個)
MaxKB產品介紹,"MaxKB 是一款基於 LLM 大語言模型的知識庫問答系統。MaxKB = Max Knowledge Base旨在成為企業的最強大大腦。
開箱即用:支援直接上傳文檔、自動爬取線上文檔,支援文字自動分割、向量化,智慧問答互動體驗好;
無縫嵌入:支援零編碼快速嵌入到第三方業務系統;

1 分段標題(選填) 分段內容(必填,問題答案,最長不超過4096個字元)) 分段內容(必填,問題答案)) 問題(選填,單元格內一行一個)
2 MaxKB產品介紹 MaxKB 是一款基於 LLM 大語言模型的知識庫問答系統。MaxKB = Max Knowledge Base,旨在成為企業的最強大大腦。 開箱即用:支援直接上傳文檔、自動爬取線上文檔,支援文字自動分割、向量化,智慧問答互動體驗好; 無縫嵌入:支援零編碼快速嵌入到第三方業務系統; 多模型支援:支持對接主流的大模型,包括Ollama 本地私有大模型(如Llama 2、Llama 3、qwen)、通義千問、OpenAI、Azure OpenAI、Kimi、智譜AI、訊飛星火和百度千帆大模型等。 MaxKB 是一款基於 LLM 大語言模型的知識庫問答系統。MaxKB = Max Knowledge Base,旨在成為企業的最強大大腦。 開箱即用:支援直接上傳文檔、自動爬取線上文檔,支援文字自動分割、向量化,智慧問答互動體驗好; 無縫嵌入:支援零編碼快速嵌入到第三方業務系統; 多模型支援:支持對接主流的大模型,包括Ollama 本地私有大模型(如Llama 2、Llama 3、qwen)、通義千問、OpenAI、Azure OpenAI、Kimi、智譜AI、訊飛星火和百度千帆大模型等。 MaxKB是什麼? MaxKB產品介紹 MaxKB支援的大語言模型 MaxKB優勢
3
4

View File

@ -11,6 +11,8 @@ urlpatterns = [
path('dataset/<str:dataset_id>/export', views.Dataset.Export.as_view(), name="export"),
path('dataset/<str:dataset_id>/export_zip', views.Dataset.ExportZip.as_view(), name="export_zip"),
path('dataset/<str:dataset_id>/re_embedding', views.Dataset.Embedding.as_view(), name="dataset_key"),
path('dataset/<str:dataset_id>/generate_related', views.Dataset.GenerateRelated.as_view(),
name="dataset_generate_related"),
path('dataset/<str:dataset_id>/application', views.Dataset.Application.as_view()),
path('dataset/<int:current_page>/<int:page_size>', views.Dataset.Page.as_view(), name="dataset"),
path('dataset/<str:dataset_id>/sync_web', views.Dataset.SyncWeb.as_view()),

View File

@ -0,0 +1,56 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file common.py.py
@date2025/3/25 15:43
@desc:
"""
from django.db.models import QuerySet
from dataset.models import DataSet, Document
def get_dataset_operation_object(dataset_id: str):
dataset_model = QuerySet(model=DataSet).filter(id=dataset_id).first()
if dataset_model is not None:
return {
"name": dataset_model.name,
"desc": dataset_model.desc,
"type": dataset_model.type,
"create_time": dataset_model.create_time,
"update_time": dataset_model.update_time
}
return {}
def get_document_operation_object(document_id: str):
document_model = QuerySet(model=Document).filter(id=document_id).first()
if document_model is not None:
return {
"name": document_model.name,
"type": document_model.type,
}
return {}
def get_document_operation_object_batch(document_id_list: str):
document_model_list = QuerySet(model=Document).filter(id__in=document_id_list)
if document_model_list is not None:
return {
"name": f'[{",".join([document_model.name for document_model in document_model_list])}]',
'document_list': [{'name': document_model.name, 'type': document_model.type} for document_model in
document_model_list]
}
return {}
def get_dataset_document_operation_object(dataset_dict: dict, document_dict: dict):
return {
'name': f'{dataset_dict.get("name", "")}/{document_dict.get("name", "")}',
'dataset_name': dataset_dict.get("name", ""),
'dataset_desc': dataset_dict.get("desc", ""),
'dataset_type': dataset_dict.get("type", ""),
'document_name': document_dict.get("name", ""),
'document_type': document_dict.get("type", ""),
}

View File

@ -13,13 +13,17 @@ from rest_framework.parsers import MultiPartParser
from rest_framework.views import APIView
from rest_framework.views import Request
import dataset.models
from common.auth import TokenAuth, has_permissions
from common.constants.permission_constants import PermissionConstants, CompareConstants, Permission, Group, Operate, \
ViewPermission, RoleConstants
from common.log.log import log
from common.response import result
from common.response.result import get_page_request_params, get_page_api_response, get_api_response
from common.swagger_api.common_api import CommonApi
from dataset.serializers.common_serializers import GenerateRelatedSerializer
from dataset.serializers.dataset_serializers import DataSetSerializers
from dataset.views.common import get_dataset_operation_object
from setting.serializers.provider_serializers import ModelSerializer
from django.utils.translation import gettext_lazy as _
@ -31,8 +35,8 @@ class Dataset(APIView):
authentication_classes = [TokenAuth]
@action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary="同步Web站点知识库",
operation_id="同步Web站点知识库",
@swagger_auto_schema(operation_summary=_("Synchronize the knowledge base of the website"),
operation_id=_("Synchronize the knowledge base of the website"),
manual_parameters=DataSetSerializers.SyncWeb.get_request_params_api(),
responses=result.get_default_response(),
tags=[_('Knowledge Base')])
@ -42,6 +46,8 @@ class Dataset(APIView):
dynamic_tag=keywords.get('dataset_id'))],
compare=CompareConstants.AND), PermissionConstants.DATASET_EDIT,
compare=CompareConstants.AND)
@log(menu='Knowledge Base', operate="Synchronize the knowledge base of the website",
get_operation_object=lambda r, keywords: get_dataset_operation_object(keywords.get('dataset_id')))
def put(self, request: Request, dataset_id: str):
return result.success(DataSetSerializers.SyncWeb(
data={'sync_type': request.query_params.get('sync_type'), 'id': dataset_id,
@ -52,14 +58,17 @@ class Dataset(APIView):
parser_classes = [MultiPartParser]
@action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary="创建QA知识库",
operation_id="创建QA知识库",
@swagger_auto_schema(operation_summary=_("Create QA knowledge base"),
operation_id=_("Create QA knowledge base"),
manual_parameters=DataSetSerializers.Create.CreateQASerializers.get_request_params_api(),
responses=get_api_response(
DataSetSerializers.Create.CreateQASerializers.get_response_body_api()),
tags=[_('Knowledge Base')]
)
@has_permissions(PermissionConstants.DATASET_CREATE, compare=CompareConstants.AND)
@log(menu='Knowledge Base', operate="Create QA knowledge base",
get_operation_object=lambda r, keywords: {'name': r.data.get('name'), 'desc': r.data.get('desc'),
'file_list': r.FILES.getlist('file')})
def post(self, request: Request):
return result.success(DataSetSerializers.Create(data={'user_id': request.user.id}).save_qa({
'file_list': request.FILES.getlist('file'),
@ -79,6 +88,13 @@ class Dataset(APIView):
tags=[_('Knowledge Base')]
)
@has_permissions(PermissionConstants.DATASET_CREATE, compare=CompareConstants.AND)
@log(menu='Knowledge Base', operate="Create a web site knowledge base",
get_operation_object=lambda r, keywords: {'name': r.data.get('name'), 'desc': r.data.get('desc'),
'file_list': r.FILES.getlist('file'),
'meta': {'source_url': r.data.get('source_url'),
'selector': r.data.get('selector'),
'embedding_mode_id': r.data.get('embedding_mode_id')}}
)
def post(self, request: Request):
return result.success(DataSetSerializers.Create(data={'user_id': request.user.id}).save_web(request.data))
@ -117,6 +133,8 @@ class Dataset(APIView):
tags=[_('Knowledge Base')]
)
@has_permissions(PermissionConstants.DATASET_CREATE, compare=CompareConstants.AND)
@log(menu='Knowledge Base', operate="Create a knowledge base",
get_operation_object=lambda r, keywords: {'name': r.data.get('name'), 'desc': r.data.get('desc')})
def post(self, request: Request):
return result.success(DataSetSerializers.Create(data={'user_id': request.user.id}).save(request.data))
@ -150,10 +168,30 @@ class Dataset(APIView):
)
@has_permissions(lambda r, keywords: Permission(group=Group.DATASET, operate=Operate.MANAGE,
dynamic_tag=keywords.get('dataset_id')))
@log(menu='Knowledge Base', operate="Re-vectorize",
get_operation_object=lambda r, keywords: get_dataset_operation_object(keywords.get('dataset_id')))
def put(self, request: Request, dataset_id: str):
return result.success(
DataSetSerializers.Operate(data={'id': dataset_id, 'user_id': request.user.id}).re_embedding())
class GenerateRelated(APIView):
authentication_classes = [TokenAuth]
@action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_('Generate related'), operation_id=_('Generate related'),
manual_parameters=DataSetSerializers.Operate.get_request_params_api(),
request_body=GenerateRelatedSerializer.get_request_body_api(),
responses=result.get_default_response(),
tags=[_('Knowledge Base')]
)
@log(menu='document', operate="Generate related documents",
get_operation_object=lambda r, keywords: get_dataset_operation_object(keywords.get('dataset_id'))
)
def put(self, request: Request, dataset_id: str):
return result.success(
DataSetSerializers.Operate(data={'id': dataset_id, 'user_id': request.user.id}).generate_related(
request.data))
class Export(APIView):
authentication_classes = [TokenAuth]
@ -164,6 +202,8 @@ class Dataset(APIView):
)
@has_permissions(lambda r, keywords: Permission(group=Group.DATASET, operate=Operate.MANAGE,
dynamic_tag=keywords.get('dataset_id')))
@log(menu='Knowledge Base', operate="Export knowledge base",
get_operation_object=lambda r, keywords: get_dataset_operation_object(keywords.get('dataset_id')))
def get(self, request: Request, dataset_id: str):
return DataSetSerializers.Operate(data={'id': dataset_id, 'user_id': request.user.id}).export_excel()
@ -178,6 +218,8 @@ class Dataset(APIView):
)
@has_permissions(lambda r, keywords: Permission(group=Group.DATASET, operate=Operate.MANAGE,
dynamic_tag=keywords.get('dataset_id')))
@log(menu='Knowledge Base', operate="Export knowledge base containing images",
get_operation_object=lambda r, keywords: get_dataset_operation_object(keywords.get('dataset_id')))
def get(self, request: Request, dataset_id: str):
return DataSetSerializers.Operate(data={'id': dataset_id, 'user_id': request.user.id}).export_zip()
@ -193,6 +235,8 @@ class Dataset(APIView):
dynamic_tag=keywords.get('dataset_id')),
lambda r, k: Permission(group=Group.DATASET, operate=Operate.DELETE,
dynamic_tag=k.get('dataset_id')), compare=CompareConstants.AND)
@log(menu='Knowledge Base', operate="Delete knowledge base",
get_operation_object=lambda r, keywords: get_dataset_operation_object(keywords.get('dataset_id')))
def delete(self, request: Request, dataset_id: str):
operate = DataSetSerializers.Operate(data={'id': dataset_id})
return result.success(operate.delete())
@ -219,6 +263,8 @@ class Dataset(APIView):
)
@has_permissions(lambda r, keywords: Permission(group=Group.DATASET, operate=Operate.MANAGE,
dynamic_tag=keywords.get('dataset_id')))
@log(menu='Knowledge Base', operate="Modify knowledge base information",
get_operation_object=lambda r, keywords: get_dataset_operation_object(keywords.get('dataset_id')))
def put(self, request: Request, dataset_id: str):
return result.success(
DataSetSerializers.Operate(data={'id': dataset_id, 'user_id': request.user.id}).edit(request.data,

Some files were not shown because too many files have changed in this diff Show More