Compare commits

...

237 Commits

Author SHA1 Message Date
StyleZhang
84c62ea192 chat add tts 2024-01-24 16:35:44 +08:00
StyleZhang
decdc326b6 try to ask style 2024-01-24 16:10:50 +08:00
StyleZhang
a0d8d8fc43 refact chat 2024-01-24 15:22:31 +08:00
StyleZhang
eb9d9ebf14 Merge branch 'main' into feat/multiple-model-configuration 2024-01-24 13:23:18 +08:00
StyleZhang
b62aef71ff merge main 2024-01-24 12:05:15 +08:00
StyleZhang
77b7783259 Merge branch 'main' into feat/multiple-model-configuration 2024-01-23 20:07:53 +08:00
StyleZhang
0865130508 merge main 2024-01-23 19:51:45 +08:00
StyleZhang
6edb01a36b Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-23 19:28:52 +08:00
Joel
b4fccf0937 fix: support click unauth tool show 2024-01-23 19:22:53 +08:00
Joel
304a1fda4e fix: no response loading 2024-01-23 19:09:00 +08:00
Joel
e90f75278d chore: en generation api doc 2024-01-23 18:25:26 +08:00
Joel
8ce00b625a chore: generation api ch 2024-01-23 18:18:44 +08:00
Joel
b1161c633c feat: chat en api doc 2024-01-23 18:11:12 +08:00
StyleZhang
6d2c1007bd fix: opening suggested questions style 2024-01-23 17:59:16 +08:00
StyleZhang
df31598d57 Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-23 17:56:56 +08:00
Joel
0c24540d7b chore: open prombem very long 2024-01-23 17:43:36 +08:00
Joel
ba467c5013 chore: chat cn api doc 2024-01-23 17:39:53 +08:00
Joel
3c926a69e0 fix: import right btn 2024-01-23 17:01:39 +08:00
StyleZhang
6a3639bcbc Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-23 16:45:36 +08:00
zxhlyh
55645f1a89 fix: model-parameter-modal slider (#2135) 2024-01-23 16:19:14 +08:00
Joel
20a9eec48f fix: table has many vals ui 2024-01-23 16:05:19 +08:00
Joel
0e7f609b38 chore: add no tools description 2024-01-23 15:49:35 +08:00
Joel
eb9cd481ca chore: default fill app template name 2024-01-23 15:33:17 +08:00
Joel
e0fffb48b1 fix: refresh not set opener problems 2024-01-23 15:30:04 +08:00
Joel
802260c4fc feat: default jump to app config page 2024-01-23 15:27:32 +08:00
StyleZhang
8694e6800d fix: speech to text 2024-01-23 15:03:23 +08:00
Joel
ffd04d08bd chore: menu loc and name 2024-01-23 14:54:41 +08:00
Joel
8d2ee10e4e chore: function calling text 2024-01-23 14:50:23 +08:00
Joel
88c01728b3 chore: doc indent 2024-01-23 14:48:45 +08:00
Joel
6c81c48e17 feat: chat en api doc add assistant info 2024-01-23 14:47:09 +08:00
Joel
b6223e9a10 feat: completion en 2024-01-23 14:30:47 +08:00
Joel
a30b6d9912 just add 2024-01-23 14:17:13 +08:00
Joel
6fbce4334e feat: chat add agent content 2024-01-23 14:16:59 +08:00
Joel
384688e232 feat: completion api en 2024-01-23 13:56:32 +08:00
Joel
477699110f feat: chat en doc 2024-01-23 13:40:21 +08:00
StyleZhang
809e6b6b60 Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-23 13:29:25 +08:00
Joel
1324199412 chore: some conversation 2024-01-23 11:51:12 +08:00
StyleZhang
29a53104ba Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-23 11:05:29 +08:00
Joel
6e9a3ff26f chore: remove not support api 2024-01-23 10:34:59 +08:00
Joel
c9f32e0a6e feat: new temp chat api doc 2024-01-22 22:05:12 +08:00
Joel
3458549105 Merge branch 'main' into feat/agent-fe 2024-01-22 21:02:14 +08:00
StyleZhang
d6aac4d326 Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-22 20:56:11 +08:00
StyleZhang
7f847cbb6c fix: dataset config style 2024-01-22 20:54:57 +08:00
Joel
ac2ea635e9 chore: explore app list show agent tag 2024-01-22 20:52:21 +08:00
Joel
ff6cbed395 fix: question too log conver delete btn 2024-01-22 20:43:43 +08:00
StyleZhang
74388f7807 Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-22 20:36:55 +08:00
StyleZhang
cbc406785b opening variable 2024-01-22 20:36:25 +08:00
Joel
ef1e4b79d1 chore: agent mode not show datasetconfig 2024-01-22 20:25:27 +08:00
Joel
79306908b7 fix: add not type 2024-01-22 20:17:50 +08:00
Joel
ed8f24f3c7 chore: not auth tool show status 2024-01-22 20:02:27 +08:00
StyleZhang
626c97b1c5 Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-22 19:56:36 +08:00
StyleZhang
dccf105fc4 fix: layout 2024-01-22 19:56:02 +08:00
Joel
00c1104084 feat: suport auto send 2024-01-22 19:34:03 +08:00
Joel
5eca2fa246 fix: no collection show error 2024-01-22 19:09:11 +08:00
Joel
4a8c3c171c fix: react spelling 2024-01-22 19:04:20 +08:00
StyleZhang
aac4fd43d5 rename 2024-01-22 19:01:34 +08:00
StyleZhang
7d2a6cc007 opening suggested questions 2024-01-22 18:22:15 +08:00
Joel
e1bc912cf8 chore: no auth tooltip 2024-01-22 18:12:25 +08:00
Joel
14fc595c83 chore: auth tip 2024-01-22 18:01:22 +08:00
Joel
5182a6d175 chore: input loc 2024-01-22 17:59:14 +08:00
StyleZhang
368fa97f01 Merge remote-tracking branch 'origin/feat/agent-fe' into feat/multiple-model-configuration 2024-01-22 17:55:14 +08:00
StyleZhang
e1584a4f76 annotation 2024-01-22 17:54:51 +08:00
Joel
a2098e5627 chore: config setting label 2024-01-22 17:52:44 +08:00
Joel
06ea7f47e1 chore: text 2024-01-22 17:41:36 +08:00
Joel
e3ff484be4 chore: mode name 2024-01-22 17:40:11 +08:00
Joel
95fc6d7af4 feat: change preview and turnate 2024-01-22 17:29:17 +08:00
StyleZhang
8cab8103f0 model-parameter-modal 2024-01-22 17:04:10 +08:00
Joel
3df49bfa99 chore: colloection show full description 2024-01-22 16:57:37 +08:00
StyleZhang
f41f748fef model-parameter-modal 2024-01-22 16:56:35 +08:00
Joel
4ec82b5b45 feat: change cot to react 2024-01-22 16:51:08 +08:00
Joel
1efe100a41 feat: webapp support opening questions 2024-01-22 16:46:30 +08:00
Joel
ad0f15e4aa feat: debug page support more suggestions 2024-01-22 16:23:10 +08:00
Joel
04226ddae6 feat: opening texts 2024-01-22 13:22:59 +08:00
Joel
a38c90ec56 feat: opening remarks ui 2024-01-22 11:38:30 +08:00
StyleZhang
c3e413fec3 chat 2024-01-22 11:36:47 +08:00
StyleZhang
475016478d Merge branch 'feat/agent-fe' into feat/multiple-model-configuration 2024-01-22 10:27:03 +08:00
Joel
818c7cc462 temp: question opts 2024-01-21 16:21:21 +08:00
Joel
9f173bcbe9 feat: op btn to right top 2024-01-21 15:57:52 +08:00
Joel
f4c9e324cd fix: stop genenration not work 2024-01-20 23:44:31 +08:00
Joel
775284c3b0 fix: out put low and may out put twice message problem 2024-01-20 23:21:37 +08:00
Joel
cebf51bb72 Merge branch 'main' into feat/agent-fe 2024-01-20 22:16:04 +08:00
Joel
f790611968 fix: tool show name 2024-01-19 20:56:29 +08:00
Joel
0fd419f475 chore: message null 2024-01-19 20:34:03 +08:00
Joel
b5c12552dd fix: output twice 2024-01-19 20:22:31 +08:00
Joel
1131f47b04 fix: knowledge name not use id 2024-01-19 20:14:58 +08:00
Joel
4cc57b18cd fix: tiny ui 2024-01-19 18:50:52 +08:00
Joel
d03a9c3b1d fix: test btn wrap 2024-01-19 18:42:50 +08:00
Joel
fbadb3f4ba fix: handle old app 2024-01-19 18:18:44 +08:00
Joel
47f7b8f3c0 feat: handle app detail type name 2024-01-19 18:03:51 +08:00
Joel
a3922b3586 fix: agnetConfig may null 2024-01-19 14:59:52 +08:00
Joel
8fdcbe993a fix: agent detect show problem 2024-01-19 14:27:32 +08:00
Joel
ecbcd5d2bc fix: message not show prolem 2024-01-19 14:18:36 +08:00
Joel
f5b64794e1 feat: log add agent thought 2024-01-19 13:55:54 +08:00
Joel
a908acc623 fix: move draw code to debug app 2024-01-19 13:43:36 +08:00
Joel
8de934207c fix: img thought 2024-01-19 11:51:17 +08:00
Joel
bb31b885d7 Merge branch 'main' into feat/agent-fe 2024-01-19 11:14:57 +08:00
Joel
55ffb645c7 temp: webapp can use 2024-01-19 11:13:48 +08:00
Joel
e77f2a8be2 feat: thought merge 2024-01-18 21:08:10 +08:00
Joel
2763c674e6 temp: tools into output 2024-01-18 20:34:57 +08:00
Joel
f6851d5866 temp: res content 2024-01-18 18:21:57 +08:00
Joel
5a65e5c761 feat: add list sort 2024-01-18 18:21:57 +08:00
StyleZhang
50aec5e7df fix: external-data-tool icon 2024-01-18 18:15:59 +08:00
Joel
56e3dfba76 feat: history support mixed tool output 2024-01-18 18:06:03 +08:00
StyleZhang
f1bd8d820f external-data-tool in prompt variable 2024-01-18 15:56:33 +08:00
StyleZhang
bbe0b7640b fix: tool style in chat 2024-01-18 12:18:57 +08:00
StyleZhang
dd0c7a89a5 fix: answer style 2024-01-18 12:04:14 +08:00
Joel
a517fd921e fix: tool hide 2024-01-18 11:43:39 +08:00
Joel
202b8a8032 feat: some i18n 2024-01-18 11:35:25 +08:00
StyleZhang
d80019ceef fix: chat params 2024-01-18 11:31:40 +08:00
Joel
626fa03e10 chore: remove 2024-01-18 11:17:25 +08:00
Joel
f5d79388c4 feat: remove universe chat entrance 2024-01-18 11:13:46 +08:00
Joel
01d49952dd chore: tool des show 2024-01-18 10:40:48 +08:00
Joel
af0c1d0951 chore: remove tooltip 2024-01-18 10:32:21 +08:00
Joel
b33a4bc4cf feat: delete icons 2024-01-18 10:23:20 +08:00
StyleZhang
13346b3c31 merge feat/agent-fe 2024-01-18 10:08:46 +08:00
Joel
6f3ce95f25 fix: not show setting 2024-01-18 10:08:00 +08:00
Joel
9e8ac18b5f feat: mode switch and take disabled 2024-01-17 20:15:43 +08:00
Joel
e7921b95b8 feat: tool deleted 2024-01-17 19:57:48 +08:00
StyleZhang
9a74a29208 app list 2024-01-17 19:25:45 +08:00
Joel
1f20c76409 feat: built in prompt 2024-01-17 18:27:43 +08:00
Joel
e61bbfba5f fix: url path problem 2024-01-17 17:54:06 +08:00
Joel
99274617c7 feat: support import schema from url 2024-01-17 17:47:36 +08:00
Joel
c39f1319b6 fix: tool name not show all 2024-01-17 17:09:19 +08:00
Joel
eb0e7fcbdc fix: openai completion also use function call 2024-01-17 16:58:25 +08:00
Joel
cf31cb4615 fix: old dataset text generation show agent tools 2024-01-17 16:45:53 +08:00
Joel
cceeaa9da0 files 2024-01-17 16:38:46 +08:00
Joel
23ca724ab8 feat: support no tools 2024-01-17 16:37:18 +08:00
StyleZhang
6664d278c1 fix 2024-01-17 16:28:49 +08:00
StyleZhang
07787c20d2 Merge remote-tracking branch 'origin/feat/agent-fe' into feat/multiple-model-configuration 2024-01-17 15:50:53 +08:00
StyleZhang
6d43084995 fix: layout 2024-01-17 15:44:55 +08:00
Joel
81abd5c7e9 feat: in tools to auth and ui 2024-01-17 15:40:54 +08:00
StyleZhang
a1b0f9f0ae Merge remote-tracking branch 'origin/feat/agent-fe' into feat/multiple-model-configuration 2024-01-17 15:24:06 +08:00
Joel
35533c59a8 feat: tool info and setting 2024-01-17 15:17:05 +08:00
Joel
9d83a6fb9b feat: show tool info 2024-01-17 14:58:59 +08:00
StyleZhang
c3b37c633e fix: citation 2024-01-17 14:44:02 +08:00
StyleZhang
d1101f40ea chat 2024-01-17 11:41:25 +08:00
Joel
84cc86e370 feat: web app support tool icons 2024-01-17 11:32:16 +08:00
Joel
b8154a7094 feat: webapp support agent 2024-01-17 11:13:42 +08:00
StyleZhang
e5a785d7d5 merge feat/agent-fe 2024-01-17 10:52:07 +08:00
StyleZhang
8cf63f1719 merge feat/agent-fe 2024-01-17 10:36:26 +08:00
Joel
47d02069cd Merge branch 'main' into feat/agent-fe 2024-01-17 10:24:28 +08:00
Joel
d9d717fb5d fix: prompt text 2024-01-16 20:55:15 +08:00
Joel
7ec96da673 fix: add btn add tools icon 2024-01-16 20:52:25 +08:00
Joel
ea59d7d4c0 fix: default max iteration num 2024-01-16 20:26:57 +08:00
Joel
a1899be05c feat: img gallary 2024-01-16 19:54:50 +08:00
StyleZhang
5b3cf90828 adjust style 2024-01-16 19:47:55 +08:00
Joel
a2297946c4 fix: added btn 2024-01-16 19:40:59 +08:00
Joel
052b615816 fix: tools default enabled 2024-01-16 19:35:31 +08:00
Joel
2e6272a01c fix: old dataset data in context 2024-01-16 19:24:09 +08:00
Joel
56829025aa feat: support dalle 2024-01-16 18:27:52 +08:00
Joel
d514299b9d feat: support show thoughts 2024-01-16 17:59:29 +08:00
Joel
24e7d0df37 feat: new thought ui 2024-01-16 17:04:02 +08:00
StyleZhang
02b93fd870 i18n 2024-01-16 17:02:51 +08:00
StyleZhang
a994df9c18 adjust layout 2024-01-16 16:37:48 +08:00
Joel
ec4ff682b8 feat: debug page support agent 2024-01-16 15:12:04 +08:00
Joel
b601b2dadd chore: dataset to new struct 2024-01-16 14:02:32 +08:00
Joel
3ebe9d344f feat: support new dataset config 2024-01-16 12:01:37 +08:00
StyleZhang
896271851e add text-generation 2024-01-16 11:49:35 +08:00
Joel
6693d0cb39 fix: add tools btn 2024-01-16 11:46:51 +08:00
Joel
e803df92f8 feat: show default name 2024-01-16 11:31:35 +08:00
Joel
7385850f5f feat: form support and set default 2024-01-16 11:28:15 +08:00
StyleZhang
07dcc38e3b Merge branch 'main' into feat/multiple-model-configuration 2024-01-16 10:19:22 +08:00
Joel
272776bca1 feat: show tools config 2024-01-15 20:40:13 +08:00
StyleZhang
c883f15eb9 chat 2024-01-15 20:28:55 +08:00
Joel
d5d6cca478 Merge branch 'main' into feat/agent-fe 2024-01-15 20:28:16 +08:00
Joel
2fbce04315 temp: 2024-01-15 20:00:20 +08:00
Joel
e56fcb0420 fix: enabled num 2024-01-15 19:12:43 +08:00
Joel
d33987c64f feat: handle tools select 2024-01-15 18:22:34 +08:00
Joel
98a1dc5dac feat: tools show and remove 2024-01-15 18:02:26 +08:00
Joel
0ba18a551d feat: support rename tool name 2024-01-15 11:42:31 +08:00
Joel
31ee135351 fix: test api error 2024-01-15 11:34:25 +08:00
Joel
77e914bba5 feat: get set agent setting 2024-01-15 11:09:36 +08:00
Joel
f78ccc894f feat: react stargy setting 2024-01-15 10:22:54 +08:00
StyleZhang
1b809a1bd9 publish as multiple model 2024-01-15 10:14:16 +08:00
Joel
be90ff1185 feat: add isagent stragry 2024-01-14 13:42:00 +08:00
StyleZhang
9ab906009f publish as multiple model 2024-01-13 20:58:33 +08:00
StyleZhang
76aef96028 merge main 2024-01-13 18:22:57 +08:00
StyleZhang
103f9990d4 chat item 2024-01-13 18:20:33 +08:00
Joel
b115b140e1 feat: agent tool types 2024-01-12 20:21:08 +08:00
Joel
00d9899a14 fix: emoji problem 2024-01-12 19:42:44 +08:00
Joel
6f689bee8b feat: support collection type changed 2024-01-12 18:19:19 +08:00
Joel
f9bdad884e feat: remove collection 2024-01-12 18:05:50 +08:00
Joel
4cd84a1070 feat: model edit 2024-01-12 17:53:30 +08:00
Joel
2aab2c58f1 feat: switch tools 2024-01-12 17:14:08 +08:00
Joel
bc5e536eeb feat: test api 2024-01-12 16:49:10 +08:00
Joel
e067683cee feat: edit tools mock 2024-01-12 15:57:35 +08:00
Joel
8cdb0eec85 feat: fin tool creditial 2024-01-12 15:22:14 +08:00
Joel
833a5f2e01 feat: create tool 2024-01-12 14:42:03 +08:00
Joel
86290b6b23 feat: paser schema 2024-01-12 13:39:20 +08:00
Joel
08b4b18421 feat: add is need auth detect 2024-01-12 10:38:06 +08:00
StyleZhang
151e8c2150 multiple model 2024-01-12 10:22:52 +08:00
Joel
29553a9b48 feat: built in credit save and update 2024-01-11 20:10:34 +08:00
Joel
a63947d6ab feat: view tool params 2024-01-11 18:24:31 +08:00
Joel
af19af0601 feat: built in list and tools list 2024-01-11 16:48:10 +08:00
Joel
4e15baf1cb feat: handle key name 2024-01-11 15:59:53 +08:00
Joel
06740f2546 feat: test api modal 2024-01-11 15:54:35 +08:00
Joel
94ad44189c feat: set auth 2024-01-11 15:17:27 +08:00
Joel
cb0f98426d feat: auth input 2024-01-11 14:29:23 +08:00
Joel
51bad10aea feat: available tools 2024-01-11 13:52:28 +08:00
StyleZhang
9735e3b74f Merge branch 'main' into feat/multiple-model-configuration 2024-01-11 13:31:53 +08:00
Joel
18bb452bfc feat: schema textarea ui 2024-01-11 12:43:01 +08:00
Joel
bd5478ee2f feat: get schema 2024-01-11 11:38:19 +08:00
StyleZhang
7268da812c multiple model 2024-01-11 10:36:12 +08:00
Joel
0cabf90509 temp: 2024-01-10 21:15:32 +08:00
Joel
39c1dfab4d feat: handle build in auth logic 2024-01-10 20:04:46 +08:00
Joel
eeba590fe6 feat: trigger show auth 2024-01-10 18:45:45 +08:00
Joel
5a07577c0e feat: setup authorize 2024-01-10 18:40:23 +08:00
Joel
3cdf64f7af feat: view built in tool config 2024-01-10 17:59:03 +08:00
Joel
0f9ba639f9 feat: tool lists 2024-01-10 16:14:29 +08:00
Joel
ea08ce69a0 feat: middle 2024-01-10 15:14:00 +08:00
Joel
1d633cb51c chore: merge main 2024-01-10 14:55:07 +08:00
Joel
bee07c4dc3 feat: collection header 2024-01-10 14:43:04 +08:00
Joel
83a51ff247 feat: debug choose tool side bar 2024-01-10 11:03:20 +08:00
Joel
853c9cd4de feat: choose a toy popup 2024-01-09 20:41:17 +08:00
Joel
a02e09d486 feat: tool left 2024-01-09 20:02:18 +08:00
Joel
5d018d804f feat: tools comp struct 2024-01-09 16:23:54 +08:00
Joel
d023c8f980 feat: tool panels ui 2024-01-09 10:40:30 +08:00
Joel
6bc702cac5 feat: tools list select 2024-01-08 17:59:58 +08:00
Joel
1ae7d129cd feat: fin agent setting 2024-01-08 11:32:55 +08:00
Joel
9aa284ec13 feat: two prompt editor 2024-01-05 17:37:15 +08:00
Joel
d89c44de96 feat: ingrate advanced prompt input 2024-01-05 17:00:07 +08:00
Joel
54fd137267 feat: agent setting entrance 2024-01-05 15:43:37 +08:00
Joel
e39d5c5ba1 feat: choosen assistant type 2024-01-05 15:14:43 +08:00
Joel
c74515701c feat: choose assistant type picker struct 2024-01-05 11:33:55 +08:00
Joel
2a5490a2ed feat: btn struct 2024-01-05 11:13:02 +08:00
Joel
2ddebcf847 feat: expert mode ui 2024-01-05 10:48:03 +08:00
Joel
d604969ac1 feat: debug header 2024-01-04 18:16:55 +08:00
Joel
9177efdb3a feat: api based var 2024-01-04 17:17:46 +08:00
Joel
d7d81b6033 feat: add vars options 2024-01-04 16:51:03 +08:00
Joel
57001d44ef feat: create var type dropdown 2024-01-04 15:34:17 +08:00
Joel
5e37bd4d03 Merge branch 'main' into feat/agent-fe 2024-01-04 14:14:37 +08:00
Joel
1e974a6838 chore: missing files 2024-01-04 11:30:31 +08:00
Joel
bdd5315dfc feat: change create app ui 2024-01-04 11:29:14 +08:00
Joel
18ae6c3410 feat: create app modal layout 2024-01-03 18:29:20 +08:00
Joel
5052c0cf1d feat: app list page 2024-01-03 17:55:03 +08:00
Joel
d491865c09 feat: tools page holder 2024-01-03 15:25:21 +08:00
Joel
2bf38b2ea6 feat: header nav text and icon 2024-01-03 14:33:42 +08:00
56 changed files with 3209 additions and 188 deletions

View File

@@ -7,6 +7,8 @@ import {
AlignLeft01,
AlignRight01,
} from '@/app/components/base/icons/src/vender/line/layout'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { APP_SIDEBAR_SHOULD_COLLAPSE } from '@/app/components/app/configuration/debug/types'
export type IAppDetailNavProps = {
iconType?: 'app' | 'dataset' | 'notion'
@@ -39,6 +41,14 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
})
}, [])
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {
if (v.type === APP_SIDEBAR_SHOULD_COLLAPSE) {
setModeState('collapse')
localStorage.setItem('app-detail-collapse-or-expand', 'collapse')
}
})
return (
<div
className={`

View File

@@ -15,10 +15,12 @@ export type Resources = {
type CitationProps = {
data: CitationItem[]
showHitInfo?: boolean
containerClassName?: string
}
const Citation: FC<CitationProps> = ({
data,
showHitInfo,
containerClassName = 'chat-answer-container',
}) => {
const { t } = useTranslation()
const elesRef = useRef<HTMLDivElement[]>([])
@@ -46,7 +48,7 @@ const Citation: FC<CitationProps> = ({
}, []), [data])
const handleAdjustResourcesLayout = () => {
const containerWidth = document.querySelector('.chat-answer-container')!.clientWidth - 40
const containerWidth = document.querySelector(`.${containerClassName}`)!.clientWidth - 40
let totalWidth = 0
for (let i = 0; i < resources.length; i++) {
totalWidth += elesRef.current[i].clientWidth

View File

@@ -1,6 +1,7 @@
import type { Dispatch, FC, ReactNode, RefObject, SetStateAction } from 'react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { uniqueId } from 'lodash-es'
import { File02 } from '@/app/components/base/icons/src/vender/line/files'
import PromptLogModal from '@/app/components/base/prompt-log-modal'
import Tooltip from '@/app/components/base/tooltip'
@@ -39,7 +40,7 @@ const Log: FC<LogProps> = ({
children
? children(setShowModal)
: (
<Tooltip selector='prompt-log-modal-trigger' content={t('common.operation.log') || ''}>
<Tooltip selector={`prompt-log-modal-trigger-${uniqueId()}`} content={t('common.operation.log') || ''}>
<div className={`
hidden absolute -left-[14px] -top-[14px] group-hover:block w-7 h-7
p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-md cursor-pointer

View File

@@ -0,0 +1,188 @@
import type { FC } from 'react'
import {
memo,
useMemo,
} from 'react'
import type { ModelAndParameter } from '../types'
import {
APP_CHAT_WITH_MULTIPLE_MODEL,
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
} from '../types'
import {
AgentStrategy,
ModelModeType,
} from '@/types/app'
import Chat from '@/app/components/base/chat/chat'
import { useChat } from '@/app/components/base/chat/chat/hooks'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
import type {
ChatConfig,
OnSend,
} from '@/app/components/base/chat/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useProviderContext } from '@/context/provider-context'
import {
fetchConvesationMessages,
fetchSuggestedQuestions,
stopChatMessageResponding,
} from '@/service/debug'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
import Avatar from '@/app/components/base/avatar'
import { useAppContext } from '@/context/app-context'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
type ChatItemProps = {
modelAndParameter: ModelAndParameter
}
const ChatItem: FC<ChatItemProps> = ({
modelAndParameter,
}) => {
const { userProfile } = useAppContext()
const {
isAdvancedMode,
modelConfig,
appId,
inputs,
promptMode,
speechToTextConfig,
introduction,
suggestedQuestions: openingSuggestedQuestions,
suggestedQuestionsAfterAnswerConfig,
citationConfig,
moderationConfig,
chatPromptConfig,
completionPromptConfig,
dataSets,
datasetConfigs,
visionConfig,
annotationConfig,
collectionList,
textToSpeechConfig,
} = useDebugConfigurationContext()
const { textGenerationModelList } = useProviderContext()
const postDatasets = dataSets.map(({ id }) => ({
dataset: {
enabled: true,
id,
},
}))
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
const config: ChatConfig = {
pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '',
prompt_type: promptMode,
chat_prompt_config: isAdvancedMode ? chatPromptConfig : {},
completion_prompt_config: isAdvancedMode ? completionPromptConfig : {},
user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables),
dataset_query_variable: contextVar || '',
opening_statement: introduction,
more_like_this: {
enabled: false,
},
suggested_questions: openingSuggestedQuestions,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
text_to_speech: textToSpeechConfig,
speech_to_text: speechToTextConfig,
retriever_resource: citationConfig,
sensitive_word_avoidance: moderationConfig,
agent_mode: {
...modelConfig.agentConfig,
strategy: (modelAndParameter.provider === 'openai' && modelConfig.mode === ModelModeType.chat) ? AgentStrategy.functionCall : AgentStrategy.react,
},
dataset_configs: {
...datasetConfigs,
datasets: {
datasets: [...postDatasets],
} as any,
},
file_upload: {
image: visionConfig,
},
annotation_reply: annotationConfig,
}
const {
chatList,
isResponsing,
handleSend,
suggestedQuestions,
handleRestart,
} = useChat(
config,
{
inputs,
promptVariables: modelConfig.configs.prompt_variables,
},
[],
taskId => stopChatMessageResponding(appId, taskId),
)
const doSend: OnSend = (message, files) => {
const currentProvider = textGenerationModelList.find(item => item.provider === modelAndParameter.provider)
const currentModel = currentProvider?.models.find(model => model.model === modelAndParameter.model)
const supportVision = currentModel?.features?.includes(ModelFeatureEnum.vision)
const configData = {
...config,
model: {
provider: modelAndParameter.provider,
name: modelAndParameter.model,
mode: currentModel?.model_properties.mode,
completion_params: modelAndParameter.parameters,
},
}
const data: any = {
query: message,
inputs,
model_config: configData,
}
if (visionConfig.enabled && files?.length && supportVision)
data.files = files
handleSend(
`apps/${appId}/chat-messages`,
data,
{
onGetConvesationMessages: (conversationId, getAbortController) => fetchConvesationMessages(appId, conversationId, getAbortController),
onGetSuggestedQuestions: (responseItemId, getAbortController) => fetchSuggestedQuestions(appId, responseItemId, getAbortController),
},
)
}
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {
if (v.type === APP_CHAT_WITH_MULTIPLE_MODEL)
doSend(v.payload.message, v.payload.files)
if (v.type === APP_CHAT_WITH_MULTIPLE_MODEL_RESTART)
handleRestart()
})
const allToolIcons = useMemo(() => {
const icons: Record<string, any> = {}
modelConfig.agentConfig.tools?.forEach((item: any) => {
icons[item.tool_name] = collectionList.find((collection: any) => collection.id === item.provider_id)?.icon
})
return icons
}, [collectionList, modelConfig.agentConfig.tools])
if (!chatList.length)
return null
return (
<Chat
config={config}
chatList={chatList}
isResponsing={isResponsing}
noChatInput
chatContainerclassName='p-4'
chatFooterClassName='!-bottom-4'
suggestedQuestions={suggestedQuestions}
onSend={doSend}
showPromptLog
questionIcon={<Avatar name={userProfile.name} size={40} />}
allToolIcons={allToolIcons}
/>
)
}
export default memo(ChatItem)

View File

@@ -0,0 +1,39 @@
'use client'
import { createContext, useContext } from 'use-context-selector'
import type { ModelAndParameter } from '../types'
export type DebugWithMultipleModelContextType = {
multipleModelConfigs: ModelAndParameter[]
onMultipleModelConfigsChange: (multiple: boolean, modelConfigs: ModelAndParameter[]) => void
onDebugWithMultipleModelChange: (singleModelConfig: ModelAndParameter) => void
}
const DebugWithMultipleModelContext = createContext<DebugWithMultipleModelContextType>({
multipleModelConfigs: [],
onMultipleModelConfigsChange: () => {},
onDebugWithMultipleModelChange: () => {},
})
export const useDebugWithMultipleModelContext = () => useContext(DebugWithMultipleModelContext)
type DebugWithMultipleModelContextProviderProps = {
children: React.ReactNode
} & DebugWithMultipleModelContextType
export const DebugWithMultipleModelContextProvider = ({
children,
onMultipleModelConfigsChange,
multipleModelConfigs,
onDebugWithMultipleModelChange,
}: DebugWithMultipleModelContextProviderProps) => {
return (
<DebugWithMultipleModelContext.Provider value={{
onMultipleModelConfigsChange,
multipleModelConfigs,
onDebugWithMultipleModelChange,
}}>
{children}
</DebugWithMultipleModelContext.Provider>
)
}
export default DebugWithMultipleModelContext

View File

@@ -0,0 +1,124 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { memo } from 'react'
import type { ModelAndParameter } from '../types'
import ModelParameterTrigger from './model-parameter-trigger'
import ChatItem from './chat-item'
import TextGenerationItem from './text-generation-item'
import { useDebugWithMultipleModelContext } from './context'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
import Dropdown from '@/app/components/base/dropdown'
import type { Item } from '@/app/components/base/dropdown'
import { useProviderContext } from '@/context/provider-context'
import { ModelStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
type DebugItemProps = {
modelAndParameter: ModelAndParameter
className?: string
}
const DebugItem: FC<DebugItemProps> = ({
modelAndParameter,
className,
}) => {
const { t } = useTranslation()
const { mode } = useDebugConfigurationContext()
const {
multipleModelConfigs,
onMultipleModelConfigsChange,
onDebugWithMultipleModelChange,
} = useDebugWithMultipleModelContext()
const { textGenerationModelList } = useProviderContext()
const index = multipleModelConfigs.findIndex(v => v.id === modelAndParameter.id)
const currentProvider = textGenerationModelList.find(item => item.provider === modelAndParameter.provider)
const currentModel = currentProvider?.models.find(item => item.model === modelAndParameter.model)
const handleSelect = (item: Item) => {
if (item.value === 'duplicate') {
if (multipleModelConfigs.length >= 4)
return
onMultipleModelConfigsChange(
true,
[
...multipleModelConfigs.slice(0, index + 1),
{
...modelAndParameter,
id: `${Date.now()}`,
},
...multipleModelConfigs.slice(index + 1),
],
)
}
if (item.value === 'debug-as-single-model')
onDebugWithMultipleModelChange(modelAndParameter)
if (item.value === 'remove') {
onMultipleModelConfigsChange(
true,
multipleModelConfigs.filter(item => item.id !== modelAndParameter.id),
)
}
}
return (
<div className={`flex flex-col min-w-[320px] rounded-xl bg-white border-[0.5px] border-black/5 ${className}`}>
<div className='shrink-0 flex items-center justify-between h-10 px-3 border-b-[0.5px] border-b-black/5'>
<div className='flex items-center justify-center w-6 h-5 font-medium italic text-gray-500'>
#{index + 1}
</div>
<ModelParameterTrigger
modelAndParameter={modelAndParameter}
/>
<Dropdown
onSelect={handleSelect}
items={[
...(
multipleModelConfigs.length <= 3
? [
{
value: 'duplicate',
text: t('appDebug.duplicateModel'),
},
]
: []
),
...(
(modelAndParameter.provider && modelAndParameter.model)
? [
{
value: 'debug-as-single-model',
text: t('appDebug.debugAsSingleModel'),
},
]
: []
),
]}
secondItems={
multipleModelConfigs.length > 2
? [
{
value: 'remove',
text: t('common.operation.remove'),
},
]
: undefined
}
/>
</div>
<div style={{ height: 'calc(100% - 40px)' }}>
{
mode === 'chat' && currentProvider && currentModel && currentModel.status === ModelStatusEnum.active && (
<ChatItem modelAndParameter={modelAndParameter} />
)
}
{
mode === 'completion' && currentProvider && currentModel && currentModel.status === ModelStatusEnum.active && (
<TextGenerationItem modelAndParameter={modelAndParameter}/>
)
}
</div>
</div>
)
}
export default memo(DebugItem)

View File

@@ -0,0 +1,131 @@
import type { FC } from 'react'
import {
memo,
useCallback,
} from 'react'
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types'
import DebugItem from './debug-item'
import {
DebugWithMultipleModelContextProvider,
useDebugWithMultipleModelContext,
} from './context'
import type { DebugWithMultipleModelContextType } from './context'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import ChatInput from '@/app/components/base/chat/chat/chat-input'
import type { VisionFile } from '@/app/components/base/chat/types'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
const DebugWithMultipleModel = () => {
const {
mode,
speechToTextConfig,
visionConfig,
} = useDebugConfigurationContext()
const { multipleModelConfigs } = useDebugWithMultipleModelContext()
const { eventEmitter } = useEventEmitterContextContext()
const handleSend = useCallback((message: string, files?: VisionFile[]) => {
eventEmitter?.emit({
type: APP_CHAT_WITH_MULTIPLE_MODEL,
payload: {
message,
files,
},
} as any)
}, [eventEmitter])
const twoLine = multipleModelConfigs.length === 2
const threeLine = multipleModelConfigs.length === 3
const fourLine = multipleModelConfigs.length === 4
return (
<div className='flex flex-col h-full'>
<div
className={`
mb-3 overflow-auto
${(twoLine || threeLine) && 'flex gap-2'}
`}
style={{ height: mode === 'chat' ? 'calc(100% - 60px)' : '100%' }}
>
{
(twoLine || threeLine) && multipleModelConfigs.map(modelConfig => (
<DebugItem
key={modelConfig.id}
modelAndParameter={modelConfig}
className={`
h-full min-h-[200px]
${twoLine && 'w-1/2'}
${threeLine && 'w-1/3'}
`}
/>
))
}
{
fourLine && (
<>
<div
className='flex space-x-2 mb-2 min-h-[200px]'
style={{ height: 'calc(50% - 4px)' }}
>
{
multipleModelConfigs.slice(0, 2).map(modelConfig => (
<DebugItem
key={modelConfig.id}
modelAndParameter={modelConfig}
className='w-1/2 h-full'
/>
))
}
</div>
<div
className='flex space-x-2 min-h-[200px]'
style={{ height: 'calc(50% - 4px)' }}
>
{
multipleModelConfigs.slice(2, 4).map(modelConfig => (
<DebugItem
key={modelConfig.id}
modelAndParameter={modelConfig}
className='w-1/2 h-full'
/>
))
}
</div>
</>
)
}
</div>
{
mode === 'chat' && (
<div className='shrink-0'>
<ChatInput
onSend={handleSend}
speechToTextConfig={speechToTextConfig}
visionConfig={visionConfig}
/>
</div>
)
}
</div>
)
}
const DebugWithMultipleModelMemoed = memo(DebugWithMultipleModel)
const DebugWithMultipleModelWrapper: FC<DebugWithMultipleModelContextType> = ({
onMultipleModelConfigsChange,
multipleModelConfigs,
onDebugWithMultipleModelChange,
}) => {
return (
<DebugWithMultipleModelContextProvider
onMultipleModelConfigsChange={onMultipleModelConfigsChange}
multipleModelConfigs={multipleModelConfigs}
onDebugWithMultipleModelChange={onDebugWithMultipleModelChange}
>
<DebugWithMultipleModelMemoed />
</DebugWithMultipleModelContextProvider>
)
}
export default memo(DebugWithMultipleModelWrapper)

View File

@@ -0,0 +1,125 @@
import type { FC } from 'react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import type { ModelAndParameter } from '../types'
import { useDebugWithMultipleModelContext } from './context'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
import {
MODEL_STATUS_TEXT,
ModelStatusEnum,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
type ModelParameterTriggerProps = {
modelAndParameter: ModelAndParameter
}
const ModelParameterTrigger: FC<ModelParameterTriggerProps> = ({
modelAndParameter,
}) => {
const { t } = useTranslation()
const {
mode,
isAdvancedMode,
} = useDebugConfigurationContext()
const {
multipleModelConfigs,
onMultipleModelConfigsChange,
onDebugWithMultipleModelChange,
} = useDebugWithMultipleModelContext()
const language = useLanguage()
const index = multipleModelConfigs.findIndex(v => v.id === modelAndParameter.id)
const handleSelectModel = ({ modelId, provider }: { modelId: string; provider: string }) => {
const newModelConfigs = [...multipleModelConfigs]
newModelConfigs[index] = {
...newModelConfigs[index],
model: modelId,
provider,
}
onMultipleModelConfigsChange(true, newModelConfigs)
}
const handleParamsChange = (params: any) => {
const newModelConfigs = [...multipleModelConfigs]
newModelConfigs[index] = {
...newModelConfigs[index],
parameters: params,
}
onMultipleModelConfigsChange(true, newModelConfigs)
}
return (
<ModelParameterModal
mode={mode}
isAdvancedMode={isAdvancedMode}
provider={modelAndParameter.provider}
modelId={modelAndParameter.model}
completionParams={modelAndParameter.parameters}
onCompletionParamsChange={handleParamsChange}
setModel={handleSelectModel}
debugWithMultipleModel
onDebugWithMultipleModelChange={() => onDebugWithMultipleModelChange(modelAndParameter)}
renderTrigger={({
open,
currentProvider,
currentModel,
}) => (
<div
className={`
flex items-center max-w-[200px] h-8 px-2 rounded-lg cursor-pointer
${open && 'bg-gray-100'}
${currentModel && currentModel.status !== ModelStatusEnum.active && '!bg-[#FFFAEB]'}
`}
>
{
currentProvider && (
<ModelIcon
className='mr-1 !w-4 !h-4'
provider={currentProvider}
modelName={currentModel?.model}
/>
)
}
{
!currentProvider && (
<div className='flex items-center justify-center mr-1 w-4 h-4 rounded border border-dashed border-primary-100'>
<CubeOutline className='w-[11px] h-[11px] text-primary-600' />
</div>
)
}
{
currentModel && (
<ModelName
className='mr-0.5 text-gray-800'
modelItem={currentModel}
/>
)
}
{
!currentModel && (
<div className='mr-0.5 text-[13px] font-medium text-primary-600 truncate'>
{t('common.modelProvider.selectModel')}
</div>
)
}
<ChevronDown className={`w-3 h-3 ${(currentModel && currentProvider) ? 'text-gray-800' : 'text-primary-600'}`} />
{
currentModel && currentModel.status !== ModelStatusEnum.active && (
<TooltipPlus popupContent={MODEL_STATUS_TEXT[currentModel.status][language]}>
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
</TooltipPlus>
)
}
</div>
)}
/>
)
}
export default memo(ModelParameterTrigger)

View File

@@ -0,0 +1,103 @@
import type { FC } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { ModelAndParameter } from '../types'
import Button from '@/app/components/base/button'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { useProviderContext } from '@/context/provider-context'
import type { ModelItem } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
type PublishWithMultipleModelProps = {
multipleModelConfigs: ModelAndParameter[]
onSelect: (v: ModelAndParameter) => void
}
const PublishWithMultipleModel: FC<PublishWithMultipleModelProps> = ({
multipleModelConfigs,
onSelect,
}) => {
const { t } = useTranslation()
const language = useLanguage()
const { textGenerationModelList } = useProviderContext()
const [open, setOpen] = useState(false)
const validModelConfigs: (ModelAndParameter & { modelItem: ModelItem })[] = []
multipleModelConfigs.forEach((item) => {
const provider = textGenerationModelList.find(model => model.provider === item.provider)
if (provider) {
const model = provider.models.find(model => model.model === item.model)
if (model) {
validModelConfigs.push({
id: item.id,
model: item.model,
provider: item.provider,
modelItem: model,
parameters: item.parameters,
})
}
}
})
const handleToggle = () => {
if (validModelConfigs.length)
setOpen(v => !v)
}
const handleSelect = (item: ModelAndParameter) => {
onSelect(item)
setOpen(false)
}
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
>
<PortalToFollowElemTrigger onClick={handleToggle}>
<Button
type='primary'
disabled={!validModelConfigs.length}
className='pl-3 pr-2 h-8 text-[13px]'
>
{t('appDebug.operation.applyConfig')}
<ChevronDown className='ml-0.5 w-3 h-3' />
</Button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='p-1 w-[168px] rounded-lg border-[0.5px] border-gray-200 shadow-lg bg-white'>
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>
{t('appDebug.publishAs')}
</div>
{
validModelConfigs.map((item, index) => (
<div
key={item.id}
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-100 cursor-pointer text-sm text-gray-500'
onClick={() => handleSelect(item)}
>
#{index + 1}
<div
className='ml-1 text-gray-700 truncate'
title={item.modelItem.label[language]}
>
{item.modelItem.label[language]}
</div>
</div>
))
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default PublishWithMultipleModel

View File

@@ -0,0 +1,153 @@
import type { FC } from 'react'
import { memo } from 'react'
import type { ModelAndParameter } from '../types'
import { APP_CHAT_WITH_MULTIPLE_MODEL } from '../types'
import type {
OnSend,
TextGenerationConfig,
} from '@/app/components/base/text-generation/types'
import { useTextGeneration } from '@/app/components/base/text-generation/hooks'
import TextGeneration from '@/app/components/app/text-generate/item'
import { useDebugConfigurationContext } from '@/context/debug-configuration'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
import { TransferMethod } from '@/app/components/base/chat/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useProviderContext } from '@/context/provider-context'
type TextGenerationItemProps = {
modelAndParameter: ModelAndParameter
}
const TextGenerationItem: FC<TextGenerationItemProps> = ({
modelAndParameter,
}) => {
const {
isAdvancedMode,
modelConfig,
appId,
inputs,
promptMode,
speechToTextConfig,
introduction,
suggestedQuestionsAfterAnswerConfig,
citationConfig,
moderationConfig,
externalDataToolsConfig,
chatPromptConfig,
completionPromptConfig,
dataSets,
datasetConfigs,
visionConfig,
moreLikeThisConfig,
} = useDebugConfigurationContext()
const { textGenerationModelList } = useProviderContext()
const postDatasets = dataSets.map(({ id }) => ({
dataset: {
enabled: true,
id,
},
}))
const contextVar = modelConfig.configs.prompt_variables.find(item => item.is_context_var)?.key
const config: TextGenerationConfig = {
pre_prompt: !isAdvancedMode ? modelConfig.configs.prompt_template : '',
prompt_type: promptMode,
chat_prompt_config: isAdvancedMode ? chatPromptConfig : {},
completion_prompt_config: isAdvancedMode ? completionPromptConfig : {},
user_input_form: promptVariablesToUserInputsForm(modelConfig.configs.prompt_variables),
dataset_query_variable: contextVar || '',
opening_statement: introduction,
suggested_questions_after_answer: suggestedQuestionsAfterAnswerConfig,
speech_to_text: speechToTextConfig,
retriever_resource: citationConfig,
sensitive_word_avoidance: moderationConfig,
external_data_tools: externalDataToolsConfig,
more_like_this: moreLikeThisConfig,
agent_mode: {
enabled: false,
tools: [],
},
dataset_configs: {
...datasetConfigs,
datasets: {
datasets: [...postDatasets],
} as any,
},
file_upload: {
image: visionConfig,
},
}
const {
completion,
handleSend,
isResponsing,
messageId,
} = useTextGeneration()
const doSend: OnSend = (message, files) => {
const currentProvider = textGenerationModelList.find(item => item.provider === modelAndParameter.provider)
const currentModel = currentProvider?.models.find(model => model.model === modelAndParameter.model)
const configData = {
...config,
model: {
provider: modelAndParameter.provider,
name: modelAndParameter.model,
mode: currentModel?.model_properties.mode,
completion_params: modelAndParameter.parameters,
},
}
const data: any = {
inputs,
model_config: configData,
}
if (visionConfig.enabled && files && files?.length > 0) {
data.files = files.map((item) => {
if (item.transfer_method === TransferMethod.local_file) {
return {
...item,
url: '',
}
}
return item
})
}
handleSend(
`apps/${appId}/completion-messages`,
data,
)
}
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {
if (v.type === APP_CHAT_WITH_MULTIPLE_MODEL)
doSend(v.payload.message, v.payload.files)
})
const varList = modelConfig.configs.prompt_variables.map((item: any) => {
return {
label: item.key,
value: inputs[item.key],
}
})
return (
<TextGeneration
className='flex flex-col h-full overflow-y-auto border-none'
innerClassName='grow flex flex-col'
contentClassName='grow'
content={completion}
isLoading={!completion && isResponsing}
isResponsing={isResponsing}
isInstalledApp={false}
messageId={messageId}
isError={false}
onRetry={() => { }}
appId={appId}
varList={varList}
/>
)
}
export default memo(TextGenerationItem)

View File

@@ -0,0 +1,54 @@
import {
useCallback,
useRef,
useState,
} from 'react'
import type {
DebugWithSingleOrMultipleModelConfigs,
ModelAndParameter,
} from './types'
export const useDebugWithSingleOrMultipleModel = (appId: string) => {
const localeDebugWithSingleOrMultipleModelConfigs = localStorage.getItem('app-debug-with-single-or-multiple-models')
const debugWithSingleOrMultipleModelConfigs = useRef<DebugWithSingleOrMultipleModelConfigs>({})
if (localeDebugWithSingleOrMultipleModelConfigs) {
try {
debugWithSingleOrMultipleModelConfigs.current = JSON.parse(localeDebugWithSingleOrMultipleModelConfigs) || {}
}
catch (e) {
console.error(e)
}
}
const [
debugWithMultipleModel,
setDebugWithMultipleModel,
] = useState(debugWithSingleOrMultipleModelConfigs.current[appId]?.multiple || false)
const [
multipleModelConfigs,
setMultipleModelConfigs,
] = useState(debugWithSingleOrMultipleModelConfigs.current[appId]?.configs || [])
const handleMultipleModelConfigsChange = useCallback((
multiple: boolean,
modelConfigs: ModelAndParameter[],
) => {
const value = {
multiple,
configs: modelConfigs,
}
debugWithSingleOrMultipleModelConfigs.current[appId] = value
localStorage.setItem('app-debug-with-single-or-multiple-models', JSON.stringify(debugWithSingleOrMultipleModelConfigs.current))
setDebugWithMultipleModel(value.multiple)
setMultipleModelConfigs(value.configs)
}, [appId])
return {
debugWithMultipleModel,
multipleModelConfigs,
handleMultipleModelConfigsChange,
}
}

View File

@@ -12,6 +12,12 @@ import HasNotSetAPIKEY from '../base/warning-mask/has-not-set-api'
import FormattingChanged from '../base/warning-mask/formatting-changed'
import GroupName from '../base/group-name'
import CannotQueryDataset from '../base/warning-mask/cannot-query-dataset'
import DebugWithMultipleModel from './debug-with-multiple-model'
import type { ModelAndParameter } from './types'
import {
APP_CHAT_WITH_MULTIPLE_MODEL,
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
} from './types'
import { AgentStrategy, AppType, ModelModeType, TransferMethod } from '@/types/app'
import PromptValuePanel, { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
import type { IChatItem } from '@/app/components/app/chat/type'
@@ -28,17 +34,30 @@ import type { Inputs } from '@/models/debug'
import { fetchFileUploadConfig } from '@/service/common'
import type { Annotation as AnnotationType } from '@/models/log'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { ModelParameterModalProps } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useProviderContext } from '@/context/provider-context'
type IDebug = {
hasSetAPIKEY: boolean
onSetting: () => void
inputs: Inputs
modelParameterParams: Pick<ModelParameterModalProps, 'setModel' | 'onCompletionParamsChange'>
debugWithMultipleModel: boolean
multipleModelConfigs: ModelAndParameter[]
onMultipleModelConfigsChange: (multiple: boolean, modelConfigs: ModelAndParameter[]) => void
}
const Debug: FC<IDebug> = ({
hasSetAPIKEY = true,
onSetting,
inputs,
modelParameterParams,
debugWithMultipleModel,
multipleModelConfigs,
onMultipleModelConfigsChange,
}) => {
const { t } = useTranslation()
const {
@@ -72,7 +91,9 @@ const Debug: FC<IDebug> = ({
datasetConfigs,
visionConfig,
annotationConfig,
setVisionConfig,
} = useContext(ConfigContext)
const { eventEmitter } = useEventEmitterContextContext()
const { data: speech2textDefaultModel } = useDefaultModel(4)
const { data: text2speechDefaultModel } = useDefaultModel(5)
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
@@ -119,7 +140,7 @@ const Debug: FC<IDebug> = ({
setFormattingChanged(false)
}, [formattingChanged])
const clearConversation = async () => {
const handleClearConversation = () => {
setConversationId(null)
abortController?.abort()
setResponsingFalse()
@@ -134,6 +155,16 @@ const Debug: FC<IDebug> = ({
: [])
setIsShowSuggestion(false)
}
const clearConversation = async () => {
if (debugWithMultipleModel) {
eventEmitter?.emit({
type: APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
} as any)
return
}
handleClearConversation()
}
const handleConfirm = () => {
clearConversation()
@@ -601,6 +632,21 @@ const Debug: FC<IDebug> = ({
})
}
const handleSendTextCompletion = () => {
if (debugWithMultipleModel) {
eventEmitter?.emit({
type: APP_CHAT_WITH_MULTIPLE_MODEL,
payload: {
message: '',
files: completionFiles,
},
} as any)
return
}
sendTextCompletion()
}
const varList = modelConfig.configs.prompt_variables.map((item: any) => {
return {
label: item.key,
@@ -608,6 +654,51 @@ const Debug: FC<IDebug> = ({
}
})
const { textGenerationModelList } = useProviderContext()
const handleChangeToSingleModel = (item: ModelAndParameter) => {
const currentProvider = textGenerationModelList.find(modelItem => modelItem.provider === item.provider)
const currentModel = currentProvider?.models.find(model => model.model === item.model)
modelParameterParams.setModel({
modelId: item.model,
provider: item.provider,
mode: currentModel?.model_properties.mode as string,
features: currentModel?.features,
})
modelParameterParams.onCompletionParamsChange(item.parameters)
onMultipleModelConfigsChange(
false,
[],
)
}
const handleVisionConfigInMultipleModel = () => {
if (debugWithMultipleModel && !visionConfig.enabled) {
const supportedVision = multipleModelConfigs.some((modelConfig) => {
const currentProvider = textGenerationModelList.find(modelItem => modelItem.provider === modelConfig.provider)
const currentModel = currentProvider?.models.find(model => model.model === modelConfig.model)
return currentModel?.features?.includes(ModelFeatureEnum.vision)
})
if (supportedVision) {
setVisionConfig({
...visionConfig,
enabled: true,
})
}
else {
setVisionConfig({
...visionConfig,
enabled: false,
})
}
}
}
useEffect(() => {
handleVisionConfigInMultipleModel()
}, [multipleModelConfigs])
const allToolIcons = (() => {
const icons: Record<string, any> = {}
modelConfig.agentConfig.tools?.forEach((item: any) => {
@@ -621,18 +712,40 @@ const Debug: FC<IDebug> = ({
<div className="shrink-0">
<div className='flex items-center justify-between mb-2'>
<div className='h2 '>{t('appDebug.inputs.title')}</div>
{mode === 'chat' && (
<Button className='flex items-center gap-1 !h-8 !bg-white' onClick={clearConversation}>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.66663 2.66629V5.99963H3.05463M3.05463 5.99963C3.49719 4.90505 4.29041 3.98823 5.30998 3.39287C6.32954 2.7975 7.51783 2.55724 8.68861 2.70972C9.85938 2.8622 10.9465 3.39882 11.7795 4.23548C12.6126 5.07213 13.1445 6.16154 13.292 7.33296M3.05463 5.99963H5.99996M13.3333 13.333V9.99963H12.946M12.946 9.99963C12.5028 11.0936 11.7093 12.0097 10.6898 12.6045C9.67038 13.1993 8.48245 13.4393 7.31203 13.2869C6.1416 13.1344 5.05476 12.5982 4.22165 11.7621C3.38854 10.926 2.8562 9.83726 2.70796 8.66629M12.946 9.99963H9.99996" stroke="#1C64F2" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<span className='text-primary-600 text-[13px] font-semibold'>{t('common.operation.refresh')}</span>
</Button>
)}
<div className='flex items-center'>
{
debugWithMultipleModel
? (
<>
<Button
className={`
h-8 px-2.5 text-[13px] font-medium text-primary-600 bg-white
${multipleModelConfigs.length >= 4 && 'opacity-30'}
`}
onClick={() => onMultipleModelConfigsChange(true, [...multipleModelConfigs, { id: `${Date.now()}`, model: '', provider: '', parameters: {} }])}
disabled={multipleModelConfigs.length >= 4}
>
<Plus className='mr-1 w-3.5 h-3.5' />
{t('common.modelProvider.addModel')}({multipleModelConfigs.length}/4)
</Button>
<div className='mx-2 w-[1px] h-[14px] bg-gray-200' />
</>
)
: null
}
{mode === 'chat' && (
<Button className='flex items-center gap-1 !h-8 !bg-white' onClick={clearConversation}>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.66663 2.66629V5.99963H3.05463M3.05463 5.99963C3.49719 4.90505 4.29041 3.98823 5.30998 3.39287C6.32954 2.7975 7.51783 2.55724 8.68861 2.70972C9.85938 2.8622 10.9465 3.39882 11.7795 4.23548C12.6126 5.07213 13.1445 6.16154 13.292 7.33296M3.05463 5.99963H5.99996M13.3333 13.333V9.99963H12.946M12.946 9.99963C12.5028 11.0936 11.7093 12.0097 10.6898 12.6045C9.67038 13.1993 8.48245 13.4393 7.31203 13.2869C6.1416 13.1344 5.05476 12.5982 4.22165 11.7621C3.38854 10.926 2.8562 9.83726 2.70796 8.66629M12.946 9.99963H9.99996" stroke="#1C64F2" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<span className='text-primary-600 text-[13px] font-semibold'>{t('common.operation.refresh')}</span>
</Button>
)}
</div>
</div>
<PromptValuePanel
appType={mode as AppType}
onSend={sendTextCompletion}
onSend={handleSendTextCompletion}
inputs={inputs}
visionConfig={{
...visionConfig,
@@ -641,81 +754,96 @@ const Debug: FC<IDebug> = ({
onVisionFilesChange={setCompletionFiles}
/>
</div>
<div className="flex flex-col grow">
{/* Chat */}
{mode === AppType.chat && (
<div className="mt-[34px] h-full flex flex-col">
<div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[76px]'), 'relative mt-1.5 grow h-[200px] overflow-hidden')}>
<div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}>
<Chat
chatList={chatList}
query={userQuery}
onQueryChange={setUserQuery}
onSend={onSend}
checkCanSend={checkCanSend}
feedbackDisabled
useCurrentUserAvatar
isResponsing={isResponsing}
canStopResponsing={!!messageTaskId}
abortResponsing={async () => {
await stopChatMessageResponding(appId, messageTaskId)
setHasStopResponded(true)
setResponsingFalse()
}}
isShowSuggestion={doShowSuggestion}
suggestionList={suggestQuestions}
isShowSpeechToText={speechToTextConfig.enabled && !!speech2textDefaultModel}
isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel}
isShowCitation={citationConfig.enabled}
isShowCitationHitInfo
isShowPromptLog
visionConfig={{
...visionConfig,
image_file_size_limit: fileUploadConfigResponse?.image_file_size_limit,
}}
supportAnnotation
appId={appId}
onChatListChange={setChatList}
allToolIcons={allToolIcons}
/>
</div>
</div>
{
debugWithMultipleModel && (
<div className='grow mt-3 overflow-hidden'>
<DebugWithMultipleModel
multipleModelConfigs={multipleModelConfigs}
onMultipleModelConfigsChange={onMultipleModelConfigsChange}
onDebugWithMultipleModelChange={handleChangeToSingleModel}
/>
</div>
)}
{/* Text Generation */}
{mode === AppType.completion && (
<div className="mt-6">
<GroupName name={t('appDebug.result')} />
{(completionRes || isResponsing) && (
<TextGeneration
className="mt-2"
content={completionRes}
isLoading={!completionRes && isResponsing}
isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel}
isResponsing={isResponsing}
isInstalledApp={false}
messageId={messageId}
isError={false}
onRetry={() => { }}
supportAnnotation
appId={appId}
varList={varList}
)
}
{
!debugWithMultipleModel && (
<div className="flex flex-col grow">
{/* Chat */}
{mode === AppType.chat && (
<div className="mt-[34px] h-full flex flex-col">
<div className={cn(doShowSuggestion ? 'pb-[140px]' : (isResponsing ? 'pb-[113px]' : 'pb-[76px]'), 'relative mt-1.5 grow h-[200px] overflow-hidden')}>
<div className="h-full overflow-y-auto overflow-x-hidden" ref={chatListDomRef}>
<Chat
chatList={chatList}
query={userQuery}
onQueryChange={setUserQuery}
onSend={onSend}
checkCanSend={checkCanSend}
feedbackDisabled
useCurrentUserAvatar
isResponsing={isResponsing}
canStopResponsing={!!messageTaskId}
abortResponsing={async () => {
await stopChatMessageResponding(appId, messageTaskId)
setHasStopResponded(true)
setResponsingFalse()
}}
isShowSuggestion={doShowSuggestion}
suggestionList={suggestQuestions}
isShowSpeechToText={speechToTextConfig.enabled && !!speech2textDefaultModel}
isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel}
isShowCitation={citationConfig.enabled}
isShowCitationHitInfo
isShowPromptLog
visionConfig={{
...visionConfig,
image_file_size_limit: fileUploadConfigResponse?.image_file_size_limit,
}}
supportAnnotation
appId={appId}
onChatListChange={setChatList}
allToolIcons={allToolIcons}
/>
</div>
</div>
</div>
)}
{/* Text Generation */}
{mode === AppType.completion && (
<div className="mt-6">
<GroupName name={t('appDebug.result')} />
{(completionRes || isResponsing) && (
<TextGeneration
className="mt-2"
content={completionRes}
isLoading={!completionRes && isResponsing}
isShowTextToSpeech={textToSpeechConfig.enabled && !!text2speechDefaultModel}
isResponsing={isResponsing}
isInstalledApp={false}
messageId={messageId}
isError={false}
onRetry={() => { }}
supportAnnotation
appId={appId}
varList={varList}
/>
)}
</div>
)}
{isShowFormattingChangeConfirm && (
<FormattingChanged
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
)}
{isShowCannotQueryDataset && (
<CannotQueryDataset
onConfirm={() => setShowCannotQueryDataset(false)}
/>
)}
</div>
)}
{isShowFormattingChangeConfirm && (
<FormattingChanged
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
)}
{isShowCannotQueryDataset && (
<CannotQueryDataset
onConfirm={() => setShowCannotQueryDataset(false)}
/>
)}
</div>
)
}
{!hasSetAPIKEY && (<HasNotSetAPIKEY isTrailFinished={!IS_CE_EDITION} onSetting={onSetting} />)}
</>
)

View File

@@ -0,0 +1,18 @@
export type ModelAndParameter = {
id: string
model: string
provider: string
parameters: Record<string, any>
}
export type MultipleAndConfigs = {
multiple: boolean
configs: ModelAndParameter[]
}
export type DebugWithSingleOrMultipleModelConfigs = {
[k: string]: MultipleAndConfigs
}
export const APP_CHAT_WITH_MULTIPLE_MODEL = 'APP_CHAT_WITH_MULTIPLE_MODEL'
export const APP_CHAT_WITH_MULTIPLE_MODEL_RESTART = 'APP_CHAT_WITH_MULTIPLE_MODEL_RESTART'
export const APP_SIDEBAR_SHOULD_COLLAPSE = 'APP_SIDEBAR_SHOULD_COLLAPSE'

View File

@@ -13,6 +13,10 @@ import Button from '../../base/button'
import Loading from '../../base/loading'
import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config'
import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal'
import { useDebugWithSingleOrMultipleModel } from './debug/hooks'
import type { ModelAndParameter } from './debug/types'
import { APP_SIDEBAR_SHOULD_COLLAPSE } from './debug/types'
import PublishWithMultipleModel from './debug/debug-with-multiple-model/publish-with-multiple-model'
import AssistantTypePicker from './config/assistant-type-picker'
import type {
AnnotationReplyConfig,
@@ -50,6 +54,7 @@ import type { FormValue } from '@/app/components/header/account-setting/model-pr
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { fetchCollectionList } from '@/service/tools'
import { type Collection } from '@/app/components/tools/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
type PublichConfig = {
modelConfig: ModelConfig
@@ -524,8 +529,8 @@ const Configuration: FC = () => {
else { return promptEmpty }
})()
const contextVarEmpty = mode === AppType.completion && dataSets.length > 0 && !hasSetContextVar
const handlePublish = async (isSilence?: boolean) => {
const modelId = modelConfig.model_id
const handlePublish = async (isSilence?: boolean, modelAndParameter?: ModelAndParameter) => {
const modelId = modelAndParameter?.model || modelConfig.model_id
const promptTemplate = modelConfig.configs.prompt_template
const promptVariables = modelConfig.configs.prompt_variables
@@ -578,10 +583,10 @@ const Configuration: FC = () => {
strategy: isFunctionCall ? AgentStrategy.functionCall : AgentStrategy.react,
},
model: {
provider: modelConfig.provider,
provider: modelAndParameter?.provider || modelConfig.provider,
name: modelId,
mode: modelConfig.mode,
completion_params: completionParams as any,
completion_params: modelAndParameter?.parameters || completionParams as any,
},
dataset_configs: {
...datasetConfigs,
@@ -629,6 +634,26 @@ const Configuration: FC = () => {
const [showUseGPT4Confirm, setShowUseGPT4Confirm] = useState(false)
const { locale } = useContext(I18n)
const { eventEmitter } = useEventEmitterContextContext()
const {
debugWithMultipleModel,
multipleModelConfigs,
handleMultipleModelConfigsChange,
} = useDebugWithSingleOrMultipleModel(appId)
const handleDebugWithMultipleModelChange = () => {
handleMultipleModelConfigsChange(
true,
[
{ id: `${Date.now()}`, model: modelConfig.model_id, provider: modelConfig.provider, parameters: completionParams },
{ id: `${Date.now()}-no-repeat`, model: '', provider: '', parameters: {} },
],
)
eventEmitter?.emit({
type: APP_SIDEBAR_SHOULD_COLLAPSE,
} as any)
}
if (isLoading) {
return <div className='flex h-full items-center justify-center'>
<Loading type='area' />
@@ -709,7 +734,7 @@ const Configuration: FC = () => {
<>
<div className="flex flex-col h-full">
<div className='flex grow h-[200px]'>
<div className="w-full sm:w-1/2 shrink-0 flex flex-col h-full">
<div className={`w-full sm:w-1/2 shrink-0 flex flex-col h-full ${debugWithMultipleModel && 'max-w-[560px]'}`}>
{/* Header Left */}
<div className='flex justify-between items-center px-6 h-14'>
<div className='flex items-center'>
@@ -743,22 +768,30 @@ const Configuration: FC = () => {
</div>
<Config />
</div>
{!isMobile && <div className="relative w-1/2 h-full overflow-y-auto flex flex-col " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
{!isMobile && <div className="grow relative w-1/2 h-full overflow-y-auto flex flex-col " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
{/* Header Right */}
<div className='flex justify-end items-center flex-wrap px-6 h-14 space-x-2'>
{/* Model and Parameters */}
<ModelParameterModal
isAdvancedMode={isAdvancedMode}
mode={mode}
provider={modelConfig.provider}
completionParams={completionParams}
modelId={modelConfig.model_id}
setModel={setModel as any}
onCompletionParamsChange={(newParams: FormValue) => {
setCompletionParams(newParams)
}}
/>
<div className='w-[1px] h-[14px] bg-gray-200'></div>
{
!debugWithMultipleModel && (
<>
<ModelParameterModal
isAdvancedMode={isAdvancedMode}
mode={mode}
provider={modelConfig.provider}
completionParams={completionParams}
modelId={modelConfig.model_id}
setModel={setModel as any}
onCompletionParamsChange={(newParams: FormValue) => {
setCompletionParams(newParams)
}}
debugWithMultipleModel={debugWithMultipleModel}
onDebugWithMultipleModelChange={handleDebugWithMultipleModelChange}
/>
<div className='w-[1px] h-[14px] bg-gray-200'></div>
</>
)
}
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
{isMobile && (
<Button className='!h-8 !text-[13px] font-medium' onClick={showDebugPanel}>
@@ -766,13 +799,37 @@ const Configuration: FC = () => {
<CodeBracketIcon className="h-4 w-4 text-gray-500" />
</Button>
)}
<Button type='primary' onClick={() => handlePublish(false)} className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}>{t('appDebug.operation.applyConfig')}</Button>
{
debugWithMultipleModel
? (
<PublishWithMultipleModel
multipleModelConfigs={multipleModelConfigs}
onSelect={item => handlePublish(false, item)}
/>
)
: (
<Button
type='primary'
onClick={() => handlePublish(false)}
className={cn(cannotPublish && '!bg-primary-200 !cursor-not-allowed', 'shrink-0 w-[70px] !h-8 !text-[13px] font-medium')}
>
{t('appDebug.operation.applyConfig')}
</Button>
)
}
</div>
<div className='flex flex-col grow h-0 px-6 py-4 rounded-tl-2xl border-t border-l bg-gray-50 '>
<Debug
hasSetAPIKEY={hasSettedApiKey}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
inputs={inputs}
modelParameterParams={{
setModel: setModel as any,
onCompletionParamsChange: setCompletionParams,
}}
debugWithMultipleModel={debugWithMultipleModel}
multipleModelConfigs={multipleModelConfigs}
onMultipleModelConfigsChange={handleMultipleModelConfigsChange}
/>
</div>
</div>}
@@ -829,6 +886,13 @@ const Configuration: FC = () => {
hasSetAPIKEY={hasSettedApiKey}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
inputs={inputs}
modelParameterParams={{
setModel: setModel as any,
onCompletionParamsChange: setCompletionParams,
}}
debugWithMultipleModel={debugWithMultipleModel}
multipleModelConfigs={multipleModelConfigs}
onMultipleModelConfigsChange={handleMultipleModelConfigsChange}
/>
</Drawer>
)}

View File

@@ -49,6 +49,9 @@ export type IGenerationItemProps = {
isShowTextToSpeech?: boolean
appId?: string
varList?: { label: string; value: string | number | object }[]
innerClassName?: string
contentClassName?: string
footerClassName?: string
}
export const SimpleBtn = ({ className, isDisabled, onClick, children }: {
@@ -95,6 +98,8 @@ const GenerationItem: FC<IGenerationItemProps> = ({
isShowTextToSpeech,
appId,
varList,
innerClassName,
contentClassName,
}) => {
const { t } = useTranslation()
const params = useParams()
@@ -177,7 +182,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
const handleOpenLogModal = async (setModal: Dispatch<SetStateAction<boolean>>) => {
const data = await fetchTextGenerationMessge({
appId: params.appId,
appId: params.appId as string,
messageId: messageId!,
})
setPromptLog(data.message as any || [])
@@ -249,7 +254,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
)
: (
<div
className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4')}
className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4', innerClassName)}
style={mainStyle}
>
{(isTop && taskId) && (
@@ -258,7 +263,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
{taskId}
</div>)
}
<div className='flex'>
<div className={`flex ${contentClassName}`}>
<div className='grow w-0'>
{isError
? <div className='text-gray-400 text-sm'>{t('share.generation.batchFailed.outputPlaceholder')}</div>

View File

@@ -0,0 +1,58 @@
import type { FC } from 'react'
import type {
ChatItem,
VisionFile,
} from '../../types'
import { useChatContext } from '../context'
import { Markdown } from '@/app/components/base/markdown'
import Thought from '@/app/components/app/chat/thought'
import ImageGallery from '@/app/components/base/image-gallery'
type AgentContentProps = {
item: ChatItem
}
const AgentContent: FC<AgentContentProps> = ({
item,
}) => {
const { allToolIcons } = useChatContext()
const {
annotation,
agent_thoughts,
} = item
const getImgs = (list?: VisionFile[]) => {
if (!list)
return []
return list.filter(file => file.type === 'image' && file.belongs_to === 'assistant')
}
if (annotation?.logAnnotation)
return <Markdown content={annotation?.logAnnotation.content || ''} />
return (
<div>
{agent_thoughts?.map((thought, index) => (
<div key={index}>
{thought.thought && (
<Markdown content={thought.thought} />
)}
{/* {item.tool} */}
{/* perhaps not use tool */}
{!!thought.tool && (
<Thought
thought={thought}
allToolIcons={allToolIcons || {}}
isFinished={!!thought.observation}
/>
)}
{getImgs(thought.message_files).length > 0 && (
<ImageGallery srcs={getImgs(thought.message_files).map(file => file.url)} />
)}
</div>
))}
</div>
)
}
export default AgentContent

View File

@@ -0,0 +1,22 @@
import type { FC } from 'react'
import type { ChatItem } from '../../types'
import { Markdown } from '@/app/components/base/markdown'
type BasicContentProps = {
item: ChatItem
}
const BasicContent: FC<BasicContentProps> = ({
item,
}) => {
const {
annotation,
content,
} = item
if (annotation?.logAnnotation)
return <Markdown content={annotation?.logAnnotation.content || ''} />
return <Markdown content={content} />
}
export default BasicContent

View File

@@ -0,0 +1,99 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import type { ChatItem } from '../../types'
import { useChatContext } from '../context'
import { useCurrentAnswerIsResponsing } from '../hooks'
import Operation from './operation'
import AgentContent from './agent-content'
import BasicContent from './basic-content'
import SuggestedQuestions from './suggested-questions'
import More from './more'
import { AnswerTriangle } from '@/app/components/base/icons/src/vender/solid/general'
import LoadingAnim from '@/app/components/app/chat/loading-anim'
import Citation from '@/app/components/app/chat/citation'
import { EditTitle } from '@/app/components/app/annotation/edit-annotation-modal/edit-item'
type AnswerProps = {
item: ChatItem
}
const Answer: FC<AnswerProps> = ({
item,
}) => {
const { t } = useTranslation()
const {
config,
answerIcon,
} = useChatContext()
const responsing = useCurrentAnswerIsResponsing(item.id)
const {
content,
citation,
agent_thoughts,
more,
annotation,
} = item
const hasAgentThoughts = !!agent_thoughts?.length
return (
<div className='flex mb-2 last:mb-0'>
<div className='shrink-0 relative w-10 h-10'>
{
answerIcon || (
<div className='flex items-center justify-center w-full h-full rounded-full bg-[#d5f5f6] border-[0.5px] border-black/5 text-xl'>
🤖
</div>
)
}
{
responsing && (
<div className='absolute -top-[3px] -left-[3px] pl-[6px] flex items-center w-4 h-4 bg-white rounded-full shadow-xs border-[0.5px] border-gray-50'>
<LoadingAnim type='avatar' />
</div>
)
}
</div>
<div className='chat-answer-container grow w-0 group ml-4'>
<div className='relative pr-10'>
<AnswerTriangle className='absolute -left-2 top-0 w-2 h-3 text-gray-100' />
<div className='group relative inline-block px-4 py-3 max-w-full bg-gray-100 rounded-b-2xl rounded-tr-2xl text-sm text-gray-900'>
<Operation item={item} />
{
responsing && !content && !hasAgentThoughts && (
<div className='flex items-center justify-center w-6 h-5'>
<LoadingAnim type='text' />
</div>
)
}
{
content && !hasAgentThoughts && (
<BasicContent item={item} />
)
}
{
hasAgentThoughts && !content && (
<AgentContent item={item} />
)
}
{
annotation?.id && !annotation?.logAnnotation && (
<EditTitle
className='mt-1'
title={t('appAnnotation.editBy', { author: annotation.authorName })}
/>
)
}
<SuggestedQuestions item={item} />
{
!!citation?.length && config?.retriever_resource?.enabled && !responsing && (
<Citation data={citation} showHitInfo />
)
}
</div>
</div>
<More more={more} />
</div>
</div>
)
}
export default Answer

View File

@@ -0,0 +1,45 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import type { ChatItem } from '../../types'
import { formatNumber } from '@/utils/format'
type MoreProps = {
more: ChatItem['more']
}
const More: FC<MoreProps> = ({
more,
}) => {
const { t } = useTranslation()
return (
<div className='flex items-center mt-1 h-[18px] text-xs text-gray-400 opacity-0 group-hover:opacity-100'>
{
more && (
<>
<div
className='mr-2 shrink-0 truncate max-w-[33.3%]'
title={`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`}
>
{`${t('appLog.detail.timeConsuming')} ${more.latency}${t('appLog.detail.second')}`}
</div>
<div
className='shrink-0 truncate max-w-[33.3%]'
title={`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`}
>
{`${t('appLog.detail.tokenCost')} ${formatNumber(more.tokens)}`}
</div>
<div className='shrink-0 mx-2'>·</div>
<div
className='shrink-0 truncate max-w-[33.3%]'
title={more.time}
>
{more.time}
</div>
</>
)
}
</div>
)
}
export default More

View File

@@ -0,0 +1,54 @@
import type { FC } from 'react'
import type { ChatItem } from '../../types'
import { useCurrentAnswerIsResponsing } from '../hooks'
import { useChatContext } from '../context'
import CopyBtn from '@/app/components/app/chat/copy-btn'
import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication'
import AudioBtn from '@/app/components/base/audio-btn'
type OperationProps = {
item: ChatItem
}
const Operation: FC<OperationProps> = ({
item,
}) => {
const { config } = useChatContext()
const responsing = useCurrentAnswerIsResponsing(item.id)
const {
isOpeningStatement,
content,
annotation,
} = item
return (
<div className='absolute top-[-14px] right-[-14px] flex justify-end gap-1'>
{
!isOpeningStatement && !responsing && (
<CopyBtn
value={content}
className='hidden group-hover:block'
/>
)
}
{!isOpeningStatement && config?.text_to_speech && (
<AudioBtn
value={content}
className='hidden group-hover:block'
/>
)}
{
annotation?.id && (
<div
className='relative box-border flex items-center justify-center h-7 w-7 p-0.5 rounded-lg bg-white cursor-pointer text-[#444CE7] shadow-md'
>
<div className='p-1 rounded-lg bg-[#EEF4FF] '>
<MessageFast className='w-4 h-4' />
</div>
</div>
)
}
</div>
)
}
export default Operation

View File

@@ -0,0 +1,35 @@
import type { FC } from 'react'
import type { ChatItem } from '../../types'
import { useChatContext } from '../context'
type SuggestedQuestionsProps = {
item: ChatItem
}
const SuggestedQuestions: FC<SuggestedQuestionsProps> = ({
item,
}) => {
const { onSend } = useChatContext()
const {
isOpeningStatement,
suggestedQuestions,
} = item
if (!isOpeningStatement || !suggestedQuestions?.length)
return null
return (
<div className='flex flex-wrap'>
{suggestedQuestions.map((question, index) => (
<div
key={index}
className='mt-1 mr-1 max-w-full last:mr-0 shrink-0 py-[5px] leading-[18px] items-center px-4 rounded-lg border border-gray-200 shadow-xs bg-white text-xs font-medium text-primary-600 cursor-pointer'
onClick={() => onSend?.(question)}
>
{question}
</div>),
)}
</div>
)
}
export default SuggestedQuestions

View File

@@ -0,0 +1,220 @@
import type { FC } from 'react'
import {
useRef,
useState,
} from 'react'
import { useContext } from 'use-context-selector'
import Recorder from 'js-audio-recorder'
import { useTranslation } from 'react-i18next'
import Textarea from 'rc-textarea'
import type {
EnableType,
OnSend,
VisionConfig,
} from '../types'
import { TransferMethod } from '../types'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import { ToastContext } from '@/app/components/base/toast'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import VoiceInput from '@/app/components/base/voice-input'
import { Microphone01 } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { Microphone01 as Microphone01Solid } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { Send03 } from '@/app/components/base/icons/src/vender/solid/communication'
import ChatImageUploader from '@/app/components/base/image-uploader/chat-image-uploader'
import ImageList from '@/app/components/base/image-uploader/image-list'
import {
useClipboardUploader,
useDraggableUploader,
useImageFiles,
} from '@/app/components/base/image-uploader/hooks'
type ChatInputProps = {
visionConfig?: VisionConfig
speechToTextConfig?: EnableType
onSend?: OnSend
}
const ChatInput: FC<ChatInputProps> = ({
visionConfig,
speechToTextConfig,
onSend,
}) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const [voiceInputShow, setVoiceInputShow] = useState(false)
const {
files,
onUpload,
onRemove,
onReUpload,
onImageLinkLoadError,
onImageLinkLoadSuccess,
onClear,
} = useImageFiles()
const { onPaste } = useClipboardUploader({ onUpload, visionConfig, files })
const { onDragEnter, onDragLeave, onDragOver, onDrop, isDragActive } = useDraggableUploader<HTMLTextAreaElement>({ onUpload, files, visionConfig })
const isUseInputMethod = useRef(false)
const [query, setQuery] = useState('')
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value
setQuery(value)
}
const handleSend = () => {
if (onSend) {
onSend(query, files.filter(file => file.progress !== -1).map(fileItem => ({
type: 'image',
transfer_method: fileItem.type,
url: fileItem.url,
upload_file_id: fileItem.fileId,
})))
setQuery('')
}
if (!files.find(item => item.type === TransferMethod.local_file && !item.fileId)) {
if (files.length)
onClear()
}
}
const handleKeyUp = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.code === 'Enter') {
e.preventDefault()
// prevent send message when using input method enter
if (!e.shiftKey && !isUseInputMethod.current)
handleSend()
}
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
isUseInputMethod.current = e.nativeEvent.isComposing
if (e.code === 'Enter' && !e.shiftKey) {
setQuery(query.replace(/\n$/, ''))
e.preventDefault()
}
}
const logError = (message: string) => {
notify({ type: 'error', message, duration: 3000 })
}
const handleVoiceInputShow = () => {
(Recorder as any).getPermission().then(() => {
setVoiceInputShow(true)
}, () => {
logError(t('common.voiceInput.notAllow'))
})
}
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const sendBtn = (
<div
className='group flex items-center justify-center w-8 h-8 rounded-lg hover:bg-[#EBF5FF] cursor-pointer'
onClick={handleSend}
>
<Send03
className={`
w-5 h-5 text-gray-300 group-hover:text-primary-600
${!!query.trim() && 'text-primary-600'}
`}
/>
</div>
)
return (
<div
className={`
relative p-[5.5px] max-h-[150px] bg-white border-[1.5px] border-gray-200 rounded-xl overflow-y-auto
${isDragActive && 'border-primary-600'}
`}
>
{
visionConfig?.enabled && (
<>
<div className='absolute bottom-2 left-2 flex items-center'>
<ChatImageUploader
settings={visionConfig}
onUpload={onUpload}
disabled={files.length >= visionConfig.number_limits}
/>
<div className='mx-1 w-[1px] h-4 bg-black/5' />
</div>
<div className='pl-[52px]'>
<ImageList
list={files}
onRemove={onRemove}
onReUpload={onReUpload}
onImageLinkLoadSuccess={onImageLinkLoadSuccess}
onImageLinkLoadError={onImageLinkLoadError}
/>
</div>
</>
)
}
<Textarea
className={`
block w-full px-2 pr-[118px] py-[7px] leading-5 max-h-none text-sm text-gray-700 outline-none appearance-none resize-none
${visionConfig?.enabled && 'pl-12'}
`}
value={query}
onChange={handleContentChange}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
onPaste={onPaste}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
onDrop={onDrop}
autoSize
/>
<div className='absolute bottom-[7px] right-2 flex items-center h-8'>
<div className='flex items-center px-1 h-5 rounded-md bg-gray-100 text-xs font-medium text-gray-500'>
{query.trim().length}
</div>
{
query
? (
<div className='flex justify-center items-center ml-2 w-8 h-8 cursor-pointer hover:bg-gray-100 rounded-lg' onClick={() => setQuery('')}>
<XCircle className='w-4 h-4 text-[#98A2B3]' />
</div>
)
: speechToTextConfig?.enabled
? (
<div
className='group flex justify-center items-center ml-2 w-8 h-8 hover:bg-primary-50 rounded-lg cursor-pointer'
onClick={handleVoiceInputShow}
>
<Microphone01 className='block w-4 h-4 text-gray-500 group-hover:hidden' />
<Microphone01Solid className='hidden w-4 h-4 text-primary-600 group-hover:block' />
</div>
)
: null
}
<div className='mx-2 w-[1px] h-4 bg-black opacity-5' />
{isMobile
? sendBtn
: (
<TooltipPlus
popupContent={
<div>
<div>{t('common.operation.send')} Enter</div>
<div>{t('common.operation.lineBreak')} Shift Enter</div>
</div>
}
>
{sendBtn}
</TooltipPlus>
)}
</div>
{
voiceInputShow && (
<VoiceInput
onCancel={() => setVoiceInputShow(false)}
onConverted={text => setQuery(text)}
/>
)
}
</div>
)
}
export default ChatInput

View File

@@ -0,0 +1,60 @@
'use client'
import type { ReactNode } from 'react'
import { createContext, useContext } from 'use-context-selector'
import type {
ChatConfig,
ChatItem,
OnSend,
} from '../types'
import type { Emoji } from '@/app/components/tools/types'
export type ChatContextValue = {
config?: ChatConfig
isResponsing?: boolean
chatList: ChatItem[]
showPromptLog?: boolean
questionIcon?: ReactNode
answerIcon?: ReactNode
allToolIcons?: Record<string, string | Emoji>
onSend?: OnSend
}
const ChatContext = createContext<ChatContextValue>({
chatList: [],
})
type ChatContextProviderProps = {
children: ReactNode
} & ChatContextValue
export const ChatContextProvider = ({
children,
config,
isResponsing,
chatList,
showPromptLog,
questionIcon,
answerIcon,
allToolIcons,
onSend,
}: ChatContextProviderProps) => {
return (
<ChatContext.Provider value={{
config,
isResponsing,
chatList: chatList || [],
showPromptLog,
questionIcon,
answerIcon,
allToolIcons,
onSend,
}}>
{children}
</ChatContext.Provider>
)
}
export const useChatContext = () => useContext(ChatContext)
export default ChatContext

View File

@@ -0,0 +1,395 @@
import {
useEffect,
useRef,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { produce } from 'immer'
import { useGetState } from 'ahooks'
import dayjs from 'dayjs'
import type {
ChatConfig,
ChatItem,
Inputs,
PromptVariable,
VisionFile,
} from '../types'
import { useChatContext } from './context'
import { TransferMethod } from '@/types/app'
import { useToastContext } from '@/app/components/base/toast'
import { ssePost } from '@/service/base'
import { replaceStringWithValues } from '@/app/components/app/configuration/prompt-value-panel'
type GetAbortController = (abortController: AbortController) => void
type SendCallback = {
onGetConvesationMessages: (conversationId: string, getAbortController: GetAbortController) => Promise<any>
onGetSuggestedQuestions?: (responseItemId: string, getAbortController: GetAbortController) => Promise<any>
}
export const useChat = (
config: ChatConfig,
promptVariablesConfig?: {
inputs: Inputs
promptVariables: PromptVariable[]
},
prevChatList?: ChatItem[],
stopChat?: (taskId: string) => void,
) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const connversationId = useRef('')
const hasStopResponded = useRef(false)
const [isResponsing, setIsResponsing] = useState(false)
const [chatList, setChatList, getChatList] = useGetState<ChatItem[]>(prevChatList || [])
const [taskId, setTaskId] = useState('')
const [suggestedQuestions, setSuggestQuestions] = useState<string[]>([])
const [abortController, setAbortController] = useState<AbortController | null>(null)
const [conversationMessagesAbortController, setConversationMessagesAbortController] = useState<AbortController | null>(null)
const [suggestedQuestionsAbortController, setSuggestedQuestionsAbortController] = useState<AbortController | null>(null)
const getIntroduction = (str: string) => {
return replaceStringWithValues(str, promptVariablesConfig?.promptVariables || [], promptVariablesConfig?.inputs || {})
}
useEffect(() => {
if (config.opening_statement && !chatList.some(item => !item.isAnswer)) {
setChatList([{
id: `${Date.now()}`,
content: getIntroduction(config.opening_statement),
isAnswer: true,
isOpeningStatement: true,
suggestedQuestions: config.suggested_questions,
}])
}
}, [config.opening_statement, config.suggested_questions, promptVariablesConfig?.inputs])
const handleStop = () => {
if (stopChat && taskId)
stopChat(taskId)
if (abortController)
abortController.abort()
if (conversationMessagesAbortController)
conversationMessagesAbortController.abort()
if (suggestedQuestionsAbortController)
suggestedQuestionsAbortController.abort()
}
const handleRestart = () => {
handleStop()
hasStopResponded.current = true
connversationId.current = ''
setIsResponsing(false)
setChatList(config.opening_statement
? [{
id: `${Date.now()}`,
content: config.opening_statement,
isAnswer: true,
isOpeningStatement: true,
suggestedQuestions: config.suggested_questions,
}]
: [])
setSuggestQuestions([])
}
const handleSend = async (
url: string,
data: any,
{
onGetConvesationMessages,
onGetSuggestedQuestions,
}: SendCallback,
) => {
setSuggestQuestions([])
if (isResponsing) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
return false
}
if (promptVariablesConfig?.inputs && promptVariablesConfig?.promptVariables) {
const {
promptVariables,
inputs,
} = promptVariablesConfig
let hasEmptyInput = ''
const requiredVars = promptVariables.filter(({ key, name, required, type }) => {
if (type === 'api')
return false
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res
})
if (requiredVars?.length) {
requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput)
return
if (!inputs[key])
hasEmptyInput = name
})
}
if (hasEmptyInput) {
notify({ type: 'error', message: t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }) })
return false
}
}
const updateCurrentQA = ({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
}: {
responseItem: ChatItem
questionId: string
placeholderAnswerId: string
questionItem: ChatItem
}) => {
// closesure new list is outdated.
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({ ...responseItem })
})
setChatList(newListWithAnswer)
}
const questionId = `question-${Date.now()}`
const questionItem = {
id: questionId,
content: data.query,
isAnswer: false,
message_files: data.files,
}
const placeholderAnswerId = `answer-placeholder-${Date.now()}`
const placeholderAnswerItem = {
id: placeholderAnswerId,
content: '',
isAnswer: true,
}
const newList = [...getChatList(), questionItem, placeholderAnswerItem]
setChatList(newList)
// answer
const responseItem: ChatItem = {
id: `${Date.now()}`,
content: '',
agent_thoughts: [],
message_files: [],
isAnswer: true,
}
setIsResponsing(true)
hasStopResponded.current = false
const bodyParams = {
response_mode: 'streaming',
conversation_id: connversationId.current,
...data,
}
if (bodyParams?.files?.length) {
bodyParams.files = bodyParams.files.map((item: VisionFile) => {
if (item.transfer_method === TransferMethod.local_file) {
return {
...item,
url: '',
}
}
return item
})
}
let isAgentMode = false
let hasSetResponseId = false
ssePost(
url,
{
body: bodyParams,
},
{
getAbortController: (abortController) => {
setAbortController(abortController)
},
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
if (!isAgentMode) {
responseItem.content = responseItem.content + message
}
else {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
if (lastThought)
lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
}
if (messageId && !hasSetResponseId) {
responseItem.id = messageId
hasSetResponseId = true
}
if (isFirstMessage && newConversationId)
connversationId.current = newConversationId
setTaskId(taskId)
if (messageId)
responseItem.id = messageId
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
async onCompleted(hasError?: boolean) {
setIsResponsing(false)
if (hasError)
return
if (connversationId.current) {
const { data }: any = await onGetConvesationMessages(
connversationId.current,
newAbortController => setConversationMessagesAbortController(newAbortController),
)
const newResponseItem = data.find((item: any) => item.id === responseItem.id)
if (!newResponseItem)
return
setChatList(produce(getChatList(), (draft) => {
const index = draft.findIndex(item => item.id === responseItem.id)
if (index !== -1) {
const requestion = draft[index - 1]
draft[index - 1] = {
...requestion,
log: newResponseItem.message,
}
draft[index] = {
...draft[index],
more: {
time: dayjs.unix(newResponseItem.created_at).format('hh:mm A'),
tokens: newResponseItem.answer_tokens + newResponseItem.message_tokens,
latency: newResponseItem.provider_response_latency.toFixed(2),
},
}
}
}))
}
if (config.suggested_questions_after_answer?.enabled && !hasStopResponded.current && onGetSuggestedQuestions) {
const { data }: any = await onGetSuggestedQuestions(
responseItem.id,
newAbortController => setSuggestedQuestionsAbortController(newAbortController),
)
setSuggestQuestions(data)
}
},
onFile(file) {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1]
if (lastThought)
responseItem.agent_thoughts![responseItem.agent_thoughts!.length - 1].message_files = [...(lastThought as any).message_files, file]
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
onThought(thought) {
isAgentMode = true
const response = responseItem as any
if (thought.message_id && !hasSetResponseId)
response.id = thought.message_id
if (response.agent_thoughts.length === 0) {
response.agent_thoughts.push(thought)
}
else {
const lastThought = response.agent_thoughts[response.agent_thoughts.length - 1]
// thought changed but still the same thought, so update.
if (lastThought.id === thought.id) {
thought.thought = lastThought.thought
thought.message_files = lastThought.message_files
responseItem.agent_thoughts![response.agent_thoughts.length - 1] = thought
}
else {
responseItem.agent_thoughts!.push(thought)
}
}
updateCurrentQA({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
})
},
onMessageEnd: (messageEnd) => {
if (messageEnd.metadata?.annotation_reply) {
responseItem.id = messageEnd.id
responseItem.annotation = ({
id: messageEnd.metadata.annotation_reply.id,
authorName: messageEnd.metadata.annotation_reply.account.name,
})
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({
...responseItem,
})
})
setChatList(newListWithAnswer)
return
}
responseItem.citation = messageEnd.metadata?.retriever_resources || []
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({ ...responseItem })
})
setChatList(newListWithAnswer)
},
onMessageReplace: (messageReplace) => {
responseItem.content = messageReplace.answer
},
onError() {
setIsResponsing(false)
// role back placeholder answer
setChatList(produce(getChatList(), (draft) => {
draft.splice(draft.findIndex(item => item.id === placeholderAnswerId), 1)
}))
},
})
return true
}
return {
chatList,
getChatList,
setChatList,
conversationId: connversationId.current,
isResponsing,
setIsResponsing,
handleSend,
suggestedQuestions,
handleRestart,
handleStop,
}
}
export const useCurrentAnswerIsResponsing = (answerId: string) => {
const {
isResponsing,
chatList,
} = useChatContext()
const isLast = answerId === chatList[chatList.length - 1]?.id
return isLast && isResponsing
}

View File

@@ -0,0 +1,129 @@
import type {
FC,
ReactNode,
} from 'react'
import {
memo,
useRef,
} from 'react'
import { useThrottleEffect } from 'ahooks'
import type {
ChatConfig,
ChatItem,
OnSend,
} from '../types'
import Question from './question'
import Answer from './answer'
import ChatInput from './chat-input'
import TryToAsk from './try-to-ask'
import { ChatContextProvider } from './context'
import type { Emoji } from '@/app/components/tools/types'
export type ChatProps = {
config: ChatConfig
onSend?: OnSend
chatList: ChatItem[]
isResponsing: boolean
noChatInput?: boolean
chatContainerclassName?: string
chatFooterClassName?: string
suggestedQuestions?: string[]
showPromptLog?: boolean
questionIcon?: ReactNode
answerIcon?: ReactNode
allToolIcons?: Record<string, string | Emoji>
}
const Chat: FC<ChatProps> = ({
config,
onSend,
chatList,
isResponsing,
noChatInput,
chatContainerclassName,
chatFooterClassName,
suggestedQuestions,
showPromptLog,
questionIcon,
answerIcon,
allToolIcons,
}) => {
const ref = useRef<HTMLDivElement>(null)
const chatFooterRef = useRef<HTMLDivElement>(null)
useThrottleEffect(() => {
if (ref.current)
ref.current.scrollTop = ref.current.scrollHeight
}, [chatList], { wait: 500 })
const hasTryToAsk = config.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend
return (
<ChatContextProvider
config={config}
chatList={chatList}
isResponsing={isResponsing}
showPromptLog={showPromptLog}
questionIcon={questionIcon}
answerIcon={answerIcon}
allToolIcons={allToolIcons}
onSend={onSend}
>
<div className='relative h-full'>
<div
ref={ref}
className={`relative h-full overflow-y-auto ${chatContainerclassName}`}
>
{
chatList.map((item) => {
if (item.isAnswer) {
return (
<Answer
key={item.id}
item={item}
/>
)
}
return (
<Question
key={item.id}
item={item}
/>
)
})
}
{
(hasTryToAsk || !noChatInput) && (
<div
className={`sticky bottom-0 w-full backdrop-blur-[20px] ${chatFooterClassName}`}
ref={chatFooterRef}
style={{
background: 'linear-gradient(0deg, #FFF 0%, rgba(255, 255, 255, 0.40) 100%)',
}}
>
{
hasTryToAsk && (
<TryToAsk
suggestedQuestions={suggestedQuestions}
onSend={onSend}
/>
)
}
{
!noChatInput && (
<ChatInput
visionConfig={config?.file_upload?.image}
speechToTextConfig={config.speech_to_text}
onSend={onSend}
/>
)
}
</div>
)
}
</div>
</div>
</ChatContextProvider>
)
}
export default memo(Chat)

View File

@@ -0,0 +1,62 @@
import type { FC } from 'react'
import { useRef } from 'react'
import type { ChatItem } from '../types'
import { useChatContext } from './context'
import { QuestionTriangle } from '@/app/components/base/icons/src/vender/solid/general'
import { User } from '@/app/components/base/icons/src/public/avatar'
import Log from '@/app/components/app/chat/log'
import { Markdown } from '@/app/components/base/markdown'
import ImageGallery from '@/app/components/base/image-gallery'
type QuestionProps = {
item: ChatItem
}
const Question: FC<QuestionProps> = ({
item,
}) => {
const ref = useRef(null)
const {
showPromptLog,
isResponsing,
questionIcon,
} = useChatContext()
const {
content,
message_files,
} = item
const imgSrcs = message_files?.length ? message_files.map(item => item.url) : []
return (
<div className='flex justify-end mb-2 last:mb-0 pl-10' ref={ref}>
<div className='group relative mr-4'>
<QuestionTriangle className='absolute -right-2 top-0 w-2 h-3 text-[#D1E9FF]/50' />
{
showPromptLog && !isResponsing && (
<Log log={item.log!} containerRef={ref} />
)
}
<div className='px-4 py-3 bg-[#D1E9FF]/50 rounded-b-2xl rounded-tl-2xl text-sm text-gray-900'>
{
!!imgSrcs.length && (
<ImageGallery srcs={imgSrcs} />
)
}
<Markdown content={content} />
</div>
<div className='mt-1 h-[18px]' />
</div>
<div className='shrink-0 w-10 h-10'>
{
questionIcon || (
<div className='w-full h-full rounded-full border-[0.5px] border-black/5'>
<User className='w-full h-full' />
</div>
)
}
</div>
</div>
)
}
export default Question

View File

@@ -0,0 +1,54 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import type { OnSend } from '../types'
import { Star04 } from '@/app/components/base/icons/src/vender/solid/shapes'
import Button from '@/app/components/base/button'
type TryToAskProps = {
suggestedQuestions: string[]
onSend: OnSend
}
const TryToAsk: FC<TryToAskProps> = ({
suggestedQuestions,
onSend,
}) => {
const { t } = useTranslation()
return (
<div>
<div className='flex items-center mb-2.5 py-2'>
<div
className='grow h-[1px]'
style={{
background: 'linear-gradient(270deg, #F3F4F6 0%, rgba(243, 244, 246, 0) 100%)',
}}
/>
<div className='shrink-0 flex items-center px-3 text-gray-500'>
<Star04 className='mr-1 w-2.5 h-2.5' />
<span className='text-xs text-gray-500 font-medium'>{t('appDebug.feature.suggestedQuestionsAfterAnswer.tryToAsk')}</span>
</div>
<div
className='grow h-[1px]'
style={{
background: 'linear-gradient(270deg, rgba(243, 244, 246, 0) 0%, #F3F4F6 100%)',
}}
/>
</div>
<div className='flex flex-wrap'>
{
suggestedQuestions.map((suggestQuestion, index) => (
<Button
key={index}
className='mb-2 mr-2 last:mr-0 px-3 py-[5px] bg-white text-primary-600 text-xs font-medium'
onClick={() => onSend(suggestQuestion)}
>
{suggestQuestion}
</Button>
))
}
</div>
</div>
)
}
export default TryToAsk

View File

@@ -0,0 +1,48 @@
import type {
ModelConfig,
VisionFile,
VisionSettings,
} from '@/types/app'
import type { IChatItem } from '@/app/components/app/chat/type'
export type { VisionFile } from '@/types/app'
export { TransferMethod } from '@/types/app'
export type {
Inputs,
PromptVariable,
} from '@/models/debug'
export type UserInputForm = {
default: string
label: string
required: boolean
variable: string
}
export type UserInputFormTextInput = {
'text-inpput': UserInputForm & {
max_length: number
}
}
export type UserInputFormSelect = {
'select': UserInputForm & {
options: string[]
}
}
export type UserInputFormParagraph = {
'paragraph': UserInputForm
}
export type VisionConfig = VisionSettings
export type EnableType = {
enabled: boolean
}
export type ChatConfig = Omit<ModelConfig, 'model'>
export type ChatItem = IChatItem
export type OnSend = (message: string, files?: VisionFile[]) => void

View File

@@ -0,0 +1,97 @@
import type { FC } from 'react'
import { useState } from 'react'
import { DotsHorizontal } from '@/app/components/base/icons/src/vender/line/general'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
export type Item = {
value: string
text: string
}
type DropdownProps = {
items: Item[]
secondItems?: Item[]
onSelect: (item: Item) => void
renderTrigger?: (open: boolean) => React.ReactNode
}
const Dropdown: FC<DropdownProps> = ({
items,
onSelect,
secondItems,
renderTrigger,
}) => {
const [open, setOpen] = useState(false)
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-end'
>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
{
renderTrigger
? renderTrigger(open)
: (
<div
className={`
flex items-center justify-center w-6 h-6 cursor-pointer rounded-md
${open && 'bg-black/5'}
`}
>
<DotsHorizontal className='w-4 h-4 text-gray-500' />
</div>
)
}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg text-sm text-gray-700'>
{
!!items.length && (
<div className='p-1'>
{
items.map(item => (
<div
key={item.value}
className='flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100'
onClick={() => onSelect(item)}
>
{item.text}
</div>
))
}
</div>
)
}
{
(!!items.length && !!secondItems?.length) && (
<div className='h-[1px] bg-gray-100' />
)
}
{
!!secondItems?.length && (
<div className='p-1'>
{
secondItems.map(item => (
<div
key={item.value}
className='flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100'
onClick={() => onSelect(item)}
>
{item.text}
</div>
))
}
</div>
)
}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default Dropdown

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="message-heart-circle">
<path id="Solid" fill-rule="evenodd" clip-rule="evenodd" d="M8.33334 1.3335C4.83554 1.3335 2.00001 4.16903 2.00001 7.66683C2.00001 8.3735 2.116 9.05444 2.33051 9.69084C2.36824 9.80278 2.39045 9.86902 2.40488 9.91786L2.40961 9.93431L2.40711 9.93952C2.38997 9.97486 2.36451 10.0223 2.31687 10.1105L1.21562 12.1489C1.14736 12.2751 1.07614 12.4069 1.02717 12.5214C0.978485 12.6353 0.89963 12.8442 0.93843 13.0919C0.983911 13.3822 1.15477 13.6378 1.40562 13.7908C1.61963 13.9213 1.84282 13.9283 1.96665 13.9269C2.09123 13.9254 2.24018 13.91 2.38296 13.8952L5.8196 13.54C5.87464 13.5343 5.90342 13.5314 5.92449 13.5297L5.92721 13.5295L5.93545 13.5325C5.96135 13.5418 5.99648 13.5553 6.05711 13.5786C6.76441 13.8511 7.53226 14.0002 8.33334 14.0002C11.8311 14.0002 14.6667 11.1646 14.6667 7.66683C14.6667 4.16903 11.8311 1.3335 8.33334 1.3335ZM5.97972 5.72165C6.73124 5.08746 7.73145 5.27376 8.33126 5.96633C8.93106 5.27376 9.91836 5.09414 10.6828 5.72165C11.4472 6.34916 11.5401 7.41616 10.9499 8.16621C10.5843 8.63089 9.66661 9.4796 9.02123 10.0581C8.78417 10.2706 8.66564 10.3769 8.52339 10.4197C8.40136 10.4564 8.26116 10.4564 8.13913 10.4197C7.99688 10.3769 7.87835 10.2706 7.64128 10.0581C6.9959 9.4796 6.0782 8.63089 5.71257 8.16621C5.1224 7.41616 5.22821 6.35583 5.97972 5.72165Z" fill="#DD2590"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="send-03">
<path id="Solid" d="M18.4385 10.5535C18.6111 10.2043 18.6111 9.79465 18.4385 9.44548C18.2865 9.13803 18.0197 8.97682 17.8815 8.89905C17.7327 8.81532 17.542 8.72955 17.3519 8.64403L3.36539 2.35014C3.17087 2.26257 2.97694 2.17526 2.81335 2.11859C2.66315 2.06656 2.36076 1.97151 2.02596 2.06467C1.64761 2.16994 1.34073 2.4469 1.19734 2.81251C1.07045 3.13604 1.13411 3.44656 1.17051 3.60129C1.21017 3.76983 1.27721 3.9717 1.34445 4.17418L2.69818 8.25278C2.80718 8.58118 2.86168 8.74537 2.96302 8.86678C3.05252 8.97399 3.16752 9.05699 3.29746 9.10816C3.44462 9.1661 3.61762 9.1661 3.96363 9.1661H10.0001C10.4603 9.1661 10.8334 9.53919 10.8334 9.99943C10.8334 10.4597 10.4603 10.8328 10.0001 10.8328H3.97939C3.63425 10.8328 3.46168 10.8328 3.3148 10.8905C3.18508 10.9414 3.07022 11.0241 2.98072 11.1309C2.87937 11.2519 2.82459 11.4155 2.71502 11.7428L1.3504 15.8191C1.28243 16.0221 1.21472 16.2242 1.17455 16.3929C1.13773 16.5476 1.07301 16.8587 1.19956 17.1831C1.34245 17.5493 1.64936 17.827 2.02806 17.9327C2.36342 18.0263 2.6665 17.9309 2.81674 17.8789C2.98066 17.8221 3.17507 17.7346 3.37023 17.6467L17.3518 11.355C17.542 11.2695 17.7327 11.1837 17.8815 11.0999C18.0197 11.0222 18.2865 10.861 18.4385 10.5535Z" fill="#D0D5DD"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,3 @@
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Rectangle 1" d="M1.03647 1.5547C0.59343 0.890144 1.06982 0 1.86852 0H8V12L1.03647 1.5547Z" fill="#F2F4F7"/>
</svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@@ -0,0 +1,6 @@
<svg width="8" height="12" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Rectangle 2">
<path d="M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z" fill="white"/>
<path d="M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z" fill="#D1E9FF" fill-opacity="0.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 346 B

View File

@@ -0,0 +1,5 @@
<svg width="11" height="10" viewBox="0 0 11 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="star-04">
<path id="Solid" d="M5.88897 0.683596C5.82708 0.522683 5.67249 0.416504 5.50008 0.416504C5.32768 0.416504 5.17308 0.522683 5.11119 0.683596L4.27287 2.86321C4.1477 3.18865 4.10837 3.28243 4.05457 3.35809C4.00059 3.43401 3.93426 3.50034 3.85834 3.55433C3.78267 3.60813 3.68889 3.64746 3.36346 3.77263L1.18384 4.61094C1.02293 4.67283 0.916748 4.82743 0.916748 4.99984C0.916748 5.17224 1.02293 5.32684 1.18384 5.38873L3.36346 6.22705C3.68889 6.35221 3.78267 6.39155 3.85834 6.44535C3.93426 6.49933 4.00059 6.56566 4.05457 6.64158C4.10837 6.71724 4.1477 6.81102 4.27287 7.13646L5.11119 9.31608C5.17308 9.47699 5.32768 9.58317 5.50008 9.58317C5.67249 9.58317 5.82709 9.47699 5.88898 9.31608L6.72729 7.13646C6.85246 6.81102 6.89179 6.71724 6.94559 6.64158C6.99957 6.56566 7.06591 6.49933 7.14183 6.44535C7.21749 6.39155 7.31127 6.35221 7.6367 6.22705L9.81632 5.38873C9.97723 5.32684 10.0834 5.17224 10.0834 4.99984C10.0834 4.82743 9.97723 4.67283 9.81632 4.61094L7.6367 3.77263C7.31127 3.64746 7.21749 3.60813 7.14183 3.55433C7.06591 3.50034 6.99957 3.43401 6.94559 3.35809C6.89179 3.28243 6.85246 3.18865 6.72729 2.86321L5.88897 0.683596Z" fill="#667085"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,38 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "message-heart-circle"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Solid",
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M8.33334 1.3335C4.83554 1.3335 2.00001 4.16903 2.00001 7.66683C2.00001 8.3735 2.116 9.05444 2.33051 9.69084C2.36824 9.80278 2.39045 9.86902 2.40488 9.91786L2.40961 9.93431L2.40711 9.93952C2.38997 9.97486 2.36451 10.0223 2.31687 10.1105L1.21562 12.1489C1.14736 12.2751 1.07614 12.4069 1.02717 12.5214C0.978485 12.6353 0.89963 12.8442 0.93843 13.0919C0.983911 13.3822 1.15477 13.6378 1.40562 13.7908C1.61963 13.9213 1.84282 13.9283 1.96665 13.9269C2.09123 13.9254 2.24018 13.91 2.38296 13.8952L5.8196 13.54C5.87464 13.5343 5.90342 13.5314 5.92449 13.5297L5.92721 13.5295L5.93545 13.5325C5.96135 13.5418 5.99648 13.5553 6.05711 13.5786C6.76441 13.8511 7.53226 14.0002 8.33334 14.0002C11.8311 14.0002 14.6667 11.1646 14.6667 7.66683C14.6667 4.16903 11.8311 1.3335 8.33334 1.3335ZM5.97972 5.72165C6.73124 5.08746 7.73145 5.27376 8.33126 5.96633C8.93106 5.27376 9.91836 5.09414 10.6828 5.72165C11.4472 6.34916 11.5401 7.41616 10.9499 8.16621C10.5843 8.63089 9.66661 9.4796 9.02123 10.0581C8.78417 10.2706 8.66564 10.3769 8.52339 10.4197C8.40136 10.4564 8.26116 10.4564 8.13913 10.4197C7.99688 10.3769 7.87835 10.2706 7.64128 10.0581C6.9959 9.4796 6.0782 8.63089 5.71257 8.16621C5.1224 7.41616 5.22821 6.35583 5.97972 5.72165Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "MessageHeartCircle"
}

View File

@@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './MessageHeartCircle.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'MessageHeartCircle'
export default Icon

View File

@@ -0,0 +1,36 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "20",
"height": "20",
"viewBox": "0 0 20 20",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "send-03"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Solid",
"d": "M18.4385 10.5535C18.6111 10.2043 18.6111 9.79465 18.4385 9.44548C18.2865 9.13803 18.0197 8.97682 17.8815 8.89905C17.7327 8.81532 17.542 8.72955 17.3519 8.64403L3.36539 2.35014C3.17087 2.26257 2.97694 2.17526 2.81335 2.11859C2.66315 2.06656 2.36076 1.97151 2.02596 2.06467C1.64761 2.16994 1.34073 2.4469 1.19734 2.81251C1.07045 3.13604 1.13411 3.44656 1.17051 3.60129C1.21017 3.76983 1.27721 3.9717 1.34445 4.17418L2.69818 8.25278C2.80718 8.58118 2.86168 8.74537 2.96302 8.86678C3.05252 8.97399 3.16752 9.05699 3.29746 9.10816C3.44462 9.1661 3.61762 9.1661 3.96363 9.1661H10.0001C10.4603 9.1661 10.8334 9.53919 10.8334 9.99943C10.8334 10.4597 10.4603 10.8328 10.0001 10.8328H3.97939C3.63425 10.8328 3.46168 10.8328 3.3148 10.8905C3.18508 10.9414 3.07022 11.0241 2.98072 11.1309C2.87937 11.2519 2.82459 11.4155 2.71502 11.7428L1.3504 15.8191C1.28243 16.0221 1.21472 16.2242 1.17455 16.3929C1.13773 16.5476 1.07301 16.8587 1.19956 17.1831C1.34245 17.5493 1.64936 17.827 2.02806 17.9327C2.36342 18.0263 2.6665 17.9309 2.81674 17.8789C2.98066 17.8221 3.17507 17.7346 3.37023 17.6467L17.3518 11.355C17.542 11.2695 17.7327 11.1837 17.8815 11.0999C18.0197 11.0222 18.2865 10.861 18.4385 10.5535Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "Send03"
}

View File

@@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Send03.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Send03'
export default Icon

View File

@@ -2,3 +2,5 @@ export { default as AiText } from './AiText'
export { default as CuteRobote } from './CuteRobote'
export { default as MessageDotsCircle } from './MessageDotsCircle'
export { default as MessageFast } from './MessageFast'
export { default as MessageHeartCircle } from './MessageHeartCircle'
export { default as Send03 } from './Send03'

View File

@@ -0,0 +1,27 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "8",
"height": "12",
"viewBox": "0 0 8 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Rectangle 1",
"d": "M1.03647 1.5547C0.59343 0.890144 1.06982 0 1.86852 0H8V12L1.03647 1.5547Z",
"fill": "currentColor"
},
"children": []
}
]
},
"name": "AnswerTriangle"
}

View File

@@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './AnswerTriangle.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'AnswerTriangle'
export default Icon

View File

@@ -0,0 +1,45 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "8",
"height": "12",
"viewBox": "0 0 8 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Rectangle 2"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M6.96353 1.5547C7.40657 0.890144 6.93018 0 6.13148 0H0V12L6.96353 1.5547Z",
"fill": "currentColor",
"fill-opacity": "0.5"
},
"children": []
}
]
}
]
},
"name": "QuestionTriangle"
}

View File

@@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './QuestionTriangle.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'QuestionTriangle'
export default Icon

View File

@@ -1,3 +1,4 @@
export { default as AnswerTriangle } from './AnswerTriangle'
export { default as CheckCircle } from './CheckCircle'
export { default as CheckDone01 } from './CheckDone01'
export { default as Download02 } from './Download02'
@@ -5,6 +6,7 @@ export { default as Edit04 } from './Edit04'
export { default as Eye } from './Eye'
export { default as MessageClockCircle } from './MessageClockCircle'
export { default as PlusCircle } from './PlusCircle'
export { default as QuestionTriangle } from './QuestionTriangle'
export { default as SearchMd } from './SearchMd'
export { default as Target04 } from './Target04'
export { default as Tool03 } from './Tool03'

View File

@@ -0,0 +1,36 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "11",
"height": "10",
"viewBox": "0 0 11 10",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "star-04"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Solid",
"d": "M5.88897 0.683596C5.82708 0.522683 5.67249 0.416504 5.50008 0.416504C5.32768 0.416504 5.17308 0.522683 5.11119 0.683596L4.27287 2.86321C4.1477 3.18865 4.10837 3.28243 4.05457 3.35809C4.00059 3.43401 3.93426 3.50034 3.85834 3.55433C3.78267 3.60813 3.68889 3.64746 3.36346 3.77263L1.18384 4.61094C1.02293 4.67283 0.916748 4.82743 0.916748 4.99984C0.916748 5.17224 1.02293 5.32684 1.18384 5.38873L3.36346 6.22705C3.68889 6.35221 3.78267 6.39155 3.85834 6.44535C3.93426 6.49933 4.00059 6.56566 4.05457 6.64158C4.10837 6.71724 4.1477 6.81102 4.27287 7.13646L5.11119 9.31608C5.17308 9.47699 5.32768 9.58317 5.50008 9.58317C5.67249 9.58317 5.82709 9.47699 5.88898 9.31608L6.72729 7.13646C6.85246 6.81102 6.89179 6.71724 6.94559 6.64158C6.99957 6.56566 7.06591 6.49933 7.14183 6.44535C7.21749 6.39155 7.31127 6.35221 7.6367 6.22705L9.81632 5.38873C9.97723 5.32684 10.0834 5.17224 10.0834 4.99984C10.0834 4.82743 9.97723 4.67283 9.81632 4.61094L7.6367 3.77263C7.31127 3.64746 7.21749 3.60813 7.14183 3.55433C7.06591 3.50034 6.99957 3.43401 6.94559 3.35809C6.89179 3.28243 6.85246 3.18865 6.72729 2.86321L5.88897 0.683596Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "Star04"
}

View File

@@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Star04.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Star04'
export default Icon

View File

@@ -0,0 +1 @@
export { default as Star04 } from './Star04'

View File

@@ -0,0 +1,61 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useToastContext } from '@/app/components/base/toast'
import { ssePost } from '@/service/base'
export const useTextGeneration = () => {
const { t } = useTranslation()
const { notify } = useToastContext()
const [isResponsing, setIsResponsing] = useState(false)
const [completion, setCompletion] = useState('')
const [messageId, setMessageId] = useState<string | null>(null)
const handleSend = async (
url: string,
data: any,
) => {
if (isResponsing) {
notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') })
return false
}
setIsResponsing(true)
setCompletion('')
setMessageId('')
let res: string[] = []
ssePost(
url,
{
body: {
response_mode: 'streaming',
...data,
},
},
{
onData: (data: string, _isFirstMessage: boolean, { messageId }) => {
res.push(data)
setCompletion(res.join(''))
setMessageId(messageId)
},
onMessageReplace: (messageReplace) => {
res = [messageReplace.answer]
setCompletion(res.join(''))
},
onCompleted() {
setIsResponsing(false)
},
onError() {
setIsResponsing(false)
},
})
return true
}
return {
completion,
isResponsing,
setIsResponsing,
handleSend,
messageId,
}
}

View File

@@ -0,0 +1,41 @@
import type {
ModelConfig,
VisionFile,
VisionSettings,
} from '@/types/app'
export type { VisionFile } from '@/types/app'
export { TransferMethod } from '@/types/app'
export type UserInputForm = {
default: string
label: string
required: boolean
variable: string
}
export type UserInputFormTextInput = {
'text-inpput': UserInputForm & {
max_length: number
}
}
export type UserInputFormSelect = {
'select': UserInputForm & {
options: string[]
}
}
export type UserInputFormParagraph = {
'paragraph': UserInputForm
}
export type VisionConfig = VisionSettings
export type EnableType = {
enabled: boolean
}
export type TextGenerationConfig = Omit<ModelConfig, 'model'>
export type OnSend = (message: string, files?: VisionFile[]) => void

View File

@@ -1,4 +1,7 @@
import type { FC } from 'react'
import type {
FC,
ReactNode,
} from 'react'
import { useEffect, useMemo, useState } from 'react'
import useSWR from 'swr'
import cn from 'classnames'
@@ -8,32 +11,25 @@ import type {
FormValue,
ModelParameterRule,
} from '../declarations'
import {
MODEL_STATUS_TEXT,
ModelStatusEnum,
} from '../declarations'
import ModelIcon from '../model-icon'
import ModelName from '../model-name'
import { ModelStatusEnum } from '../declarations'
import ModelSelector from '../model-selector'
import {
useLanguage,
useTextGenerationCurrentProviderAndModelAndModelList,
} from '../hooks'
import { isNullOrUndefined } from '../utils'
import ParameterItem from './parameter-item'
import type { ParameterValue } from './parameter-item'
import Trigger from './trigger'
import type { TriggerProps } from './trigger'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { SlidersH } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import { fetchModelParameterRules } from '@/service/common'
import Loading from '@/app/components/base/loading'
import { useProviderContext } from '@/context/provider-context'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import Radio from '@/app/components/base/radio'
import { TONE_LIST } from '@/config'
import { Brush01 } from '@/app/components/base/icons/src/vender/solid/editor'
@@ -41,15 +37,19 @@ import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAn
import { Target04 } from '@/app/components/base/icons/src/vender/solid/general'
import { Sliders02 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
type ModelParameterModalProps = {
export type ModelParameterModalProps = {
isAdvancedMode: boolean
mode: string
modelId: string
provider: string
setModel: (model: { modelId: string; provider: string; mode?: string; features: string[] }) => void
setModel: (model: { modelId: string; provider: string; mode?: string; features?: string[] }) => void
completionParams: FormValue
onCompletionParamsChange: (newParams: FormValue) => void
debugWithMultipleModel: boolean
onDebugWithMultipleModelChange: () => void
renderTrigger?: (v: TriggerProps) => ReactNode
}
const stopParameerRule: ModelParameterRule = {
default: [],
@@ -78,14 +78,16 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
setModel,
completionParams,
onCompletionParamsChange,
debugWithMultipleModel,
onDebugWithMultipleModelChange,
renderTrigger,
}) => {
const { t } = useTranslation()
const language = useLanguage()
const { hasSettedApiKey, modelProviders } = useProviderContext()
const { hasSettedApiKey } = useProviderContext()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const [open, setOpen] = useState(false)
const { data: parameterRulesData, isLoading } = useSWR(`/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}`, fetchModelParameterRules)
const { data: parameterRulesData, isLoading } = useSWR((provider && modelId) ? `/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}` : null, fetchModelParameterRules)
const {
currentProvider,
currentModel,
@@ -220,71 +222,32 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
onClick={() => setOpen(v => !v)}
className='block'
>
<div
className={`
flex items-center px-2 h-8 rounded-lg border cursor-pointer hover:border-[1.5px]
${disabled ? 'border-[#F79009] bg-[#FFFAEB]' : 'border-[#444CE7] bg-primary-50'}
`}
>
{
currentProvider && (
<ModelIcon
className='mr-1.5 !w-5 !h-5'
provider={currentProvider}
modelName={currentModel?.model}
{
renderTrigger
? renderTrigger({
open,
disabled,
modelDisabled,
hasDeprecated,
currentProvider,
currentModel,
providerName: provider,
modelId,
})
: (
<Trigger
disabled={disabled}
modelDisabled={modelDisabled}
hasDeprecated={hasDeprecated}
currentProvider={currentProvider}
currentModel={currentModel}
providerName={provider}
modelId={modelId}
/>
)
}
{
!currentProvider && (
<ModelIcon
className='mr-1.5 !w-5 !h-5'
provider={modelProviders.find(item => item.provider === provider)}
modelName={modelId}
/>
)
}
{
currentModel && (
<ModelName
className='mr-1.5 text-gray-900'
modelItem={currentModel}
showMode
modeClassName='!text-[#444CE7] !border-[#A4BCFD]'
showFeatures
featuresClassName='!text-[#444CE7] !border-[#A4BCFD]'
/>
)
}
{
!currentModel && (
<div className='mr-1 text-[13px] font-medium text-gray-900 truncate'>
{modelId}
</div>
)
}
{
disabled
? (
<TooltipPlus
popupContent={
hasDeprecated
? t('common.modelProvider.deprecated')
: (modelDisabled && currentModel)
? MODEL_STATUS_TEXT[currentModel.status as string][language]
: ''
}
>
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
</TooltipPlus>
)
: (
<SlidersH className='w-4 h-4 text-indigo-600' />
)
}
</div>
}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<PortalToFollowElemContent className='z-[60]'>
<div className='w-[496px] rounded-xl border border-gray-100 bg-white shadow-xl'>
<div className='flex items-center px-4 h-12 rounded-t-xl border-b border-gray-100 bg-gray-50 text-md font-medium text-gray-900'>
<CubeOutline className='mr-2 w-4 h-4 text-primary-600' />
@@ -296,7 +259,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
{t('common.modelProvider.model')}
</div>
<ModelSelector
defaultModel={{ provider, model: modelId }}
defaultModel={(provider || modelId) ? { provider, model: modelId } : undefined}
modelList={textGenerationModelList}
onSelect={handleChangeModel}
/>
@@ -370,6 +333,17 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
)
}
</div>
<div
className='flex items-center justify-between px-6 h-[50px] bg-gray-50 border-t border-t-gray-100 text-xs font-medium text-primary-600 cursor-pointer rounded-b-xl'
onClick={() => onDebugWithMultipleModelChange()}
>
{
debugWithMultipleModel
? t('appDebug.debugAsSingleModel')
: t('appDebug.debugAsMultipleModel')
}
<ArrowNarrowLeft className='w-3 h-3 rotate-180' />
</div>
</div>
</PortalToFollowElemContent>
</div>

View File

@@ -0,0 +1,107 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import type {
Model,
ModelItem,
ModelProvider,
} from '../declarations'
import { MODEL_STATUS_TEXT } from '../declarations'
import { useLanguage } from '../hooks'
import ModelIcon from '../model-icon'
import ModelName from '../model-name'
import { useProviderContext } from '@/context/provider-context'
import { SlidersH } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import TooltipPlus from '@/app/components/base/tooltip-plus'
export type TriggerProps = {
open?: boolean
disabled?: boolean
currentProvider?: ModelProvider | Model
currentModel?: ModelItem
providerName?: string
modelId?: string
hasDeprecated?: boolean
modelDisabled?: boolean
}
const Trigger: FC<TriggerProps> = ({
disabled,
currentProvider,
currentModel,
providerName,
modelId,
hasDeprecated,
modelDisabled,
}) => {
const { t } = useTranslation()
const language = useLanguage()
const { modelProviders } = useProviderContext()
return (
<div
className={`
flex items-center px-2 h-8 rounded-lg border cursor-pointer hover:border-[1.5px]
${disabled ? 'border-[#F79009] bg-[#FFFAEB]' : 'border-[#444CE7] bg-primary-50'}
`}
>
{
currentProvider && (
<ModelIcon
className='mr-1.5 !w-5 !h-5'
provider={currentProvider}
modelName={currentModel?.model}
/>
)
}
{
!currentProvider && (
<ModelIcon
className='mr-1.5 !w-5 !h-5'
provider={modelProviders.find(item => item.provider === providerName)}
modelName={modelId}
/>
)
}
{
currentModel && (
<ModelName
className='mr-1.5 text-gray-900'
modelItem={currentModel}
showMode
modeClassName='!text-[#444CE7] !border-[#A4BCFD]'
showFeatures
featuresClassName='!text-[#444CE7] !border-[#A4BCFD]'
/>
)
}
{
!currentModel && (
<div className='mr-1 text-[13px] font-medium text-gray-900 truncate'>
{modelId}
</div>
)
}
{
disabled
? (
<TooltipPlus
popupContent={
hasDeprecated
? t('common.modelProvider.deprecated')
: (modelDisabled && currentModel)
? MODEL_STATUS_TEXT[currentModel.status as string][language]
: ''
}
>
<AlertTriangle className='w-4 h-4 text-[#F79009]' />
</TooltipPlus>
)
: (
<SlidersH className='w-4 h-4 text-indigo-600' />
)
}
</div>
)
}
export default Trigger

View File

@@ -1,4 +1,4 @@
import { createContext } from 'use-context-selector'
import { createContext, useContext } from 'use-context-selector'
import { PromptMode } from '@/models/debug'
import type {
AnnotationReplyConfig,
@@ -239,4 +239,6 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({
setVisionConfig: () => { },
})
export const useDebugConfigurationContext = () => useContext(DebugConfigurationContext)
export default DebugConfigurationContext

View File

@@ -352,6 +352,10 @@ const translation = {
score_thresholdTip: 'Used to set the similarity threshold for chunks filtering.',
retrieveChangeTip: 'Modifying the index mode and retrieval mode may affect applications associated with this Knowledge.',
},
debugAsSingleModel: 'Debug as Single Model',
debugAsMultipleModel: 'Debug as Multiple Models',
duplicateModel: 'Dusplicate',
publishAs: 'Publish as',
assistantType: {
name: 'Assistant Type',
chatAssistant: {

View File

@@ -346,6 +346,10 @@ const translation = {
score_thresholdTip: '用于设置文本片段筛选的相似度阈值。',
retrieveChangeTip: '修改索引模式和检索模式可能会影响与该知识库关联的应用程序。',
},
debugAsSingleModel: '单一模型进行调试',
debugAsMultipleModel: '多个模型进行调试',
duplicateModel: '复制模型',
publishAs: '发布为',
assistantType: {
name: '助手类型',
chatAssistant: {

View File

@@ -175,9 +175,15 @@ const baseFetch = <T>(
bodyStringify = true,
needAllResponseContent,
deleteContentType,
getAbortController,
}: IOtherOptions,
): Promise<T> => {
const options: typeof baseOptions & FetchOptionType = Object.assign({}, baseOptions, fetchOptions)
if (getAbortController) {
const abortController = new AbortController()
getAbortController(abortController)
options.signal = abortController.signal
}
if (isPublicAPI) {
const sharedToken = globalThis.location.pathname.split('/').slice(-1)[0]
const accessToken = localStorage.getItem('token') || JSON.stringify({ [sharedToken]: '' })

View File

@@ -45,15 +45,23 @@ export const sendCompletionMessage = async (appId: string, body: Record<string,
}, { onData, onCompleted, onError, onMessageReplace })
}
export const fetchSuggestedQuestions = (appId: string, messageId: string) => {
return get(`apps/${appId}/chat-messages/${messageId}/suggested-questions`)
export const fetchSuggestedQuestions = (appId: string, messageId: string, getAbortController?: any) => {
return get(
`apps/${appId}/chat-messages/${messageId}/suggested-questions`,
{},
{
getAbortController,
},
)
}
export const fetchConvesationMessages = (appId: string, conversation_id: string) => {
export const fetchConvesationMessages = (appId: string, conversation_id: string, getAbortController?: any) => {
return get(`apps/${appId}/chat-messages`, {
params: {
conversation_id,
},
}, {
getAbortController,
})
}