Compare commits
360 Commits
feat/mcp-r
...
refactor/o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2808a8aab1 | ||
|
|
1733a383e1 | ||
|
|
794c5311ef | ||
|
|
35ff0c63f4 | ||
|
|
b9a947d2fd | ||
|
|
57b9ca111a | ||
|
|
709f264ac9 | ||
|
|
9776b4e46c | ||
|
|
250f59234b | ||
|
|
82132d479a | ||
|
|
44e01e5ad4 | ||
|
|
c5ce0b763b | ||
|
|
f5a1d3f8d0 | ||
|
|
d8f1a68e87 | ||
|
|
8054ed7ad8 | ||
|
|
487b5c4d8a | ||
|
|
dedfc79406 | ||
|
|
1f0fd8215a | ||
|
|
e69fd7f22b | ||
|
|
ac4aa33e79 | ||
|
|
6795a044fa | ||
|
|
835bce9079 | ||
|
|
ab9e1bf5a3 | ||
|
|
13093bb821 | ||
|
|
c7c9e1ee44 | ||
|
|
369b367562 | ||
|
|
0081a0740f | ||
|
|
4dfb73c982 | ||
|
|
691656a397 | ||
|
|
d184f7a24b | ||
|
|
1ac746a40e | ||
|
|
d187adb0d3 | ||
|
|
736aef22c4 | ||
|
|
53881c5824 | ||
|
|
d0ed4cc1f2 | ||
|
|
8c6a577cca | ||
|
|
27b6ad75df | ||
|
|
c617a0b51a | ||
|
|
35c15cd02c | ||
|
|
3c8b61e268 | ||
|
|
6f63eefa86 | ||
|
|
4a38f2e8b1 | ||
|
|
f088069fb3 | ||
|
|
7f83f0700b | ||
|
|
296f71ed8a | ||
|
|
f4d7c90126 | ||
|
|
4063c20505 | ||
|
|
50798280db | ||
|
|
39fa080263 | ||
|
|
5c7b81569e | ||
|
|
c021947d52 | ||
|
|
f58d2e2e52 | ||
|
|
75d7ed075b | ||
|
|
b5b577dc79 | ||
|
|
81ac77e988 | ||
|
|
e754b5a863 | ||
|
|
82dd771110 | ||
|
|
8a4a34a946 | ||
|
|
472f2b1a6f | ||
|
|
2420716983 | ||
|
|
fb62ae18b7 | ||
|
|
332ff8b8cf | ||
|
|
aae10322b8 | ||
|
|
a5049d8872 | ||
|
|
bf35228b49 | ||
|
|
e59990d24e | ||
|
|
aee134110b | ||
|
|
4f2eaf4aed | ||
|
|
d19e0de486 | ||
|
|
2f141e4761 | ||
|
|
64c7601cc9 | ||
|
|
0c5a20a2e4 | ||
|
|
917864be1c | ||
|
|
e7e36d7df6 | ||
|
|
0176cf7679 | ||
|
|
96f71f12ec | ||
|
|
7942147ce0 | ||
|
|
b7a6ed6b24 | ||
|
|
790df761f0 | ||
|
|
9215256d68 | ||
|
|
12b9b64ca8 | ||
|
|
74e7979764 | ||
|
|
e0781e1bb0 | ||
|
|
327d0dab7f | ||
|
|
75f513edb0 | ||
|
|
52e2aff005 | ||
|
|
933d26e0f4 | ||
|
|
4fd3300ed0 | ||
|
|
5df8a55f1e | ||
|
|
749a4f4679 | ||
|
|
528524b075 | ||
|
|
4cca5210b9 | ||
|
|
ad67d2558a | ||
|
|
d47c3b1d63 | ||
|
|
741bb94c8b | ||
|
|
46772b4f2a | ||
|
|
8aaf26e420 | ||
|
|
281632f859 | ||
|
|
e4b5e70c34 | ||
|
|
6f635472f3 | ||
|
|
eb4927260a | ||
|
|
a2e628d7e9 | ||
|
|
389dfc08f6 | ||
|
|
7ea7e7134d | ||
|
|
1423163b3a | ||
|
|
f9ed8343fe | ||
|
|
a042892250 | ||
|
|
b67b4c8178 | ||
|
|
4ab6961fcc | ||
|
|
4e7a67df59 | ||
|
|
1e9014b080 | ||
|
|
8ac9344fef | ||
|
|
3250d982fc | ||
|
|
4dcfe276ac | ||
|
|
78126c3d0b | ||
|
|
37ad896f6a | ||
|
|
84a513a6ae | ||
|
|
f538e89976 | ||
|
|
f10f0b21f9 | ||
|
|
49c80620ae | ||
|
|
68aaf9df4a | ||
|
|
b31b48fcaf | ||
|
|
82b244471b | ||
|
|
062cbcc259 | ||
|
|
b50d8b2a23 | ||
|
|
b262410518 | ||
|
|
a34426d431 | ||
|
|
94ed39ab27 | ||
|
|
ed8501961a | ||
|
|
78000816e5 | ||
|
|
5900ff0c6e | ||
|
|
b310ea1407 | ||
|
|
beb44eea61 | ||
|
|
7658b1e79f | ||
|
|
ea1aa6e5a8 | ||
|
|
e823d97e31 | ||
|
|
515d3cd596 | ||
|
|
47366064ca | ||
|
|
61a71a0486 | ||
|
|
e640beb874 | ||
|
|
9386a4d482 | ||
|
|
90e02e64b7 | ||
|
|
08d8f70752 | ||
|
|
695afb6f75 | ||
|
|
471b1fae2d | ||
|
|
9c740f82ad | ||
|
|
ab7fed8907 | ||
|
|
ec68886e4a | ||
|
|
a3bc279c74 | ||
|
|
2e400d3f1c | ||
|
|
ed791a3bb3 | ||
|
|
2a8f819bee | ||
|
|
35280b4b8c | ||
|
|
b93ff89e9e | ||
|
|
dedc591e1c | ||
|
|
5c049911ee | ||
|
|
399f8cbd41 | ||
|
|
b26df0e614 | ||
|
|
8e482a97e5 | ||
|
|
036f61bf12 | ||
|
|
c780552197 | ||
|
|
d366ec5932 | ||
|
|
d35d7029f7 | ||
|
|
2c78f5f906 | ||
|
|
92638d138d | ||
|
|
06b1ae0cb8 | ||
|
|
2dbf7c1c51 | ||
|
|
b08228bdb5 | ||
|
|
d2b6433609 | ||
|
|
3417acafe2 | ||
|
|
f42afe28d7 | ||
|
|
0da9252eb7 | ||
|
|
b4810bb487 | ||
|
|
dc0f9c5f08 | ||
|
|
595fd878a6 | ||
|
|
9d45991181 | ||
|
|
cf2f2fd707 | ||
|
|
d4b1db0407 | ||
|
|
8470e252d6 | ||
|
|
131444ac52 | ||
|
|
ab3083f943 | ||
|
|
1e1d5c4a14 | ||
|
|
c8ab0b9428 | ||
|
|
33ce41704d | ||
|
|
4eb3aa31ee | ||
|
|
d1a9dfa3e6 | ||
|
|
0e5ebcfd00 | ||
|
|
c4e0a6acfe | ||
|
|
2243bb2862 | ||
|
|
1f7d2fa93f | ||
|
|
fb680ce764 | ||
|
|
dc5bc64040 | ||
|
|
1c2ce7e0aa | ||
|
|
a290ee7f39 | ||
|
|
79c697c34d | ||
|
|
76271cbf77 | ||
|
|
9e0ee24fd7 | ||
|
|
5eb2772d53 | ||
|
|
f943f05cb1 | ||
|
|
de5fa5e09c | ||
|
|
8d64bb0316 | ||
|
|
d7eb88f7e2 | ||
|
|
b41e1d712f | ||
|
|
96ce645064 | ||
|
|
1a972ac0e0 | ||
|
|
2e173631a0 | ||
|
|
c457d4a868 | ||
|
|
b74655651d | ||
|
|
f27a481c3c | ||
|
|
4028b26c1d | ||
|
|
011b6f2df1 | ||
|
|
7b3b73d390 | ||
|
|
004d6d8201 | ||
|
|
7cf57adceb | ||
|
|
c258035f6a | ||
|
|
569572bfdc | ||
|
|
b821ac5390 | ||
|
|
534c2ce485 | ||
|
|
bab1a5445c | ||
|
|
742f901052 | ||
|
|
cb12bb5137 | ||
|
|
06b6f2b9d8 | ||
|
|
2c102ed3b4 | ||
|
|
767e22c58d | ||
|
|
dee397f6ac | ||
|
|
a00aba23bd | ||
|
|
de5fb03efb | ||
|
|
a6e58776d2 | ||
|
|
bebe745e69 | ||
|
|
ec8c24a1c2 | ||
|
|
db4fcac768 | ||
|
|
6c71b92d1d | ||
|
|
d470fd8b88 | ||
|
|
99962b740c | ||
|
|
ef4bede062 | ||
|
|
e6e1fb0404 | ||
|
|
e6696def10 | ||
|
|
e5a3363021 | ||
|
|
f6ff436294 | ||
|
|
8a9b633af2 | ||
|
|
0a37146ba8 | ||
|
|
ac3dfcbfbe | ||
|
|
5ac09d5311 | ||
|
|
d4fd8ffdcc | ||
|
|
84274d9d85 | ||
|
|
a72feebead | ||
|
|
e930d3de43 | ||
|
|
ecc9923050 | ||
|
|
e469016775 | ||
|
|
15569387c7 | ||
|
|
4f746842a5 | ||
|
|
aab941d89c | ||
|
|
1b04fd065d | ||
|
|
76b3ba5d7e | ||
|
|
355e5b269d | ||
|
|
d4b0272fe7 | ||
|
|
59bf94b118 | ||
|
|
bd7cd22220 | ||
|
|
f48674b2c7 | ||
|
|
56af6f43c0 | ||
|
|
f83c3e171e | ||
|
|
d397a43806 | ||
|
|
8353f331f1 | ||
|
|
8cc6b08831 | ||
|
|
ffe897d58c | ||
|
|
182ac3bc98 | ||
|
|
c0cca4ae44 | ||
|
|
8981d0a09d | ||
|
|
de44938d9b | ||
|
|
75d5dcf275 | ||
|
|
d8f4825e5e | ||
|
|
c242abd81a | ||
|
|
79c9ed963f | ||
|
|
6079961f44 | ||
|
|
04ef5edea2 | ||
|
|
046ed3edef | ||
|
|
6eb9ab30b0 | ||
|
|
1c27481813 | ||
|
|
a6e19f7757 | ||
|
|
6d89f94335 | ||
|
|
2e07b4ea58 | ||
|
|
bf2f6ddd7f | ||
|
|
c936bddfe7 | ||
|
|
d3028f1dd1 | ||
|
|
0038280fba | ||
|
|
0a94609f78 | ||
|
|
f9f8390540 | ||
|
|
91dd6482ce | ||
|
|
016bbff79f | ||
|
|
32f41391c4 | ||
|
|
78a8ebc777 | ||
|
|
57fd73e51a | ||
|
|
bd448b5108 | ||
|
|
a7d12abd1f | ||
|
|
9e3618bc17 | ||
|
|
8cb270ca86 | ||
|
|
d321cd23ef | ||
|
|
9da3e82c47 | ||
|
|
2931e558b3 | ||
|
|
9a847dc5a3 | ||
|
|
c2a1178dff | ||
|
|
7f114ade4d | ||
|
|
7b633641d1 | ||
|
|
1dacdc3178 | ||
|
|
566dd14fed | ||
|
|
68cd87e069 | ||
|
|
1b57ffeb56 | ||
|
|
5d789ef394 | ||
|
|
820d6a6e96 | ||
|
|
0a67ab4103 | ||
|
|
5cc7390bb6 | ||
|
|
2ce4fabc7d | ||
|
|
7b2570974e | ||
|
|
0ef3852029 | ||
|
|
0dce1c57fc | ||
|
|
190ee76cf1 | ||
|
|
83fea49ed2 | ||
|
|
ccc50dbf2b | ||
|
|
6b503c4080 | ||
|
|
40fe381aa5 | ||
|
|
65c24a2f4b | ||
|
|
b15778b16b | ||
|
|
087e825086 | ||
|
|
3dd2bc1a40 | ||
|
|
9bde833419 | ||
|
|
e15005d1cf | ||
|
|
30e6883333 | ||
|
|
99be38c325 | ||
|
|
df876651b9 | ||
|
|
85bdcdc206 | ||
|
|
2860935e5b | ||
|
|
b219e96544 | ||
|
|
c02f93e6b9 | ||
|
|
72f32e4b8f | ||
|
|
a81f13848c | ||
|
|
81538d5709 | ||
|
|
54449e7130 | ||
|
|
c217a0bf02 | ||
|
|
39257f64b1 | ||
|
|
06dab978f7 | ||
|
|
c3f61533f7 | ||
|
|
8715eb1f41 | ||
|
|
92eb5aed7f | ||
|
|
ff965402cd | ||
|
|
973f26f9dd | ||
|
|
4e3f8a8f76 | ||
|
|
21e40db086 | ||
|
|
92cd012037 | ||
|
|
7cd937888e | ||
|
|
ec491f5f24 | ||
|
|
c0efb46c2b | ||
|
|
aa47fc3ed7 | ||
|
|
c3c9f9b3f2 | ||
|
|
d486b56595 | ||
|
|
4bb5ff8086 | ||
|
|
a748162e67 | ||
|
|
610e7481b3 | ||
|
|
9105e0f5c1 | ||
|
|
a248517520 | ||
|
|
d31f35b16d |
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@@ -1,4 +1,13 @@
|
|||||||
/src/renderer/src/store/ @0xfullex
|
/src/renderer/src/store/ @0xfullex
|
||||||
|
/src/renderer/src/databases/ @0xfullex
|
||||||
/src/main/services/ConfigManager.ts @0xfullex
|
/src/main/services/ConfigManager.ts @0xfullex
|
||||||
/packages/shared/IpcChannel.ts @0xfullex
|
/packages/shared/IpcChannel.ts @0xfullex
|
||||||
/src/main/ipc.ts @0xfullex
|
/src/main/ipc.ts @0xfullex
|
||||||
|
|
||||||
|
/migrations/ @0xfullex
|
||||||
|
/packages/shared/data/ @0xfullex
|
||||||
|
/src/main/data/ @0xfullex
|
||||||
|
/src/renderer/src/data/ @0xfullex
|
||||||
|
|
||||||
|
/packages/ui/ @MyPrototypeWhat
|
||||||
|
|
||||||
|
|||||||
252
.github/issue-checker.yml
vendored
252
.github/issue-checker.yml
vendored
@@ -1,252 +0,0 @@
|
|||||||
default-mode:
|
|
||||||
add:
|
|
||||||
remove: [pull_request_target, issues]
|
|
||||||
|
|
||||||
labels:
|
|
||||||
# <!-- [Ss]kip `LABEL` --> 跳过一个 label
|
|
||||||
# <!-- [Rr]emove `LABEL` --> 去掉一个 label
|
|
||||||
|
|
||||||
# skips and removes
|
|
||||||
- name: skip all
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Aa]ll |)[Ll]abels?'
|
|
||||||
- name: remove all
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Aa]ll |)[Ll]abels?'
|
|
||||||
|
|
||||||
- name: skip kind/bug
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)'
|
|
||||||
- name: remove kind/bug
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)'
|
|
||||||
|
|
||||||
- name: skip kind/enhancement
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)'
|
|
||||||
- name: remove kind/enhancement
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)'
|
|
||||||
|
|
||||||
- name: skip kind/question
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/question(?:`|)'
|
|
||||||
- name: remove kind/question
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/question(?:`|)'
|
|
||||||
|
|
||||||
- name: skip area/Connectivity
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)'
|
|
||||||
- name: remove area/Connectivity
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)'
|
|
||||||
|
|
||||||
- name: skip area/UI/UX
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)'
|
|
||||||
- name: remove area/UI/UX
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)'
|
|
||||||
|
|
||||||
- name: skip kind/documentation
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)'
|
|
||||||
- name: remove kind/documentation
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)'
|
|
||||||
|
|
||||||
- name: skip client:linux
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:linux(?:`|)'
|
|
||||||
- name: remove client:linux
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:linux(?:`|)'
|
|
||||||
|
|
||||||
- name: skip client:mac
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:mac(?:`|)'
|
|
||||||
- name: remove client:mac
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:mac(?:`|)'
|
|
||||||
|
|
||||||
- name: skip client:win
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:win(?:`|)'
|
|
||||||
- name: remove client:win
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:win(?:`|)'
|
|
||||||
|
|
||||||
- name: skip sig/Assistant
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)'
|
|
||||||
- name: remove sig/Assistant
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)'
|
|
||||||
|
|
||||||
- name: skip sig/Data
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)'
|
|
||||||
- name: remove sig/Data
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)'
|
|
||||||
|
|
||||||
- name: skip sig/MCP
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)'
|
|
||||||
- name: remove sig/MCP
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)'
|
|
||||||
|
|
||||||
- name: skip sig/RAG
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)'
|
|
||||||
- name: remove sig/RAG
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)'
|
|
||||||
|
|
||||||
- name: skip lgtm
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)lgtm(?:`|)'
|
|
||||||
- name: remove lgtm
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)lgtm(?:`|)'
|
|
||||||
|
|
||||||
- name: skip License
|
|
||||||
content:
|
|
||||||
regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)License(?:`|)'
|
|
||||||
- name: remove License
|
|
||||||
content:
|
|
||||||
regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)License(?:`|)'
|
|
||||||
|
|
||||||
# `Dev Team`
|
|
||||||
- name: Dev Team
|
|
||||||
mode:
|
|
||||||
add: [pull_request_target, issues]
|
|
||||||
author_association:
|
|
||||||
- COLLABORATOR
|
|
||||||
|
|
||||||
# Area labels
|
|
||||||
- name: area/Connectivity
|
|
||||||
content: area/Connectivity
|
|
||||||
regexes: '代理|[Pp]roxy'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip area/Connectivity
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove area/Connectivity
|
|
||||||
|
|
||||||
- name: area/UI/UX
|
|
||||||
content: area/UI/UX
|
|
||||||
regexes: '界面|[Uu][Ii]|重叠|按钮|图标|组件|渲染|菜单|栏目|头像|主题|样式|[Cc][Ss][Ss]'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip area/UI/UX
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove area/UI/UX
|
|
||||||
|
|
||||||
# Kind labels
|
|
||||||
- name: kind/documentation
|
|
||||||
content: kind/documentation
|
|
||||||
regexes: '文档|教程|[Dd]oc(s|umentation)|[Rr]eadme'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip kind/documentation
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove kind/documentation
|
|
||||||
|
|
||||||
# Client labels
|
|
||||||
- name: client:linux
|
|
||||||
content: client:linux
|
|
||||||
regexes: '(?:[Ll]inux|[Uu]buntu|[Dd]ebian)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip client:linux
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove client:linux
|
|
||||||
|
|
||||||
- name: client:mac
|
|
||||||
content: client:mac
|
|
||||||
regexes: '(?:[Mm]ac|[Mm]acOS|[Oo]SX)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip client:mac
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove client:mac
|
|
||||||
|
|
||||||
- name: client:win
|
|
||||||
content: client:win
|
|
||||||
regexes: '(?:[Ww]in|[Ww]indows)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip client:win
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove client:win
|
|
||||||
|
|
||||||
# SIG labels
|
|
||||||
- name: sig/Assistant
|
|
||||||
content: sig/Assistant
|
|
||||||
regexes: '快捷助手|[Aa]ssistant'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip sig/Assistant
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove sig/Assistant
|
|
||||||
|
|
||||||
- name: sig/Data
|
|
||||||
content: sig/Data
|
|
||||||
regexes: '[Ww]ebdav|坚果云|备份|同步|数据|Obsidian|Notion|Joplin|思源'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip sig/Data
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove sig/Data
|
|
||||||
|
|
||||||
- name: sig/MCP
|
|
||||||
content: sig/MCP
|
|
||||||
regexes: '[Mm][Cc][Pp]'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip sig/MCP
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove sig/MCP
|
|
||||||
|
|
||||||
- name: sig/RAG
|
|
||||||
content: sig/RAG
|
|
||||||
regexes: '知识库|[Rr][Aa][Gg]'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip sig/RAG
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove sig/RAG
|
|
||||||
|
|
||||||
# Other labels
|
|
||||||
- name: lgtm
|
|
||||||
content: lgtm
|
|
||||||
regexes: '(?:[Ll][Gg][Tt][Mm]|[Ll]ooks [Gg]ood [Tt]o [Mm]e)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip lgtm
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove lgtm
|
|
||||||
|
|
||||||
- name: License
|
|
||||||
content: License
|
|
||||||
regexes: '(?:[Ll]icense|[Cc]opyright|[Mm][Ii][Tt]|[Aa]pache)'
|
|
||||||
skip-if:
|
|
||||||
- skip all
|
|
||||||
- skip License
|
|
||||||
remove-if:
|
|
||||||
- remove all
|
|
||||||
- remove License
|
|
||||||
12
.github/pull_request_template.md
vendored
12
.github/pull_request_template.md
vendored
@@ -3,6 +3,18 @@
|
|||||||
1. Consider creating this PR as draft: https://github.com/CherryHQ/cherry-studio/blob/main/CONTRIBUTING.md
|
1. Consider creating this PR as draft: https://github.com/CherryHQ/cherry-studio/blob/main/CONTRIBUTING.md
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
⚠️ Important: Redux/IndexedDB Data-Changing Feature PRs Temporarily On Hold ⚠️
|
||||||
|
|
||||||
|
Please note: For our current development cycle, we are not accepting feature Pull Requests that introduce changes to Redux data models or IndexedDB schemas.
|
||||||
|
|
||||||
|
While we value your contributions, PRs of this nature will be blocked without merge. We welcome all other contributions (bug fixes, perf enhancements, docs, etc.). Thank you!
|
||||||
|
|
||||||
|
Once version 2.0.0 is released, we will resume reviewing feature PRs.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
### What this PR does
|
### What this PR does
|
||||||
|
|
||||||
Before this PR:
|
Before this PR:
|
||||||
|
|||||||
14
.github/workflows/auto-i18n.yml
vendored
14
.github/workflows/auto-i18n.yml
vendored
@@ -1,9 +1,10 @@
|
|||||||
name: Auto I18N
|
name: Auto I18N
|
||||||
|
|
||||||
env:
|
env:
|
||||||
API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
|
TRANSLATION_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
|
||||||
MODEL: ${{ vars.AUTO_I18N_MODEL || 'deepseek/deepseek-v3.1'}}
|
TRANSLATION_MODEL: ${{ vars.AUTO_I18N_MODEL || 'deepseek/deepseek-v3.1'}}
|
||||||
BASE_URL: ${{ vars.AUTO_I18N_BASE_URL || 'https://api.ppinfra.com/openai'}}
|
TRANSLATION_BASE_URL: ${{ vars.AUTO_I18N_BASE_URL || 'https://api.ppinfra.com/openai'}}
|
||||||
|
TRANSLATION_BASE_LOCALE: ${{ vars.AUTO_I18N_BASE_LOCALE || 'en-us'}}
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -13,7 +14,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
auto-i18n:
|
auto-i18n:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.pull_request.head.repo.full_name == 'CherryHQ/cherry-studio'
|
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == 'CherryHQ/cherry-studio'
|
||||||
name: Auto I18N
|
name: Auto I18N
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -29,20 +30,21 @@ jobs:
|
|||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
package-manager-cache: false
|
||||||
|
|
||||||
- name: 📦 Install dependencies in isolated directory
|
- name: 📦 Install dependencies in isolated directory
|
||||||
run: |
|
run: |
|
||||||
# 在临时目录安装依赖
|
# 在临时目录安装依赖
|
||||||
mkdir -p /tmp/translation-deps
|
mkdir -p /tmp/translation-deps
|
||||||
cd /tmp/translation-deps
|
cd /tmp/translation-deps
|
||||||
echo '{"dependencies": {"openai": "^5.12.2", "cli-progress": "^3.12.0", "tsx": "^4.20.3", "@biomejs/biome": "2.2.4"}}' > package.json
|
echo '{"dependencies": {"@cherrystudio/openai": "^6.5.0", "cli-progress": "^3.12.0", "tsx": "^4.20.3", "@biomejs/biome": "2.2.4"}}' > package.json
|
||||||
npm install --no-package-lock
|
npm install --no-package-lock
|
||||||
|
|
||||||
# 设置 NODE_PATH 让项目能找到这些依赖
|
# 设置 NODE_PATH 让项目能找到这些依赖
|
||||||
echo "NODE_PATH=/tmp/translation-deps/node_modules" >> $GITHUB_ENV
|
echo "NODE_PATH=/tmp/translation-deps/node_modules" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: 🏃♀️ Translate
|
- name: 🏃♀️ Translate
|
||||||
run: npx tsx scripts/auto-translate-i18n.ts
|
run: npx tsx scripts/sync-i18n.ts && npx tsx scripts/auto-translate-i18n.ts
|
||||||
|
|
||||||
- name: 🔍 Format
|
- name: 🔍 Format
|
||||||
run: cd /tmp/translation-deps && npx biome format --config-path /home/runner/work/cherry-studio/cherry-studio/biome.jsonc --write /home/runner/work/cherry-studio/cherry-studio/src/renderer/src/i18n/
|
run: cd /tmp/translation-deps && npx biome format --config-path /home/runner/work/cherry-studio/cherry-studio/biome.jsonc --write /home/runner/work/cherry-studio/cherry-studio/src/renderer/src/i18n/
|
||||||
|
|||||||
1
.github/workflows/delete-branch.yml
vendored
1
.github/workflows/delete-branch.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Delete merged branch
|
- name: Delete merged branch
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.git.deleteRef({
|
github.rest.git.deleteRef({
|
||||||
|
|||||||
187
.github/workflows/github-issue-tracker.yml
vendored
Normal file
187
.github/workflows/github-issue-tracker.yml
vendored
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
name: GitHub Issue Tracker with Feishu Notification
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
schedule:
|
||||||
|
# Run every day at 8:30 Beijing Time (00:30 UTC)
|
||||||
|
- cron: '30 0 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
process-new-issue:
|
||||||
|
if: github.event_name == 'issues'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check Beijing Time
|
||||||
|
id: check_time
|
||||||
|
run: |
|
||||||
|
# Get current time in Beijing timezone (UTC+8)
|
||||||
|
BEIJING_HOUR=$(TZ='Asia/Shanghai' date +%H)
|
||||||
|
BEIJING_MINUTE=$(TZ='Asia/Shanghai' date +%M)
|
||||||
|
|
||||||
|
echo "Beijing Time: ${BEIJING_HOUR}:${BEIJING_MINUTE}"
|
||||||
|
|
||||||
|
# Check if time is between 00:00 and 08:30
|
||||||
|
if [ $BEIJING_HOUR -lt 8 ] || ([ $BEIJING_HOUR -eq 8 ] && [ $BEIJING_MINUTE -le 30 ]); then
|
||||||
|
echo "should_delay=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "⏰ Issue created during quiet hours (00:00-08:30 Beijing Time)"
|
||||||
|
echo "Will schedule notification for 08:30"
|
||||||
|
else
|
||||||
|
echo "should_delay=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "✅ Issue created during active hours, will notify immediately"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Add pending label if in quiet hours
|
||||||
|
if: steps.check_time.outputs.should_delay == 'true'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
labels: ['pending-feishu-notification']
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
if: steps.check_time.outputs.should_delay == 'false'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Process issue with Claude
|
||||||
|
if: steps.check_time.outputs.should_delay == 'false'
|
||||||
|
uses: anthropics/claude-code-action@main
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
allowed_non_write_users: "*"
|
||||||
|
anthropic_api_key: ${{ secrets.CLAUDE_TRANSLATOR_APIKEY }}
|
||||||
|
claude_args: "--allowed-tools Bash(gh issue:*),Bash(node scripts/feishu-notify.js)"
|
||||||
|
prompt: |
|
||||||
|
你是一个GitHub Issue自动化处理助手。请完成以下任务:
|
||||||
|
|
||||||
|
## 当前Issue信息
|
||||||
|
- Issue编号:#${{ github.event.issue.number }}
|
||||||
|
- 标题:${{ github.event.issue.title }}
|
||||||
|
- 作者:${{ github.event.issue.user.login }}
|
||||||
|
- URL:${{ github.event.issue.html_url }}
|
||||||
|
- 内容:${{ github.event.issue.body }}
|
||||||
|
- 标签:${{ join(github.event.issue.labels.*.name, ', ') }}
|
||||||
|
|
||||||
|
## 任务步骤
|
||||||
|
|
||||||
|
1. **分析并总结issue**
|
||||||
|
用中文(简体)提供简洁的总结(2-3句话),包括:
|
||||||
|
- 问题的主要内容
|
||||||
|
- 核心诉求
|
||||||
|
- 重要的技术细节
|
||||||
|
|
||||||
|
2. **发送飞书通知**
|
||||||
|
使用以下命令发送飞书通知(注意:ISSUE_SUMMARY需要用引号包裹):
|
||||||
|
```bash
|
||||||
|
ISSUE_URL="${{ github.event.issue.html_url }}" \
|
||||||
|
ISSUE_NUMBER="${{ github.event.issue.number }}" \
|
||||||
|
ISSUE_TITLE="${{ github.event.issue.title }}" \
|
||||||
|
ISSUE_AUTHOR="${{ github.event.issue.user.login }}" \
|
||||||
|
ISSUE_LABELS="${{ join(github.event.issue.labels.*.name, ',') }}" \
|
||||||
|
ISSUE_SUMMARY="<你生成的中文总结>" \
|
||||||
|
node scripts/feishu-notify.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
- 总结必须使用简体中文
|
||||||
|
- ISSUE_SUMMARY 在传递给 node 命令时需要正确转义特殊字符
|
||||||
|
- 如果issue内容为空,也要提供一个简短的说明
|
||||||
|
|
||||||
|
请开始执行任务!
|
||||||
|
env:
|
||||||
|
ANTHROPIC_BASE_URL: ${{ secrets.CLAUDE_TRANSLATOR_BASEURL }}
|
||||||
|
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
|
||||||
|
FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }}
|
||||||
|
|
||||||
|
process-pending-issues:
|
||||||
|
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Process pending issues with Claude
|
||||||
|
uses: anthropics/claude-code-action@main
|
||||||
|
with:
|
||||||
|
anthropic_api_key: ${{ secrets.CLAUDE_TRANSLATOR_APIKEY }}
|
||||||
|
allowed_non_write_users: "*"
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
claude_args: "--allowed-tools Bash(gh issue:*),Bash(gh api:*),Bash(node scripts/feishu-notify.js)"
|
||||||
|
prompt: |
|
||||||
|
你是一个GitHub Issue自动化处理助手。请完成以下任务:
|
||||||
|
|
||||||
|
## 任务说明
|
||||||
|
处理所有待发送飞书通知的GitHub Issues(标记为 `pending-feishu-notification` 的issues)
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
1. **获取待处理的issues**
|
||||||
|
使用以下命令获取所有带 `pending-feishu-notification` 标签的issues:
|
||||||
|
```bash
|
||||||
|
gh api repos/${{ github.repository }}/issues?labels=pending-feishu-notification&state=open
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **总结每个issue**
|
||||||
|
对于每个找到的issue,用中文提供简洁的总结(2-3句话),包括:
|
||||||
|
- 问题的主要内容
|
||||||
|
- 核心诉求
|
||||||
|
- 重要的技术细节
|
||||||
|
|
||||||
|
3. **发送飞书通知**
|
||||||
|
对于每个issue,使用以下命令发送飞书通知:
|
||||||
|
```bash
|
||||||
|
ISSUE_URL="<issue的html_url>" \
|
||||||
|
ISSUE_NUMBER="<issue编号>" \
|
||||||
|
ISSUE_TITLE="<issue标题>" \
|
||||||
|
ISSUE_AUTHOR="<issue作者>" \
|
||||||
|
ISSUE_LABELS="<逗号分隔的标签列表,排除pending-feishu-notification>" \
|
||||||
|
ISSUE_SUMMARY="<你生成的中文总结>" \
|
||||||
|
node scripts/feishu-notify.js
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **移除标签**
|
||||||
|
成功发送后,使用以下命令移除 `pending-feishu-notification` 标签:
|
||||||
|
```bash
|
||||||
|
gh api -X DELETE repos/${{ github.repository }}/issues/<issue编号>/labels/pending-feishu-notification
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
- Repository: ${{ github.repository }}
|
||||||
|
- Feishu webhook URL和密钥已在环境变量中配置好
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
- 如果没有待处理的issues,输出提示信息后直接结束
|
||||||
|
- 处理多个issues时,每个issue之间等待2-3秒,避免API限流
|
||||||
|
- 如果某个issue处理失败,继续处理下一个,不要中断整个流程
|
||||||
|
- 所有总结必须使用中文(简体中文)
|
||||||
|
|
||||||
|
请开始执行任务!
|
||||||
|
env:
|
||||||
|
ANTHROPIC_BASE_URL: ${{ secrets.CLAUDE_TRANSLATOR_BASEURL }}
|
||||||
|
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
|
||||||
|
FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }}
|
||||||
25
.github/workflows/issue-checker.yml
vendored
25
.github/workflows/issue-checker.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: 'Issue Checker'
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited]
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, edited]
|
|
||||||
issue_comment:
|
|
||||||
types: [created, edited]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
triage:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: MaaAssistantArknights/issue-checker@v1.14
|
|
||||||
with:
|
|
||||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
|
||||||
configuration-path: .github/issue-checker.yml
|
|
||||||
not-before: 2022-08-05T00:00:00Z
|
|
||||||
include-title: 1
|
|
||||||
2
.github/workflows/pr-ci.yml
vendored
2
.github/workflows/pr-ci.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
PRCI: true
|
PRCI: true
|
||||||
if: github.event.pull_request.draft == false
|
if: github.event.pull_request.draft == false || github.head_ref == 'v2'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"files": ["src/main/**", "resources/scripts/**", "scripts/**", "playwright.config.ts", "electron.vite.config.ts"]
|
"files": ["src/main/**", "resources/scripts/**", "scripts/**", "playwright.config.ts", "electron.vite.config.ts", "packages/ui/scripts/**"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
"src/renderer/**/*.{ts,tsx}",
|
"src/renderer/**/*.{ts,tsx}",
|
||||||
"packages/aiCore/**",
|
"packages/aiCore/**",
|
||||||
"packages/extension-table-plus/**",
|
"packages/extension-table-plus/**",
|
||||||
|
"packages/ui/**",
|
||||||
"resources/js/**"
|
"resources/js/**"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -140,7 +141,7 @@
|
|||||||
"typescript/await-thenable": "warn",
|
"typescript/await-thenable": "warn",
|
||||||
// "typescript/ban-ts-comment": "error",
|
// "typescript/ban-ts-comment": "error",
|
||||||
"typescript/no-array-constructor": "error",
|
"typescript/no-array-constructor": "error",
|
||||||
// "typescript/consistent-type-imports": "error",
|
"typescript/consistent-type-imports": "error",
|
||||||
"typescript/no-array-delete": "warn",
|
"typescript/no-array-delete": "warn",
|
||||||
"typescript/no-base-to-string": "warn",
|
"typescript/no-base-to-string": "warn",
|
||||||
"typescript/no-duplicate-enum-values": "error",
|
"typescript/no-duplicate-enum-values": "error",
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -31,7 +31,8 @@
|
|||||||
},
|
},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.css": "tailwindcss"
|
"*.css": "tailwindcss",
|
||||||
|
".oxlintrc.json": "jsonc"
|
||||||
},
|
},
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
// "i18n-ally.displayLanguage": "zh-cn", // 界面显示语言
|
// "i18n-ally.displayLanguage": "zh-cn", // 界面显示语言
|
||||||
|
|||||||
131
.yarn/patches/@ai-sdk-huggingface-npm-0.0.4-8080836bc1.patch
vendored
Normal file
131
.yarn/patches/@ai-sdk-huggingface-npm-0.0.4-8080836bc1.patch
vendored
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||||
|
index b3f018730a93639aad7c203f15fb1aeb766c73f4..ade2a43d66e9184799d072153df61ef7be4ea110 100644
|
||||||
|
--- a/dist/index.mjs
|
||||||
|
+++ b/dist/index.mjs
|
||||||
|
@@ -296,7 +296,14 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||||
|
metadata: huggingfaceOptions == null ? void 0 : huggingfaceOptions.metadata,
|
||||||
|
instructions: huggingfaceOptions == null ? void 0 : huggingfaceOptions.instructions,
|
||||||
|
...preparedTools && { tools: preparedTools },
|
||||||
|
- ...preparedToolChoice && { tool_choice: preparedToolChoice }
|
||||||
|
+ ...preparedToolChoice && { tool_choice: preparedToolChoice },
|
||||||
|
+ ...(huggingfaceOptions?.reasoningEffort != null && {
|
||||||
|
+ reasoning: {
|
||||||
|
+ ...(huggingfaceOptions?.reasoningEffort != null && {
|
||||||
|
+ effort: huggingfaceOptions.reasoningEffort,
|
||||||
|
+ }),
|
||||||
|
+ },
|
||||||
|
+ }),
|
||||||
|
};
|
||||||
|
return { args: baseArgs, warnings };
|
||||||
|
}
|
||||||
|
@@ -365,6 +372,20 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
+ case 'reasoning': {
|
||||||
|
+ for (const contentPart of part.content) {
|
||||||
|
+ content.push({
|
||||||
|
+ type: 'reasoning',
|
||||||
|
+ text: contentPart.text,
|
||||||
|
+ providerMetadata: {
|
||||||
|
+ huggingface: {
|
||||||
|
+ itemId: part.id,
|
||||||
|
+ },
|
||||||
|
+ },
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
case "mcp_call": {
|
||||||
|
content.push({
|
||||||
|
type: "tool-call",
|
||||||
|
@@ -519,6 +540,11 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||||
|
id: value.item.call_id,
|
||||||
|
toolName: value.item.name
|
||||||
|
});
|
||||||
|
+ } else if (value.item.type === 'reasoning') {
|
||||||
|
+ controller.enqueue({
|
||||||
|
+ type: 'reasoning-start',
|
||||||
|
+ id: value.item.id,
|
||||||
|
+ });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@@ -570,6 +596,22 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
+ if (isReasoningDeltaChunk(value)) {
|
||||||
|
+ controller.enqueue({
|
||||||
|
+ type: 'reasoning-delta',
|
||||||
|
+ id: value.item_id,
|
||||||
|
+ delta: value.delta,
|
||||||
|
+ });
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (isReasoningEndChunk(value)) {
|
||||||
|
+ controller.enqueue({
|
||||||
|
+ type: 'reasoning-end',
|
||||||
|
+ id: value.item_id,
|
||||||
|
+ });
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
},
|
||||||
|
flush(controller) {
|
||||||
|
controller.enqueue({
|
||||||
|
@@ -593,7 +635,8 @@ var HuggingFaceResponsesLanguageModel = class {
|
||||||
|
var huggingfaceResponsesProviderOptionsSchema = z2.object({
|
||||||
|
metadata: z2.record(z2.string(), z2.string()).optional(),
|
||||||
|
instructions: z2.string().optional(),
|
||||||
|
- strictJsonSchema: z2.boolean().optional()
|
||||||
|
+ strictJsonSchema: z2.boolean().optional(),
|
||||||
|
+ reasoningEffort: z2.string().optional(),
|
||||||
|
});
|
||||||
|
var huggingfaceResponsesResponseSchema = z2.object({
|
||||||
|
id: z2.string(),
|
||||||
|
@@ -727,12 +770,31 @@ var responseCreatedChunkSchema = z2.object({
|
||||||
|
model: z2.string()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
+var reasoningTextDeltaChunkSchema = z2.object({
|
||||||
|
+ type: z2.literal('response.reasoning_text.delta'),
|
||||||
|
+ item_id: z2.string(),
|
||||||
|
+ output_index: z2.number(),
|
||||||
|
+ content_index: z2.number(),
|
||||||
|
+ delta: z2.string(),
|
||||||
|
+ sequence_number: z2.number(),
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+var reasoningTextEndChunkSchema = z2.object({
|
||||||
|
+ type: z2.literal('response.reasoning_text.done'),
|
||||||
|
+ item_id: z2.string(),
|
||||||
|
+ output_index: z2.number(),
|
||||||
|
+ content_index: z2.number(),
|
||||||
|
+ text: z2.string(),
|
||||||
|
+ sequence_number: z2.number(),
|
||||||
|
+});
|
||||||
|
var huggingfaceResponsesChunkSchema = z2.union([
|
||||||
|
responseOutputItemAddedSchema,
|
||||||
|
responseOutputItemDoneSchema,
|
||||||
|
textDeltaChunkSchema,
|
||||||
|
responseCompletedChunkSchema,
|
||||||
|
responseCreatedChunkSchema,
|
||||||
|
+ reasoningTextDeltaChunkSchema,
|
||||||
|
+ reasoningTextEndChunkSchema,
|
||||||
|
z2.object({ type: z2.string() }).loose()
|
||||||
|
// fallback for unknown chunks
|
||||||
|
]);
|
||||||
|
@@ -751,6 +813,12 @@ function isResponseCompletedChunk(chunk) {
|
||||||
|
function isResponseCreatedChunk(chunk) {
|
||||||
|
return chunk.type === "response.created";
|
||||||
|
}
|
||||||
|
+function isReasoningDeltaChunk(chunk) {
|
||||||
|
+ return chunk.type === 'response.reasoning_text.delta';
|
||||||
|
+}
|
||||||
|
+function isReasoningEndChunk(chunk) {
|
||||||
|
+ return chunk.type === 'response.reasoning_text.done';
|
||||||
|
+}
|
||||||
|
|
||||||
|
// src/huggingface-provider.ts
|
||||||
|
function createHuggingFace(options = {}) {
|
||||||
BIN
.yarn/patches/openai-npm-5.12.2-30b075401c.patch
vendored
BIN
.yarn/patches/openai-npm-5.12.2-30b075401c.patch
vendored
Binary file not shown.
113
CLAUDE.md
113
CLAUDE.md
@@ -35,14 +35,113 @@ This file provides guidance to AI coding assistants when working with code in th
|
|||||||
- **Renderer Process** (`src/renderer/`): React UI with Redux state management
|
- **Renderer Process** (`src/renderer/`): React UI with Redux state management
|
||||||
- **Preload Scripts** (`src/preload/`): Secure IPC bridge
|
- **Preload Scripts** (`src/preload/`): Secure IPC bridge
|
||||||
|
|
||||||
### Key Components
|
### Key Architectural Components
|
||||||
- **AI Core** (`src/renderer/src/aiCore/`): Middleware pipeline for multiple AI providers.
|
|
||||||
- **Services** (`src/main/services/`): MCPService, KnowledgeService, WindowService, etc.
|
#### Main Process Services (`src/main/services/`)
|
||||||
- **Build System**: Electron-Vite with experimental rolldown-vite, yarn workspaces.
|
|
||||||
- **State Management**: Redux Toolkit (`src/renderer/src/store/`) for predictable state.
|
- **MCPService**: Model Context Protocol server management
|
||||||
- **UI Components**: HeroUI (`@heroui/*`) for all new UI elements.
|
- **KnowledgeService**: Document processing and knowledge base management
|
||||||
|
- **FileStorage/S3Storage/WebDav**: Multiple storage backends
|
||||||
|
- **WindowService**: Multi-window management (main, mini, selection windows)
|
||||||
|
- **ProxyManager**: Network proxy handling
|
||||||
|
- **SearchService**: Full-text search capabilities
|
||||||
|
|
||||||
|
#### AI Core (`src/renderer/src/aiCore/`)
|
||||||
|
|
||||||
|
- **Middleware System**: Composable pipeline for AI request processing
|
||||||
|
- **Client Factory**: Supports multiple AI providers (OpenAI, Anthropic, Gemini, etc.)
|
||||||
|
- **Stream Processing**: Real-time response handling
|
||||||
|
|
||||||
|
#### Data Management
|
||||||
|
|
||||||
|
- **Cache System**: Three-layer caching (memory/shared/persist) with React hooks integration
|
||||||
|
- **Preferences**: Type-safe configuration management with multi-window synchronization
|
||||||
|
- **User Data**: SQLite-based storage with Drizzle ORM for business data
|
||||||
|
|
||||||
|
#### Knowledge Management
|
||||||
|
|
||||||
|
- **Embeddings**: Vector search with multiple providers (OpenAI, Voyage, etc.)
|
||||||
|
- **OCR**: Document text extraction (system OCR, Doc2x, Mineru)
|
||||||
|
- **Preprocessing**: Document preparation pipeline
|
||||||
|
- **Loaders**: Support for various file formats (PDF, DOCX, EPUB, etc.)
|
||||||
|
|
||||||
|
### Build System
|
||||||
|
|
||||||
|
- **Electron-Vite**: Development and build tooling (v4.0.0)
|
||||||
|
- **Rolldown-Vite**: Using experimental rolldown-vite instead of standard vite
|
||||||
|
- **Workspaces**: Monorepo structure with `packages/` directory
|
||||||
|
- **Multiple Entry Points**: Main app, mini window, selection toolbar
|
||||||
|
- **Styled Components**: CSS-in-JS styling with SWC optimization
|
||||||
|
|
||||||
|
### Testing Strategy
|
||||||
|
|
||||||
|
- **Vitest**: Unit and integration testing
|
||||||
|
- **Playwright**: End-to-end testing
|
||||||
|
- **Component Testing**: React Testing Library
|
||||||
|
- **Coverage**: Available via `yarn test:coverage`
|
||||||
|
|
||||||
|
### Key Patterns
|
||||||
|
|
||||||
|
- **IPC Communication**: Secure main-renderer communication via preload scripts
|
||||||
|
- **Service Layer**: Clear separation between UI and business logic
|
||||||
|
- **Plugin Architecture**: Extensible via MCP servers and middleware
|
||||||
|
- **Multi-language Support**: i18n with dynamic loading
|
||||||
|
- **Theme System**: Light/dark themes with custom CSS variables
|
||||||
|
|
||||||
|
### UI Design
|
||||||
|
|
||||||
|
The project is in the process of migrating from antd & styled-components to HeroUI. Please use HeroUI to build UI components. The use of antd and styled-components is prohibited.
|
||||||
|
|
||||||
|
HeroUI Docs: https://www.heroui.com/docs/guide/introduction
|
||||||
|
|
||||||
|
### Database Architecture
|
||||||
|
|
||||||
|
- **Database**: SQLite (`cherrystudio.sqlite`) + libsql driver
|
||||||
|
- **ORM**: Drizzle ORM with comprehensive migration system
|
||||||
|
- **Schemas**: Located in `src/main/data/db/schemas/` directory
|
||||||
|
|
||||||
|
#### Database Standards
|
||||||
|
|
||||||
|
- **Table Naming**: Use singular form with snake_case (e.g., `topic`, `message`, `app_state`)
|
||||||
|
- **Schema Exports**: Export using `xxxTable` pattern (e.g., `topicTable`, `appStateTable`)
|
||||||
|
- **Field Definition**: Drizzle auto-infers field names, no need to add default field names
|
||||||
|
- **JSON Fields**: For JSON support, add `{ mode: 'json' }`, refer to `preference.ts` table definition
|
||||||
|
- **JSON Serialization**: For JSON fields, no need to manually serialize/deserialize when reading/writing to database, Drizzle handles this automatically
|
||||||
|
- **Timestamps**: Use existing `crudTimestamps` utility
|
||||||
|
- **Migrations**: Generate via `yarn run migrations:generate`
|
||||||
|
|
||||||
|
## Data Access Patterns
|
||||||
|
|
||||||
|
The application uses three distinct data management systems. Choose the appropriate system based on data characteristics:
|
||||||
|
|
||||||
|
### Cache System
|
||||||
|
- **Purpose**: Temporary data that can be regenerated
|
||||||
|
- **Lifecycle**: Component-level (memory), window-level (shared), or persistent (survives restart)
|
||||||
|
- **Use Cases**: API response caching, computed results, temporary UI state
|
||||||
|
- **APIs**: `useCache`, `useSharedCache`, `usePersistCache` hooks, or `cacheService`
|
||||||
|
|
||||||
|
### Preference System
|
||||||
|
- **Purpose**: User configuration and application settings
|
||||||
|
- **Lifecycle**: Permanent until user changes
|
||||||
|
- **Use Cases**: Theme, language, editor settings, user preferences
|
||||||
|
- **APIs**: `usePreference`, `usePreferences` hooks, or `preferenceService`
|
||||||
|
|
||||||
|
### User Data API
|
||||||
|
- **Purpose**: Core business data (conversations, files, notes, etc.)
|
||||||
|
- **Lifecycle**: Permanent business records
|
||||||
|
- **Use Cases**: Topics, messages, files, knowledge base, user-generated content
|
||||||
|
- **APIs**: `useDataApi` hook or `dataApiService` for direct calls
|
||||||
|
|
||||||
|
### Selection Guidelines
|
||||||
|
|
||||||
|
- **Use Cache** for data that can be lost without impact (computed values, API responses)
|
||||||
|
- **Use Preferences** for user settings that affect app behavior (UI configuration, feature flags)
|
||||||
|
- **Use User Data API** for irreplaceable business data (conversations, documents, user content)
|
||||||
|
|
||||||
|
## Logging Standards
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
### Logging
|
|
||||||
```typescript
|
```typescript
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
const logger = loggerService.withContext('moduleName')
|
const logger = loggerService.withContext('moduleName')
|
||||||
|
|||||||
@@ -65,7 +65,28 @@ The Test Plan aims to provide users with a more stable application experience an
|
|||||||
### Other Suggestions
|
### Other Suggestions
|
||||||
|
|
||||||
- **Contact Developers**: Before submitting a PR, you can contact the developers first to discuss or get help.
|
- **Contact Developers**: Before submitting a PR, you can contact the developers first to discuss or get help.
|
||||||
- **Become a Core Developer**: If you contribute to the project consistently, congratulations, you can become a core developer and gain project membership status. Please check our [Membership Guide](https://github.com/CherryHQ/community/blob/main/docs/membership.en.md).
|
|
||||||
|
## Important Contribution Guidelines & Focus Areas
|
||||||
|
|
||||||
|
Please review the following critical information before submitting your Pull Request:
|
||||||
|
|
||||||
|
### Temporary Restriction on Data-Changing Feature PRs 🚫
|
||||||
|
|
||||||
|
**Currently, we are NOT accepting feature Pull Requests that introduce changes to our Redux data models or IndexedDB schemas.**
|
||||||
|
|
||||||
|
Our core team is currently focused on significant architectural updates that involve these data structures. To ensure stability and focus during this period, contributions of this nature will be temporarily managed internally.
|
||||||
|
|
||||||
|
* **PRs that require changes to Redux state shape or IndexedDB schemas will be closed.**
|
||||||
|
* **This restriction is temporary and will be lifted with the release of `v2.0.0`.** You can track the progress of `v2.0.0` and its related discussions on issue [#10162](https://github.com/YOUR_ORG/YOUR_REPO/issues/10162) (please replace with your actual repo link).
|
||||||
|
|
||||||
|
We highly encourage contributions for:
|
||||||
|
* Bug fixes 🐞
|
||||||
|
* Performance improvements 🚀
|
||||||
|
* Documentation updates 📚
|
||||||
|
* Features that **do not** alter Redux data models or IndexedDB schemas (e.g., UI enhancements, new components, minor refactors). ✨
|
||||||
|
|
||||||
|
We appreciate your understanding and continued support during this important development phase. Thank you!
|
||||||
|
|
||||||
|
|
||||||
## Contact Us
|
## Contact Us
|
||||||
|
|
||||||
|
|||||||
677
LICENSE
677
LICENSE
@@ -1,42 +1,661 @@
|
|||||||
**Licensing**
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
This project employs a **User-Segmented Dual Licensing** model.
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
**Core Principle:**
|
Preamble
|
||||||
|
|
||||||
* **Individual Users and Organizations with 10 or Fewer Individuals:** Governed by default under the **GNU Affero General Public License v3.0 (AGPLv3)**.
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
* **Organizations with More Than 10 Individuals:** **Must** obtain a **Commercial License**.
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
Definition: "10 or Fewer Individuals"
|
The licenses for most software and other practical works are designed
|
||||||
Refers to any organization (including companies, non-profits, government agencies, educational institutions, etc.) where the total number of individuals who can access, use, or in any way directly or indirectly benefit from the functionality of this software (Cherry Studio) does not exceed 10. This includes, but is not limited to, developers, testers, operations staff, end-users, and indirect users via integrated systems.
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
---
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
**1. Open Source License: AGPLv3 - For Individuals and Organizations of 10 or Fewer**
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
* If you are an individual user, or if your organization meets the "10 or Fewer Individuals" definition above, you are free to use, modify, and distribute Cherry Studio under the terms of the **AGPLv3**. The full text of the AGPLv3 can be found in the LICENSE file at [https://www.gnu.org/licenses/agpl-3.0.html](https://www.gnu.org/licenses/agpl-3.0.html).
|
A secondary benefit of defending all users' freedom is that
|
||||||
* **Core Obligation:** A key requirement of the AGPLv3 is that if you modify Cherry Studio and make it available over a network, or distribute the modified version, you must provide the **complete corresponding source code** under the AGPLv3 license to the recipients. Even if you qualify under the "10 or Fewer Individuals" rule, if you wish to avoid this source code disclosure obligation, you will need to obtain a Commercial License (see below).
|
improvements made in alternate versions of the program, if they
|
||||||
* Please read and understand the full terms of the AGPLv3 carefully before use.
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
**2. Commercial License - For Organizations with More Than 10 Individuals, or Users Needing to Avoid AGPLv3 Obligations**
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
* **Mandatory Requirement:** If your organization does **not** meet the "10 or Fewer Individuals" definition above (i.e., 11 or more individuals can access, use, or benefit from the software), you **must** contact us to obtain and execute a Commercial License to use Cherry Studio.
|
An older license, called the Affero General Public License and
|
||||||
* **Voluntary Option:** Even if your organization meets the "10 or Fewer Individuals" condition, if your intended use case **cannot comply with the terms of the AGPLv3** (particularly the obligations regarding **source code disclosure**), or if you require specific commercial terms **not offered** by the AGPLv3 (such as warranties, indemnities, or freedom from copyleft restrictions), you also **must** contact us to obtain and execute a Commercial License.
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
* **Common scenarios requiring a Commercial License include (but are not limited to):**
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
* Your organization has more than 10 individuals who can access, use, or benefit from the software.
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
* (Regardless of organization size) You wish to distribute a modified version of Cherry Studio but **do not want** to disclose the source code of your modifications under AGPLv3.
|
this license.
|
||||||
* (Regardless of organization size) You wish to provide a network service (SaaS) based on a modified version of Cherry Studio but **do not want** to provide the modified source code to users of the service under AGPLv3.
|
|
||||||
* (Regardless of organization size) Your corporate policies, client contracts, or project requirements prohibit the use of AGPLv3-licensed software or mandate closed-source distribution and confidentiality.
|
|
||||||
* The Commercial License grants you rights exempting you from AGPLv3 obligations (like source code disclosure) and may include additional commercial assurances.
|
|
||||||
* **Obtaining a Commercial License:** Please contact the Cherry Studio development team via email at **bd@cherry-ai.com** to discuss commercial licensing options.
|
|
||||||
|
|
||||||
**3. Contributions**
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
* We welcome community contributions to Cherry Studio. All contributions submitted to this project are considered to be offered under the **AGPLv3** license.
|
TERMS AND CONDITIONS
|
||||||
* By submitting a contribution to this project (e.g., via a Pull Request), you agree to license your code under the AGPLv3 to the project and all its downstream users (regardless of whether those users ultimately operate under AGPLv3 or a Commercial License).
|
|
||||||
* You also understand and agree that your contribution may be included in distributions of Cherry Studio offered under our commercial license.
|
|
||||||
|
|
||||||
**4. Other Terms**
|
0. Definitions.
|
||||||
|
|
||||||
* The specific terms and conditions of the Commercial License are governed by the formal commercial license agreement signed by both parties.
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
* The project maintainers reserve the right to update this licensing policy (including the definition and threshold for user count) as needed. Updates will be communicated through official project channels (e.g., code repository, official website).
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ We're actively working on the following features and improvements:
|
|||||||
- iOS App (Phase 1)
|
- iOS App (Phase 1)
|
||||||
- Multi-Window support
|
- Multi-Window support
|
||||||
- Window Pinning functionality
|
- Window Pinning functionality
|
||||||
|
- Intel AI PC (Core Ultra) Support
|
||||||
|
|
||||||
4. 🔌 **Advanced Features**
|
4. 🔌 **Advanced Features**
|
||||||
|
|
||||||
@@ -286,6 +287,14 @@ We believe the Enterprise Edition will become your team's AI productivity engine
|
|||||||
</picture>
|
</picture>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
# 📜 License
|
||||||
|
|
||||||
|
The Cherry Studio Community Edition is governed by the standard GNU Affero General Public License v3.0 (AGPL-3.0), available at https://www.gnu.org/licenses/agpl-3.0.html.
|
||||||
|
|
||||||
|
Use of the Cherry Studio Community Edition for commercial purposes is permitted, subject to full compliance with the terms and conditions of the AGPL-3.0 license.
|
||||||
|
|
||||||
|
Should you require a commercial license that provides an exemption from the AGPL-3.0 requirements, please contact us at bd@cherry-ai.com.
|
||||||
|
|
||||||
<!-- Links & Images -->
|
<!-- Links & Images -->
|
||||||
|
|
||||||
[deepwiki-shield]: https://img.shields.io/badge/Deepwiki-CherryHQ-0088CC?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNy45MyAzMiI+PHBhdGggZD0iTTE5LjMzIDE0LjEyYy42Ny0uMzkgMS41LS4zOSAyLjE4IDBsMS43NCAxYy4wNi4wMy4xMS4wNi4xOC4wN2guMDRjLjA2LjAzLjEyLjAzLjE4LjAzaC4wMmMuMDYgMCAuMTEgMCAuMTctLjAyaC4wM2MuMDYtLjAyLjEyLS4wNS4xNy0uMDhoLjAybDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjdWOC40YS44MS44MSAwIDAgMC0uNC0uN2wtMy40OC0yLjAxYS44My44MyAwIDAgMC0uODEgMEwxOS43NyA3LjdoLS4wMWwtLjE1LjEyLS4wMi4wMnMtLjA3LjA5LS4xLjE0VjhhLjQuNCAwIDAgMC0uMDguMTd2LjA0Yy0uMDMuMDYtLjAzLjEyLS4wMy4xOXYyLjAxYzAgLjc4LS40MSAxLjQ5LTEuMDkgMS44OC0uNjcuMzktMS41LjM5LTIuMTggMGwtMS43NC0xYS42LjYgMCAwIDAtLjIxLS4wOGMtLjA2LS4wMS0uMTItLjAyLS4xOC0uMDJoLS4wM2MtLjA2IDAtLjExLjAxLS4xNy4wMmgtLjAzYy0uMDYuMDItLjEyLjA0LS4xNy4wN2gtLjAybC0zLjQ3IDIuMDFjLS4yNS4xNC0uNC40MS0uNC43VjE4YzAgLjI5LjE1LjU1LjQuN2wzLjQ4IDIuMDFoLjAyYy4wNi4wNC4xMS4wNi4xNy4wOGguMDNjLjA1LjAyLjExLjAzLjE3LjAzaC4wMmMuMDYgMCAuMTIgMCAuMTgtLjAyaC4wNGMuMDYtLjAzLjEyLS4wNS4xOC0uMDhsMS43NC0xYy42Ny0uMzkgMS41LS4zOSAyLjE3IDBzMS4wOSAxLjExIDEuMDkgMS44OHYyLjAxYzAgLjA3IDAgLjEzLjAyLjE5di4wNGMuMDMuMDYuMDUuMTIuMDguMTd2LjAycy4wOC4wOS4xMi4xM2wuMDIuMDJzLjA5LjA4LjE1LjExYzAgMCAuMDEgMCAuMDEuMDFsMy40OCAyLjAxYy4yNS4xNC41Ni4xNC44MSAwbDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjd2LTQuMDFhLjgxLjgxIDAgMCAwLS40LS43bC0zLjQ4LTIuMDFoLS4wMmMtLjA1LS4wNC0uMTEtLjA2LS4xNy0uMDhoLS4wM2EuNS41IDAgMCAwLS4xNy0uMDNoLS4wM2MtLjA2IDAtLjEyIDAtLjE4LjAyLS4wNy4wMi0uMTUuMDUtLjIxLjA4bC0xLjc0IDFjLS42Ny4zOS0xLjUuMzktMi4xNyAwYTIuMTkgMi4xOSAwIDAgMS0xLjA5LTEuODhjMC0uNzguNDItMS40OSAxLjA5LTEuODhaIiBzdHlsZT0iZmlsbDojNWRiZjlkIi8+PHBhdGggZD0ibS40IDEzLjExIDMuNDcgMi4wMWMuMjUuMTQuNTYuMTQuOCAwbDMuNDctMi4wMWguMDFsLjE1LS4xMi4wMi0uMDJzLjA3LS4wOS4xLS4xNGwuMDItLjAyYy4wMy0uMDUuMDUtLjExLjA3LS4xN3YtLjA0Yy4wMy0uMDYuMDMtLjEyLjAzLS4xOVYxMC40YzAtLjc4LjQyLTEuNDkgMS4wOS0xLjg4czEuNS0uMzkgMi4xOCAwbDEuNzQgMWMuMDcuMDQuMTQuMDcuMjEuMDguMDYuMDEuMTIuMDIuMTguMDJoLjAzYy4wNiAwIC4xMS0uMDEuMTctLjAyaC4wM2MuMDYtLjAyLjEyLS4wNC4xNy0uMDdoLjAybDMuNDctMi4wMmMuMjUtLjE0LjQtLjQxLjQtLjd2LTRhLjgxLjgxIDAgMCAwLS40LS43bC0zLjQ2LTJhLjgzLjgzIDAgMCAwLS44MSAwbC0zLjQ4IDIuMDFoLS4wMWwtLjE1LjEyLS4wMi4wMi0uMS4xMy0uMDIuMDJjLS4wMy4wNS0uMDUuMTEtLjA3LjE3di4wNGMtLjAzLjA2LS4wMy4xMi0uMDMuMTl2Mi4wMWMwIC43OC0uNDIgMS40OS0xLjA5IDEuODhzLTEuNS4zOS0yLjE4IDBsLTEuNzQtMWEuNi42IDAgMCAwLS4yMS0uMDhjLS4wNi0uMDEtLjEyLS4wMi0uMTgtLjAyaC0uMDNjLS4wNiAwLS4xMS4wMS0uMTcuMDJoLS4wM2MtLjA2LjAyLS4xMi4wNS0uMTcuMDhoLS4wMkwuNCA3LjcxYy0uMjUuMTQtLjQuNDEtLjQuNjl2NC4wMWMwIC4yOS4xNS41Ni40LjciIHN0eWxlPSJmaWxsOiM0NDY4YzQiLz48cGF0aCBkPSJtMTcuODQgMjQuNDgtMy40OC0yLjAxaC0uMDJjLS4wNS0uMDQtLjExLS4wNi0uMTctLjA4aC0uMDNhLjUuNSAwIDAgMC0uMTctLjAzaC0uMDNjLS4wNiAwLS4xMiAwLS4xOC4wMmgtLjA0Yy0uMDYuMDMtLjEyLjA1LS4xOC4wOGwtMS43NCAxYy0uNjcuMzktMS41LjM5LTIuMTggMGEyLjE5IDIuMTkgMCAwIDEtMS4wOS0xLjg4di0yLjAxYzAtLjA2IDAtLjEzLS4wMi0uMTl2LS4wNGMtLjAzLS4wNi0uMDUtLjExLS4wOC0uMTdsLS4wMi0uMDJzLS4wNi0uMDktLjEtLjEzTDguMjkgMTlzLS4wOS0uMDgtLjE1LS4xMWgtLjAxbC0zLjQ3LTIuMDJhLjgzLjgzIDAgMCAwLS44MSAwTC4zNyAxOC44OGEuODcuODcgMCAwIDAtLjM3LjcxdjQuMDFjMCAuMjkuMTUuNTUuNC43bDMuNDcgMi4wMWguMDJjLjA1LjA0LjExLjA2LjE3LjA4aC4wM2MuMDUuMDIuMTEuMDMuMTYuMDNoLjAzYy4wNiAwIC4xMiAwIC4xOC0uMDJoLjA0Yy4wNi0uMDMuMTItLjA1LjE4LS4wOGwxLjc0LTFjLjY3LS4zOSAxLjUtLjM5IDIuMTcgMHMxLjA5IDEuMTEgMS4wOSAxLjg4djIuMDFjMCAuMDcgMCAuMTMuMDIuMTl2LjA0Yy4wMy4wNi4wNS4xMS4wOC4xN2wuMDIuMDJzLjA2LjA5LjEuMTRsLjAyLjAycy4wOS4wOC4xNS4xMWguMDFsMy40OCAyLjAyYy4yNS4xNC41Ni4xNC44MSAwbDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjdWMjUuMmEuODEuODEgMCAwIDAtLjQtLjdaIiBzdHlsZT0iZmlsbDojNDI5M2Q5Ii8+PC9zdmc+
|
[deepwiki-shield]: https://img.shields.io/badge/Deepwiki-CherryHQ-0088CC?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNy45MyAzMiI+PHBhdGggZD0iTTE5LjMzIDE0LjEyYy42Ny0uMzkgMS41LS4zOSAyLjE4IDBsMS43NCAxYy4wNi4wMy4xMS4wNi4xOC4wN2guMDRjLjA2LjAzLjEyLjAzLjE4LjAzaC4wMmMuMDYgMCAuMTEgMCAuMTctLjAyaC4wM2MuMDYtLjAyLjEyLS4wNS4xNy0uMDhoLjAybDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjdWOC40YS44MS44MSAwIDAgMC0uNC0uN2wtMy40OC0yLjAxYS44My44MyAwIDAgMC0uODEgMEwxOS43NyA3LjdoLS4wMWwtLjE1LjEyLS4wMi4wMnMtLjA3LjA5LS4xLjE0VjhhLjQuNCAwIDAgMC0uMDguMTd2LjA0Yy0uMDMuMDYtLjAzLjEyLS4wMy4xOXYyLjAxYzAgLjc4LS40MSAxLjQ5LTEuMDkgMS44OC0uNjcuMzktMS41LjM5LTIuMTggMGwtMS43NC0xYS42LjYgMCAwIDAtLjIxLS4wOGMtLjA2LS4wMS0uMTItLjAyLS4xOC0uMDJoLS4wM2MtLjA2IDAtLjExLjAxLS4xNy4wMmgtLjAzYy0uMDYuMDItLjEyLjA0LS4xNy4wN2gtLjAybC0zLjQ3IDIuMDFjLS4yNS4xNC0uNC40MS0uNC43VjE4YzAgLjI5LjE1LjU1LjQuN2wzLjQ4IDIuMDFoLjAyYy4wNi4wNC4xMS4wNi4xNy4wOGguMDNjLjA1LjAyLjExLjAzLjE3LjAzaC4wMmMuMDYgMCAuMTIgMCAuMTgtLjAyaC4wNGMuMDYtLjAzLjEyLS4wNS4xOC0uMDhsMS43NC0xYy42Ny0uMzkgMS41LS4zOSAyLjE3IDBzMS4wOSAxLjExIDEuMDkgMS44OHYyLjAxYzAgLjA3IDAgLjEzLjAyLjE5di4wNGMuMDMuMDYuMDUuMTIuMDguMTd2LjAycy4wOC4wOS4xMi4xM2wuMDIuMDJzLjA5LjA4LjE1LjExYzAgMCAuMDEgMCAuMDEuMDFsMy40OCAyLjAxYy4yNS4xNC41Ni4xNC44MSAwbDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjd2LTQuMDFhLjgxLjgxIDAgMCAwLS40LS43bC0zLjQ4LTIuMDFoLS4wMmMtLjA1LS4wNC0uMTEtLjA2LS4xNy0uMDhoLS4wM2EuNS41IDAgMCAwLS4xNy0uMDNoLS4wM2MtLjA2IDAtLjEyIDAtLjE4LjAyLS4wNy4wMi0uMTUuMDUtLjIxLjA4bC0xLjc0IDFjLS42Ny4zOS0xLjUuMzktMi4xNyAwYTIuMTkgMi4xOSAwIDAgMS0xLjA5LTEuODhjMC0uNzguNDItMS40OSAxLjA5LTEuODhaIiBzdHlsZT0iZmlsbDojNWRiZjlkIi8+PHBhdGggZD0ibS40IDEzLjExIDMuNDcgMi4wMWMuMjUuMTQuNTYuMTQuOCAwbDMuNDctMi4wMWguMDFsLjE1LS4xMi4wMi0uMDJzLjA3LS4wOS4xLS4xNGwuMDItLjAyYy4wMy0uMDUuMDUtLjExLjA3LS4xN3YtLjA0Yy4wMy0uMDYuMDMtLjEyLjAzLS4xOVYxMC40YzAtLjc4LjQyLTEuNDkgMS4wOS0xLjg4czEuNS0uMzkgMi4xOCAwbDEuNzQgMWMuMDcuMDQuMTQuMDcuMjEuMDguMDYuMDEuMTIuMDIuMTguMDJoLjAzYy4wNiAwIC4xMS0uMDEuMTctLjAyaC4wM2MuMDYtLjAyLjEyLS4wNC4xNy0uMDdoLjAybDMuNDctMi4wMmMuMjUtLjE0LjQtLjQxLjQtLjd2LTRhLjgxLjgxIDAgMCAwLS40LS43bC0zLjQ2LTJhLjgzLjgzIDAgMCAwLS44MSAwbC0zLjQ4IDIuMDFoLS4wMWwtLjE1LjEyLS4wMi4wMi0uMS4xMy0uMDIuMDJjLS4wMy4wNS0uMDUuMTEtLjA3LjE3di4wNGMtLjAzLjA2LS4wMy4xMi0uMDMuMTl2Mi4wMWMwIC43OC0uNDIgMS40OS0xLjA5IDEuODhzLTEuNS4zOS0yLjE4IDBsLTEuNzQtMWEuNi42IDAgMCAwLS4yMS0uMDhjLS4wNi0uMDEtLjEyLS4wMi0uMTgtLjAyaC0uMDNjLS4wNiAwLS4xMS4wMS0uMTcuMDJoLS4wM2MtLjA2LjAyLS4xMi4wNS0uMTcuMDhoLS4wMkwuNCA3LjcxYy0uMjUuMTQtLjQuNDEtLjQuNjl2NC4wMWMwIC4yOS4xNS41Ni40LjciIHN0eWxlPSJmaWxsOiM0NDY4YzQiLz48cGF0aCBkPSJtMTcuODQgMjQuNDgtMy40OC0yLjAxaC0uMDJjLS4wNS0uMDQtLjExLS4wNi0uMTctLjA4aC0uMDNhLjUuNSAwIDAgMC0uMTctLjAzaC0uMDNjLS4wNiAwLS4xMiAwLS4xOC4wMmgtLjA0Yy0uMDYuMDMtLjEyLjA1LS4xOC4wOGwtMS43NCAxYy0uNjcuMzktMS41LjM5LTIuMTggMGEyLjE5IDIuMTkgMCAwIDEtMS4wOS0xLjg4di0yLjAxYzAtLjA2IDAtLjEzLS4wMi0uMTl2LS4wNGMtLjAzLS4wNi0uMDUtLjExLS4wOC0uMTdsLS4wMi0uMDJzLS4wNi0uMDktLjEtLjEzTDguMjkgMTlzLS4wOS0uMDgtLjE1LS4xMWgtLjAxbC0zLjQ3LTIuMDJhLjgzLjgzIDAgMCAwLS44MSAwTC4zNyAxOC44OGEuODcuODcgMCAwIDAtLjM3LjcxdjQuMDFjMCAuMjkuMTUuNTUuNC43bDMuNDcgMi4wMWguMDJjLjA1LjA0LjExLjA2LjE3LjA4aC4wM2MuMDUuMDIuMTEuMDMuMTYuMDNoLjAzYy4wNiAwIC4xMiAwIC4xOC0uMDJoLjA0Yy4wNi0uMDMuMTItLjA1LjE4LS4wOGwxLjc0LTFjLjY3LS4zOSAxLjUtLjM5IDIuMTcgMHMxLjA5IDEuMTEgMS4wOSAxLjg4djIuMDFjMCAuMDcgMCAuMTMuMDIuMTl2LjA0Yy4wMy4wNi4wNS4xMS4wOC4xN2wuMDIuMDJzLjA2LjA5LjEuMTRsLjAyLjAycy4wOS4wOC4xNS4xMWguMDFsMy40OCAyLjAyYy4yNS4xNC41Ni4xNC44MSAwbDMuNDgtMi4wMWMuMjUtLjE0LjQtLjQxLjQtLjdWMjUuMmEuODEuODEgMCAwIDAtLjQtLjdaIiBzdHlsZT0iZmlsbDojNDI5M2Q5Ii8+PC9zdmc+
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"!.github/**",
|
"!.github/**",
|
||||||
"!.husky/**",
|
"!.husky/**",
|
||||||
"!.vscode/**",
|
"!.vscode/**",
|
||||||
|
"!.claude/**",
|
||||||
"!*.yaml",
|
"!*.yaml",
|
||||||
"!*.yml",
|
"!*.yml",
|
||||||
"!*.mjs",
|
"!*.mjs",
|
||||||
|
|||||||
@@ -69,7 +69,28 @@ git commit --signoff -m "Your commit message"
|
|||||||
### 其他建议
|
### 其他建议
|
||||||
|
|
||||||
- **联系开发者**:在提交 PR 之前,您可以先和开发者进行联系,共同探讨或者获取帮助。
|
- **联系开发者**:在提交 PR 之前,您可以先和开发者进行联系,共同探讨或者获取帮助。
|
||||||
- **成为核心开发者**:如果您能够稳定为项目贡献,恭喜您可以成为项目核心开发者,获取到项目成员身份。请查看我们的[成员指南](https://github.com/CherryHQ/community/blob/main/membership.md)
|
|
||||||
|
## 重要贡献指南与关注点
|
||||||
|
|
||||||
|
在提交 Pull Request 之前,请务必阅读以下关键信息:
|
||||||
|
|
||||||
|
### 🚫 暂时限制涉及数据更改的功能性 PR
|
||||||
|
|
||||||
|
**目前,我们不接受涉及 Redux 数据模型或 IndexedDB schema 变更的功能性 Pull Request。**
|
||||||
|
|
||||||
|
我们的核心团队目前正专注于涉及这些数据结构的关键架构更新和基础工作。为确保在此期间的稳定性与专注,此类贡献将暂时由内部进行管理。
|
||||||
|
|
||||||
|
* **需要更改 Redux 状态结构或 IndexedDB schema 的 PR 将会被关闭。**
|
||||||
|
* **此限制是临时性的,并将在 `v2.0.0` 版本发布后解除。** 您可以通过 Issue [#10162](https://github.com/YOUR_ORG/YOUR_REPO/issues/10162) (请替换为您的实际仓库链接) 跟踪 `v2.0.0` 的进展及相关讨论。
|
||||||
|
|
||||||
|
我们非常鼓励以下类型的贡献:
|
||||||
|
* 错误修复 🐞
|
||||||
|
* 性能改进 🚀
|
||||||
|
* 文档更新 📚
|
||||||
|
* 不改变 Redux 数据模型或 IndexedDB schema 的功能(例如,UI 增强、新组件、小型重构)。✨
|
||||||
|
|
||||||
|
感谢您在此重要开发阶段的理解与持续支持。谢谢!
|
||||||
|
|
||||||
|
|
||||||
## 联系我们
|
## 联系我们
|
||||||
|
|
||||||
|
|||||||
260
docs/technical/ocr-architecture.md
Normal file
260
docs/technical/ocr-architecture.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
> [!NOTE]
|
||||||
|
> This technical documentation was automatically generated by Claude Code based on analysis of the current OCR implementation in the codebase. The content reflects the architecture as of the current branch state.
|
||||||
|
|
||||||
|
# OCR Architecture
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Cherry Studio's OCR (Optical Character Recognition) system is a modular, extensible architecture designed to support multiple OCR providers and file types. The architecture follows a layered approach with clear separation of concerns between data access, business logic, and provider implementations.
|
||||||
|
|
||||||
|
## Architecture Layers
|
||||||
|
|
||||||
|
The OCR architecture follows a layered approach where data interactions occur through RESTful APIs, while IPC serves as part of the API layer, allowing the renderer to interact directly with the business layer:
|
||||||
|
|
||||||
|
### 1. API Layer
|
||||||
|
**Location**: `src/main/data/api/handlers/`, `src/main/ipc.ts`, `src/preload/index.ts`
|
||||||
|
|
||||||
|
- **IPC Bridge**: Serves as API layer connecting renderer to main process
|
||||||
|
- **Request Routing**: Routes IPC calls to appropriate service methods
|
||||||
|
- **Type Safety**: Zod schemas for request/response validation
|
||||||
|
- **Error Handling**: Centralized error propagation across process boundaries
|
||||||
|
- **Security**: Secure communication sandbox between renderer and main processes
|
||||||
|
|
||||||
|
### 2. OCR Service Layer (Business Layer)
|
||||||
|
**Location**: `src/main/services/ocr/`
|
||||||
|
|
||||||
|
- **OcrService**: Main business logic orchestrator and central coordinator
|
||||||
|
- **Provider Registry**: Manages registered OCR providers
|
||||||
|
- **Data Integration**: Direct interaction with data layer for provider management
|
||||||
|
- **Lifecycle Management**: Handles provider initialization and disposal
|
||||||
|
- **Validation**: Ensures provider availability and data integrity
|
||||||
|
- **Orchestration**: Coordinates between providers and data services
|
||||||
|
- **Direct IPC Access**: Renderer can directly invoke business layer methods via IPC
|
||||||
|
|
||||||
|
### 3. Provider Services Layer
|
||||||
|
**Location**: `src/main/services/ocr/builtin/`
|
||||||
|
|
||||||
|
- **Base Service**: Abstract `OcrBaseService` defines common interface
|
||||||
|
- **Data Independence**: No direct database interactions, relies on injected data
|
||||||
|
- **Built-in Providers**:
|
||||||
|
- `TesseractService`: Local Tesseract.js implementation
|
||||||
|
- `SystemOcrService`: Platform-specific system OCR
|
||||||
|
- `PpocrService`: PaddleOCR integration
|
||||||
|
- `OvOcrService`: Intel OpenVINO (NPU) OCR
|
||||||
|
- **Pure OCR Logic**: Focus solely on OCR processing capabilities
|
||||||
|
|
||||||
|
### 4. Data Layer
|
||||||
|
**Location**: `src/main/data/db/schemas/ocr/`, `src/main/data/repositories/`
|
||||||
|
|
||||||
|
- **Database Schema**: Uses Drizzle ORM with SQLite database
|
||||||
|
- **Repository Pattern**: `OcrProviderRepository` handles all database operations
|
||||||
|
- **Provider Storage**: Stores provider configurations in `ocr_provider` table
|
||||||
|
- **JSON Configuration**: Polymorphic `config` field stores provider-specific settings
|
||||||
|
- **Data Access**: Exclusively accessed by OCR Service layer
|
||||||
|
|
||||||
|
### 5. Frontend Layer
|
||||||
|
**Location**: `src/renderer/src/services/ocr/`, `src/renderer/src/hooks/ocr/`
|
||||||
|
|
||||||
|
- **Direct IPC Communication**: Direct interaction with business layer via IPC
|
||||||
|
- **React Hooks**: Custom hooks for OCR operations and state management
|
||||||
|
- **Configuration UI**: Settings pages for provider configuration
|
||||||
|
- **State Management**: Frontend state synchronization with backend data
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Frontend UI] --> B[Frontend OCR Service]
|
||||||
|
B --> C[API Layer - IPC Bridge]
|
||||||
|
C --> D[OCR Service Layer - Business Logic]
|
||||||
|
D --> E[Data Layer - Provider Repository]
|
||||||
|
D --> F[Provider Services Layer]
|
||||||
|
F --> G[OCR Processing]
|
||||||
|
G --> H[Result]
|
||||||
|
H --> F
|
||||||
|
F --> D
|
||||||
|
D --> C
|
||||||
|
C --> B
|
||||||
|
B --> A
|
||||||
|
|
||||||
|
style D fill:#e1f5fe
|
||||||
|
style F fill:#f3e5f5
|
||||||
|
style E fill:#e8f5e8
|
||||||
|
style C fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Flow Characteristics:**
|
||||||
|
- **Direct Business Access**: Frontend communicates directly with OCR Service layer via IPC
|
||||||
|
- **IPC as API Gateway**: IPC bridge functions as the API layer, handling routing and validation
|
||||||
|
- **Data Isolation**: Only business layer interacts with data persistence
|
||||||
|
- **Provider Independence**: OCR providers remain isolated from data concerns
|
||||||
|
|
||||||
|
## Provider System
|
||||||
|
|
||||||
|
### Provider Registration
|
||||||
|
- **Built-in Providers**: Automatically registered on service initialization
|
||||||
|
- **Custom Providers**: Support for extensible provider system
|
||||||
|
- **Configuration**: Each provider has its own configuration schema
|
||||||
|
|
||||||
|
### Provider Capabilities
|
||||||
|
```typescript
|
||||||
|
interface OcrProviderCapabilityRecord {
|
||||||
|
image?: boolean // Image file OCR support
|
||||||
|
pdf?: boolean // PDF file OCR support (future)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Architecture
|
||||||
|
- **Polymorphic Config**: JSON-based configuration adapts to provider needs
|
||||||
|
- **Type Safety**: Zod schemas validate provider-specific configurations
|
||||||
|
- **Runtime Validation**: Configuration validation before OCR operations
|
||||||
|
|
||||||
|
## Type System
|
||||||
|
|
||||||
|
### Core Types
|
||||||
|
- **`OcrProvider`**: Base provider interface
|
||||||
|
- **`OcrParams`**: OCR operation parameters
|
||||||
|
- **`OcrResult`**: Standardized OCR result format
|
||||||
|
- **`SupportedOcrFile`**: File types supported for OCR
|
||||||
|
|
||||||
|
### Business Types
|
||||||
|
- **`OcrProviderBusiness`**: Domain-level provider representation
|
||||||
|
- **Operations**: Create, Update, Replace, Delete operations
|
||||||
|
- **Queries**: List providers with filtering options
|
||||||
|
|
||||||
|
### Provider-Specific Types
|
||||||
|
- **TesseractConfig**: Language selection, model paths
|
||||||
|
- **SystemOcrConfig**: Language preferences
|
||||||
|
- **PaddleOCRConfig**: API endpoints, authentication
|
||||||
|
- **OpenVINOConfig**: Device selection, model paths
|
||||||
|
|
||||||
|
## Built-in Providers
|
||||||
|
|
||||||
|
### Tesseract OCR
|
||||||
|
- **Engine**: Tesseract.js
|
||||||
|
- **Languages**: Multi-language support with automatic download
|
||||||
|
- **Configuration**: Language selection, cache management
|
||||||
|
- **Performance**: Worker pooling for concurrent processing
|
||||||
|
|
||||||
|
### System OCR
|
||||||
|
- **Windows**: Windows Media Foundation OCR
|
||||||
|
- **macOS**: Vision framework OCR
|
||||||
|
- **Linux**: Platform-specific implementations
|
||||||
|
- **Features**: Native performance, system integration
|
||||||
|
|
||||||
|
### PaddleOCR
|
||||||
|
- **Deployment**: Remote API integration
|
||||||
|
- **Languages**: Chinese, English, and mixed language support
|
||||||
|
- **Configuration**: API endpoints and authentication
|
||||||
|
|
||||||
|
### Intel OpenVINO OCR
|
||||||
|
- **Hardware**: NPU acceleration support
|
||||||
|
- **Performance**: Optimized for Intel hardware
|
||||||
|
- **Use Case**: High-performance OCR scenarios
|
||||||
|
|
||||||
|
## Configuration Management
|
||||||
|
|
||||||
|
### Database Schema
|
||||||
|
```sql
|
||||||
|
CREATE TABLE ocr_provider (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
capabilities TEXT NOT NULL, -- JSON
|
||||||
|
config TEXT NOT NULL, -- JSON
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Provider Defaults
|
||||||
|
- **Initial Configuration**: Defined in `packages/shared/config/ocr.ts`
|
||||||
|
- **Migration System**: Automatic provider initialization on startup
|
||||||
|
- **User Customization**: Runtime configuration updates
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Error Categories
|
||||||
|
- **Provider Errors**: OCR engine failures, missing dependencies
|
||||||
|
- **Configuration Errors**: Invalid settings, missing parameters
|
||||||
|
- **File Errors**: Unsupported formats, corrupted files
|
||||||
|
- **System Errors**: Resource exhaustion, permissions
|
||||||
|
|
||||||
|
### Error Propagation
|
||||||
|
- **Logging**: Centralized logging with context
|
||||||
|
- **User Feedback**: Translated error messages
|
||||||
|
- **Recovery**: Graceful fallback options
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Resource Management
|
||||||
|
- **Worker Disposal**: Proper cleanup of OCR workers
|
||||||
|
- **Memory Management**: Limits on file sizes and concurrent operations
|
||||||
|
- **Caching**: Model and result caching where applicable
|
||||||
|
|
||||||
|
### Optimization
|
||||||
|
- **Lazy Loading**: Providers initialized on demand
|
||||||
|
- **Concurrent Processing**: Multiple workers for parallel operations
|
||||||
|
- **Hardware Acceleration**: NPU and GPU support where available
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
### Input Validation
|
||||||
|
- **File Type Checking**: Strict validation of supported formats
|
||||||
|
- **Size Limits**: Protection against resource exhaustion
|
||||||
|
- **Path Validation**: Prevention of path traversal attacks
|
||||||
|
|
||||||
|
### Configuration Security
|
||||||
|
- **API Key Storage**: Secure storage of sensitive configuration
|
||||||
|
- **Validation**: Runtime validation of configuration parameters
|
||||||
|
- **Sandboxing**: Isolated execution of OCR operations
|
||||||
|
|
||||||
|
## Extension Points
|
||||||
|
|
||||||
|
### Custom Providers
|
||||||
|
- **Interface**: Implement `OcrBaseService` for new providers
|
||||||
|
- **Registration**: Dynamic provider registration system
|
||||||
|
- **Configuration**: Extensible configuration schemas
|
||||||
|
|
||||||
|
### File Type Support
|
||||||
|
- **Handlers**: Modular file type processors
|
||||||
|
- **Capabilities**: Declarative provider capabilities
|
||||||
|
- **Future Support**: PDF, document formats planned
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
### Legacy System
|
||||||
|
- **Data Migration**: Automatic migration from old configuration formats
|
||||||
|
- **Compatibility**: Backward compatibility during transition
|
||||||
|
- **Testing**: Comprehensive test coverage for migration paths
|
||||||
|
|
||||||
|
### Future Enhancements
|
||||||
|
- **PDF Support**: Planned extension to document OCR
|
||||||
|
- **Cloud Providers**: API-based OCR services integration
|
||||||
|
- **AI Enhancement**: Post-processing and accuracy improvements
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### Adding New Providers
|
||||||
|
1. Create provider service extending `OcrBaseService`
|
||||||
|
2. Define provider-specific configuration schema
|
||||||
|
3. Register provider in `OcrService`
|
||||||
|
4. Add configuration UI components
|
||||||
|
5. Include comprehensive tests
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Provider services should never directly access the data layer. All data operations must go through the OCR Service layer to maintain proper separation of concerns.
|
||||||
|
|
||||||
|
### Configuration Changes
|
||||||
|
1. Update provider configuration schema
|
||||||
|
2. Add migration logic for existing configurations
|
||||||
|
3. Update UI validation and error handling
|
||||||
|
4. Test with various configuration scenarios
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Always validate configuration changes before saving to the database. Use Zod schemas for runtime validation to prevent corrupted provider configurations.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- **Unit Tests**: Provider implementation testing
|
||||||
|
- **Integration Tests**: End-to-end OCR workflows
|
||||||
|
- **Performance Tests**: Resource usage and timing
|
||||||
|
- **Error Scenarios**: Comprehensive error handling testing
|
||||||
260
docs/technical/ocr-architecture.zh.md
Normal file
260
docs/technical/ocr-architecture.zh.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
> [!NOTE]
|
||||||
|
> 本技术文档由 Claude Code 基于对当前代码库中 OCR 实现的分析自动生成。内容反映了当前分支状态的架构设计。
|
||||||
|
|
||||||
|
# OCR 架构文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
Cherry Studio 的 OCR(光学字符识别)系统是一个模块化、可扩展的架构,旨在支持多个 OCR 提供商和文件类型。该架构采用分层设计,在数据访问、业务逻辑和提供商实现之间有明确的关注点分离。
|
||||||
|
|
||||||
|
## 架构分层
|
||||||
|
|
||||||
|
OCR 架构采用分层方法,其中数据交互通过 RESTful API 进行,而 IPC 作为 API 层的一部分,允许 Renderer 直接与业务层交互:
|
||||||
|
|
||||||
|
### 1. API 层
|
||||||
|
**位置**: `src/main/data/api/handlers/`, `src/main/ipc.ts`, `src/preload/index.ts`
|
||||||
|
|
||||||
|
- **IPC 桥接**: 作为 API 层连接 Renderer 到主进程
|
||||||
|
- **请求路由**: 将 IPC 调用路由到相应的服务方法
|
||||||
|
- **类型安全**: 使用 Zod 模式进行请求/响应验证
|
||||||
|
- **错误处理**: 跨进程边界的集中式错误传播
|
||||||
|
- **安全**: Renderer 和主进程之间的安全通信沙盒
|
||||||
|
|
||||||
|
### 2. OCR 服务层(业务层)
|
||||||
|
**位置**: `src/main/services/ocr/`
|
||||||
|
|
||||||
|
- **OcrService**: 主要业务逻辑协调器和中央协调器
|
||||||
|
- **提供商注册表**: 管理已注册的 OCR 提供商
|
||||||
|
- **数据集成**: 与数据层直接交互进行提供商管理
|
||||||
|
- **生命周期管理**: 处理提供商初始化和销毁
|
||||||
|
- **验证**: 确保提供商可用性和数据完整性
|
||||||
|
- **协调**: 协调提供商和数据服务之间的交互
|
||||||
|
- **直接 IPC 访问**: Renderer 可通过 IPC 直接调用业务层方法
|
||||||
|
|
||||||
|
### 3. 提供商服务层
|
||||||
|
**位置**: `src/main/services/ocr/builtin/`
|
||||||
|
|
||||||
|
- **基础服务**: 抽象的 `OcrBaseService` 定义通用接口
|
||||||
|
- **数据独立性**: 无直接数据库交互,依赖外部传入的数据
|
||||||
|
- **内置提供商**:
|
||||||
|
- `TesseractService`: 本地 Tesseract.js 实现
|
||||||
|
- `SystemOcrService`: 平台特定的系统 OCR
|
||||||
|
- `PpocrService`: PaddleOCR 集成
|
||||||
|
- `OvOcrService`: Intel OpenVINO (NPU) OCR
|
||||||
|
- **纯 OCR 逻辑**: 专注于 OCR 处理能力
|
||||||
|
|
||||||
|
### 4. 数据层
|
||||||
|
**位置**: `src/main/data/db/schemas/ocr/`, `src/main/data/repositories/`
|
||||||
|
|
||||||
|
- **数据库架构**: 使用 Drizzle ORM 和 SQLite 数据库
|
||||||
|
- **仓储模式**: `OcrProviderRepository` 处理所有数据库操作
|
||||||
|
- **提供商存储**: 在 `ocr_provider` 表中存储提供商配置
|
||||||
|
- **JSON 配置**: 多态的 `config` 字段存储提供商特定的设置
|
||||||
|
- **数据访问**: 仅由 OCR 服务层访问
|
||||||
|
|
||||||
|
### 5. Renderer 层
|
||||||
|
**位置**: `src/renderer/src/services/ocr/`, `src/renderer/src/hooks/ocr/`
|
||||||
|
|
||||||
|
- **直接 IPC 通信**: 通过 IPC 与业务层直接交互
|
||||||
|
- **React Hooks**: 用于 OCR 操作和状态管理的自定义钩子
|
||||||
|
- **配置 UI**: 提供商配置的设置页面
|
||||||
|
- **状态管理**: Renderer 状态与后端数据同步
|
||||||
|
|
||||||
|
## 数据流
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Renderer UI] --> B[Renderer OCR 服务]
|
||||||
|
B --> C[API 层 - IPC 桥接]
|
||||||
|
C --> D[OCR 服务层 - 业务逻辑]
|
||||||
|
D --> E[数据层 - 提供商仓储]
|
||||||
|
D --> F[提供商服务层]
|
||||||
|
F --> G[OCR 处理]
|
||||||
|
G --> H[结果]
|
||||||
|
H --> F
|
||||||
|
F --> D
|
||||||
|
D --> C
|
||||||
|
C --> B
|
||||||
|
B --> A
|
||||||
|
|
||||||
|
style D fill:#e1f5fe
|
||||||
|
style F fill:#f3e5f5
|
||||||
|
style E fill:#e8f5e8
|
||||||
|
style C fill:#fff3e0
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键流程特征**:
|
||||||
|
- **直接业务访问**: Renderer 通过 IPC 与 OCR 服务层直接通信
|
||||||
|
- **IPC 作为 API 网关**: IPC 桥接作为 API 层,处理路由和验证
|
||||||
|
- **数据隔离**: 只有业务层与数据持久化交互
|
||||||
|
- **提供商独立性**: OCR 提供商保持与数据关注点的隔离
|
||||||
|
|
||||||
|
## 提供商系统
|
||||||
|
|
||||||
|
### 提供商注册
|
||||||
|
- **内置提供商**: 在服务初始化时自动注册
|
||||||
|
- **自定义提供商**: 支持可扩展的提供商系统
|
||||||
|
- **配置**: 每个提供商都有自己的配置模式
|
||||||
|
|
||||||
|
### 提供商能力
|
||||||
|
```typescript
|
||||||
|
interface OcrProviderCapabilityRecord {
|
||||||
|
image?: boolean // 图像文件 OCR 支持
|
||||||
|
pdf?: boolean // PDF 文件 OCR 支持(未来)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置架构
|
||||||
|
- **多态配置**: 基于 JSON 的配置适应提供商需求
|
||||||
|
- **类型安全**: Zod 模式验证提供商特定的配置
|
||||||
|
- **运行时验证**: OCR 操作前的配置验证
|
||||||
|
|
||||||
|
## 类型系统
|
||||||
|
|
||||||
|
### 核心类型
|
||||||
|
- **`OcrProvider`**: 基础提供商接口
|
||||||
|
- **`OcrParams`**: OCR 操作参数
|
||||||
|
- **`OcrResult`**: 标准化的 OCR 结果格式
|
||||||
|
- **`SupportedOcrFile`**: 支持 OCR 的文件类型
|
||||||
|
|
||||||
|
### 业务类型
|
||||||
|
- **`OcrProviderBusiness`**: 域级别的提供商表示
|
||||||
|
- **操作**: 创建、更新、替换、删除操作
|
||||||
|
- **查询**: 带过滤选项的提供商列表
|
||||||
|
|
||||||
|
### 提供商特定类型
|
||||||
|
- **TesseractConfig**: 语言选择、模型路径
|
||||||
|
- **SystemOcrConfig**: 语言偏好
|
||||||
|
- **PaddleOCRConfig**: API 端点、认证
|
||||||
|
- **OpenVINOConfig**: 设备选择、模型路径
|
||||||
|
|
||||||
|
## 内置提供商
|
||||||
|
|
||||||
|
### Tesseract OCR
|
||||||
|
- **引擎**: Tesseract.js
|
||||||
|
- **语言**: 支持多语言,自动下载
|
||||||
|
- **配置**: 语言选择、缓存管理
|
||||||
|
- **性能**: 工作池用于并发处理
|
||||||
|
|
||||||
|
### 系统 OCR
|
||||||
|
- **Windows**: Windows Media Foundation OCR
|
||||||
|
- **macOS**: Vision 框架 OCR
|
||||||
|
- **Linux**: 平台特定实现
|
||||||
|
- **特性**: 原生性能、系统集成
|
||||||
|
|
||||||
|
### PaddleOCR
|
||||||
|
- **部署**: 远程 API 集成
|
||||||
|
- **语言**: 中文、英文和混合语言支持
|
||||||
|
- **配置**: API 端点和认证
|
||||||
|
|
||||||
|
### Intel OpenVINO OCR
|
||||||
|
- **硬件**: NPU 加速支持
|
||||||
|
- **性能**: 为 Intel 硬件优化
|
||||||
|
- **用例**: 高性能 OCR 场景
|
||||||
|
|
||||||
|
## 配置管理
|
||||||
|
|
||||||
|
### 数据库架构
|
||||||
|
```sql
|
||||||
|
CREATE TABLE ocr_provider (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
capabilities TEXT NOT NULL, -- JSON
|
||||||
|
config TEXT NOT NULL, -- JSON
|
||||||
|
created_at INTEGER NOT NULL,
|
||||||
|
updated_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 提供商默认值
|
||||||
|
- **初始配置**: 在 `packages/shared/config/ocr.ts` 中定义
|
||||||
|
- **迁移系统**: 启动时自动提供商初始化
|
||||||
|
- **用户自定义**: 运行时配置更新
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
### 错误类别
|
||||||
|
- **提供商错误**: OCR 引擎故障、缺少依赖
|
||||||
|
- **配置错误**: 无效设置、缺少参数
|
||||||
|
- **文件错误**: 不支持的格式、损坏的文件
|
||||||
|
- **系统错误**: 资源耗尽、权限问题
|
||||||
|
|
||||||
|
### 错误传播
|
||||||
|
- **日志**: 带上下文的集中日志记录
|
||||||
|
- **用户反馈**: 翻译的错误消息
|
||||||
|
- **恢复**: 优雅的回退选项
|
||||||
|
|
||||||
|
## 性能考虑
|
||||||
|
|
||||||
|
### 资源管理
|
||||||
|
- **工作器销毁**: OCR 工作器的适当清理
|
||||||
|
- **内存管理**: 文件大小和并发操作限制
|
||||||
|
- **缓存**: 模型和结果缓存(如适用)
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
- **延迟加载**: 按需初始化提供商
|
||||||
|
- **并发处理**: 多工作器用于并行操作
|
||||||
|
- **硬件加速**: NPU 和 GPU 支持(如可用)
|
||||||
|
|
||||||
|
## 安全
|
||||||
|
|
||||||
|
### 输入验证
|
||||||
|
- **文件类型检查**: 严格验证支持的格式
|
||||||
|
- **大小限制**: 防止资源耗尽
|
||||||
|
- **路径验证**: 防止路径遍历攻击
|
||||||
|
|
||||||
|
### 配置安全
|
||||||
|
- **API 密钥存储**: 敏感配置的安全存储
|
||||||
|
- **验证**: 配置参数的运行时验证
|
||||||
|
- **沙盒**: OCR 操作的隔离执行
|
||||||
|
|
||||||
|
## 扩展点
|
||||||
|
|
||||||
|
### 自定义提供商
|
||||||
|
- **接口**: 为新提供商实现 `OcrBaseService`
|
||||||
|
- **注册**: 动态提供商注册系统
|
||||||
|
- **配置**: 可扩展的配置模式
|
||||||
|
|
||||||
|
### 文件类型支持
|
||||||
|
- **处理器**: 模块化文件类型处理器
|
||||||
|
- **能力**: 声明式提供商能力
|
||||||
|
- **未来支持**: PDF、文档格式计划中
|
||||||
|
|
||||||
|
## 迁移策略
|
||||||
|
|
||||||
|
### 遗留系统
|
||||||
|
- **数据迁移**: 从旧配置格式自动迁移
|
||||||
|
- **兼容性**: 过渡期间的向后兼容性
|
||||||
|
- **测试**: 迁移路径的全面测试覆盖
|
||||||
|
|
||||||
|
### 未来增强
|
||||||
|
- **PDF 支持**: 计划扩展到文档 OCR
|
||||||
|
- **云提供商**: 基于 API 的 OCR 服务集成
|
||||||
|
- **AI 增强**: 后处理和准确性改进
|
||||||
|
|
||||||
|
## 开发指南
|
||||||
|
|
||||||
|
### 添加新提供商
|
||||||
|
1. 创建扩展 `OcrBaseService` 的提供商服务
|
||||||
|
2. 定义提供商特定的配置模式
|
||||||
|
3. 在 `OcrService` 中注册提供商
|
||||||
|
4. 添加配置 UI 组件
|
||||||
|
5. 包含全面的测试
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> 提供商服务绝不应直接访问数据层。所有数据操作必须通过 OCR 服务层进行,以保持适当的关注点分离。
|
||||||
|
|
||||||
|
### 配置更改
|
||||||
|
1. 更新提供商配置模式
|
||||||
|
2. 为现有配置添加迁移逻辑
|
||||||
|
3. 更新 UI 验证和错误处理
|
||||||
|
4. 测试各种配置场景
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> 在保存到数据库之前,务必验证配置更改。使用 Zod 模式进行运行时验证,防止提供商配置损坏。
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
- **单元测试**: 提供商实现测试
|
||||||
|
- **集成测试**: 端到端 OCR 工作流
|
||||||
|
- **性能测试**: 资源使用和时间
|
||||||
|
- **错误场景**: 全面的错误处理测试
|
||||||
@@ -9,6 +9,7 @@ electronLanguages:
|
|||||||
- zh_CN # for macOS
|
- zh_CN # for macOS
|
||||||
- zh_TW # for macOS
|
- zh_TW # for macOS
|
||||||
- en # for macOS
|
- en # for macOS
|
||||||
|
- de
|
||||||
directories:
|
directories:
|
||||||
buildResources: build
|
buildResources: build
|
||||||
|
|
||||||
@@ -63,6 +64,9 @@ asarUnpack:
|
|||||||
- resources/**
|
- resources/**
|
||||||
- "**/*.{metal,exp,lib}"
|
- "**/*.{metal,exp,lib}"
|
||||||
- "node_modules/@img/sharp-libvips-*/**"
|
- "node_modules/@img/sharp-libvips-*/**"
|
||||||
|
extraResources:
|
||||||
|
- from: "migrations/sqlite-drizzle"
|
||||||
|
to: "migrations/sqlite-drizzle"
|
||||||
win:
|
win:
|
||||||
executableName: Cherry Studio
|
executableName: Cherry Studio
|
||||||
artifactName: ${productName}-${version}-${arch}-setup.${ext}
|
artifactName: ${productName}-${version}-${arch}-setup.${ext}
|
||||||
@@ -125,21 +129,61 @@ afterSign: scripts/notarize.js
|
|||||||
artifactBuildCompleted: scripts/artifact-build-completed.js
|
artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
What's New in v1.6.3
|
<!--LANG:en-->
|
||||||
|
What's New in v1.7.0-beta.2
|
||||||
|
|
||||||
Features:
|
New Features:
|
||||||
- Notes: Add spell-check control, automatic table line wrapping, export functionality, and LLM-based renaming
|
- Session Settings: Manage session-specific settings and model configurations independently
|
||||||
- UI: Expand topic rename clickable area, add middle-click tab closing, remove redundant scrollbars, fix message menubar overflow
|
- Notes Full-Text Search: Search across all notes with match highlighting
|
||||||
- Editor: Add read-only extension support, make TextFilePreview read-only but copyable
|
- Built-in DiDi MCP Server: Integration with DiDi ride-hailing services (China only)
|
||||||
- Models: Update support for DeepSeek v3.2, Claude 4.5, GLM 4.6, Gemini regex, and vision models
|
- Intel OV OCR: Hardware-accelerated OCR using Intel NPU
|
||||||
- Code Tools: Add GitHub Copilot CLI integration
|
- Auto-start API Server: Automatically starts when agents exist
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Agent model selection now requires explicit user choice
|
||||||
|
- Added Mistral AI provider support
|
||||||
|
- Added NewAPI generic provider support
|
||||||
|
- Improved navbar layout consistency across different modes
|
||||||
|
- Enhanced chat component responsiveness
|
||||||
|
- Better code block display on small screens
|
||||||
|
- Updated OVMS to 2025.3 official release
|
||||||
|
- Added Greek language support
|
||||||
|
|
||||||
Bug Fixes:
|
Bug Fixes:
|
||||||
- Fix migration for missing providers
|
- Fixed GitHub Copilot gpt-5-codex streaming issues
|
||||||
- Fix forked topic retaining old name after rename
|
- Fixed assistant creation failures
|
||||||
- Restore first token latency reporting in metrics
|
- Fixed translate auto-copy functionality
|
||||||
- Fix UI scrollbar and overflow issues
|
- Fixed miniapps external link opening
|
||||||
|
- Fixed message layout and overflow issues
|
||||||
|
- Fixed API key parsing to preserve spaces
|
||||||
|
- Fixed agent display in different navbar layouts
|
||||||
|
|
||||||
Technical Updates:
|
<!--LANG:zh-CN-->
|
||||||
- Upgrade to Electron 37.6.0
|
v1.7.0-beta.2 新特性
|
||||||
- Update dependencies across packages
|
|
||||||
|
新功能:
|
||||||
|
- 会话设置:独立管理会话特定的设置和模型配置
|
||||||
|
- 笔记全文搜索:跨所有笔记搜索并高亮匹配内容
|
||||||
|
- 内置滴滴 MCP 服务器:集成滴滴打车服务(仅限中国地区)
|
||||||
|
- Intel OV OCR:使用 Intel NPU 的硬件加速 OCR
|
||||||
|
- 自动启动 API 服务器:当存在 Agent 时自动启动
|
||||||
|
|
||||||
|
改进:
|
||||||
|
- Agent 模型选择现在需要用户显式选择
|
||||||
|
- 添加 Mistral AI 提供商支持
|
||||||
|
- 添加 NewAPI 通用提供商支持
|
||||||
|
- 改进不同模式下的导航栏布局一致性
|
||||||
|
- 增强聊天组件响应式设计
|
||||||
|
- 优化小屏幕代码块显示
|
||||||
|
- 更新 OVMS 至 2025.3 正式版
|
||||||
|
- 添加希腊语支持
|
||||||
|
|
||||||
|
问题修复:
|
||||||
|
- 修复 GitHub Copilot gpt-5-codex 流式传输问题
|
||||||
|
- 修复助手创建失败
|
||||||
|
- 修复翻译自动复制功能
|
||||||
|
- 修复小程序外部链接打开
|
||||||
|
- 修复消息布局和溢出问题
|
||||||
|
- 修复 API 密钥解析以保留空格
|
||||||
|
- 修复不同导航栏布局中的 Agent 显示
|
||||||
|
<!--LANG:END-->
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export default defineConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
'@main': resolve('src/main'),
|
'@main': resolve('src/main'),
|
||||||
'@types': resolve('src/renderer/src/types'),
|
'@types': resolve('src/renderer/src/types'),
|
||||||
|
'@data': resolve('src/main/data'),
|
||||||
'@shared': resolve('packages/shared'),
|
'@shared': resolve('packages/shared'),
|
||||||
'@logger': resolve('src/main/services/LoggerService'),
|
'@logger': resolve('src/main/services/LoggerService'),
|
||||||
'@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'),
|
'@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'),
|
||||||
@@ -61,7 +62,20 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
sourcemap: isDev
|
sourcemap: isDev,
|
||||||
|
rollupOptions: {
|
||||||
|
// Unlike renderer which auto-discovers entries from HTML files,
|
||||||
|
// preload requires explicit entry point configuration for multiple scripts
|
||||||
|
input: {
|
||||||
|
index: resolve(__dirname, 'src/preload/index.ts'),
|
||||||
|
simplest: resolve(__dirname, 'src/preload/simplest.ts') // Minimal preload
|
||||||
|
},
|
||||||
|
external: ['electron'],
|
||||||
|
output: {
|
||||||
|
entryFileNames: '[name].js',
|
||||||
|
format: 'cjs'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
@@ -90,12 +104,14 @@ export default defineConfig({
|
|||||||
'@shared': resolve('packages/shared'),
|
'@shared': resolve('packages/shared'),
|
||||||
'@types': resolve('src/renderer/src/types'),
|
'@types': resolve('src/renderer/src/types'),
|
||||||
'@logger': resolve('src/renderer/src/services/LoggerService'),
|
'@logger': resolve('src/renderer/src/services/LoggerService'),
|
||||||
|
'@data': resolve('src/renderer/src/data'),
|
||||||
'@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'),
|
'@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'),
|
||||||
'@mcp-trace/trace-web': resolve('packages/mcp-trace/trace-web'),
|
'@mcp-trace/trace-web': resolve('packages/mcp-trace/trace-web'),
|
||||||
'@cherrystudio/ai-core/provider': resolve('packages/aiCore/src/core/providers'),
|
'@cherrystudio/ai-core/provider': resolve('packages/aiCore/src/core/providers'),
|
||||||
'@cherrystudio/ai-core/built-in/plugins': resolve('packages/aiCore/src/core/plugins/built-in'),
|
'@cherrystudio/ai-core/built-in/plugins': resolve('packages/aiCore/src/core/plugins/built-in'),
|
||||||
'@cherrystudio/ai-core': resolve('packages/aiCore/src'),
|
'@cherrystudio/ai-core': resolve('packages/aiCore/src'),
|
||||||
'@cherrystudio/extension-table-plus': resolve('packages/extension-table-plus/src')
|
'@cherrystudio/extension-table-plus': resolve('packages/extension-table-plus/src'),
|
||||||
|
'@cherrystudio/ui': resolve('packages/ui/src')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
@@ -115,7 +131,8 @@ export default defineConfig({
|
|||||||
miniWindow: resolve(__dirname, 'src/renderer/miniWindow.html'),
|
miniWindow: resolve(__dirname, 'src/renderer/miniWindow.html'),
|
||||||
selectionToolbar: resolve(__dirname, 'src/renderer/selectionToolbar.html'),
|
selectionToolbar: resolve(__dirname, 'src/renderer/selectionToolbar.html'),
|
||||||
selectionAction: resolve(__dirname, 'src/renderer/selectionAction.html'),
|
selectionAction: resolve(__dirname, 'src/renderer/selectionAction.html'),
|
||||||
traceWindow: resolve(__dirname, 'src/renderer/traceWindow.html')
|
traceWindow: resolve(__dirname, 'src/renderer/traceWindow.html'),
|
||||||
|
dataRefactorMigrate: resolve(__dirname, 'src/renderer/dataRefactorMigrate.html')
|
||||||
},
|
},
|
||||||
onwarn(warning, warn) {
|
onwarn(warning, warn) {
|
||||||
if (warning.code === 'COMMONJS_VARIABLE_IN_ESM') return
|
if (warning.code === 'COMMONJS_VARIABLE_IN_ESM') return
|
||||||
|
|||||||
@@ -72,8 +72,9 @@ export default defineConfig([
|
|||||||
...oxlint.configs['flat/eslint'],
|
...oxlint.configs['flat/eslint'],
|
||||||
...oxlint.configs['flat/typescript'],
|
...oxlint.configs['flat/typescript'],
|
||||||
...oxlint.configs['flat/unicorn'],
|
...oxlint.configs['flat/unicorn'],
|
||||||
{
|
// Custom rules should be after oxlint to overwrite
|
||||||
// LoggerService Custom Rules - only apply to src directory
|
// LoggerService Custom Rules - only apply to src directory
|
||||||
|
{
|
||||||
files: ['src/**/*.{ts,tsx,js,jsx}'],
|
files: ['src/**/*.{ts,tsx,js,jsx}'],
|
||||||
ignores: ['src/**/__tests__/**', 'src/**/__mocks__/**', 'src/**/*.test.*', 'src/preload/**'],
|
ignores: ['src/**/__tests__/**', 'src/**/__mocks__/**', 'src/**/*.test.*', 'src/preload/**'],
|
||||||
rules: {
|
rules: {
|
||||||
@@ -87,6 +88,7 @@ export default defineConfig([
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// i18n
|
||||||
{
|
{
|
||||||
files: ['**/*.{ts,tsx,js,jsx}'],
|
files: ['**/*.{ts,tsx,js,jsx}'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
@@ -134,4 +136,30 @@ export default defineConfig([
|
|||||||
'i18n/no-template-in-t': 'warn'
|
'i18n/no-template-in-t': 'warn'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// ui migration
|
||||||
|
{
|
||||||
|
// Component Rules - prevent importing antd components when migration completed
|
||||||
|
files: ['**/*.{ts,tsx,js,jsx}'],
|
||||||
|
ignores: ['src/renderer/src/windows/dataRefactorTest/**/*.{ts,tsx}'],
|
||||||
|
rules: {
|
||||||
|
'no-restricted-imports': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
name: 'antd',
|
||||||
|
importNames: ['Flex', 'Switch', 'message', 'Button', 'Tooltip'],
|
||||||
|
message:
|
||||||
|
'❌ Do not import this component from antd. Use our custom components instead: import { ... } from "@cherrystudio/ui"'
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// name: '@heroui/react',
|
||||||
|
// message:
|
||||||
|
// '❌ Do not import components from heroui directly. Use our wrapped components instead: import { ... } from "@cherrystudio/ui"'
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|||||||
6
migrations/README.md
Normal file
6
migrations/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
**THIS DIRECTORY IS NOT FOR RUNTIME USE**
|
||||||
|
|
||||||
|
- Using `libsql` as the `sqlite3` driver, and `drizzle` as the ORM and database migration tool
|
||||||
|
- `migrations/sqlite-drizzle` contains auto-generated migration data. Please **DO NOT** modify it.
|
||||||
|
- If table structure changes, we should run migrations.
|
||||||
|
- To generate migrations, use the command `yarn run migrations:generate`
|
||||||
7
migrations/sqlite-drizzle.config.ts
Normal file
7
migrations/sqlite-drizzle.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'drizzle-kit'
|
||||||
|
export default defineConfig({
|
||||||
|
out: './migrations/sqlite-drizzle',
|
||||||
|
schema: './src/main/data/db/schemas/*',
|
||||||
|
dialect: 'sqlite',
|
||||||
|
casing: 'snake_case'
|
||||||
|
})
|
||||||
17
migrations/sqlite-drizzle/0000_solid_lord_hawal.sql
Normal file
17
migrations/sqlite-drizzle/0000_solid_lord_hawal.sql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
CREATE TABLE `app_state` (
|
||||||
|
`key` text PRIMARY KEY NOT NULL,
|
||||||
|
`value` text NOT NULL,
|
||||||
|
`description` text,
|
||||||
|
`created_at` integer,
|
||||||
|
`updated_at` integer
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `preference` (
|
||||||
|
`scope` text NOT NULL,
|
||||||
|
`key` text NOT NULL,
|
||||||
|
`value` text,
|
||||||
|
`created_at` integer,
|
||||||
|
`updated_at` integer
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `scope_name_idx` ON `preference` (`scope`,`key`);
|
||||||
10
migrations/sqlite-drizzle/0001_previous_sir_ram.sql
Normal file
10
migrations/sqlite-drizzle/0001_previous_sir_ram.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE `ocr_provider` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`capabilities` text NOT NULL,
|
||||||
|
`config` text NOT NULL,
|
||||||
|
`created_at` integer,
|
||||||
|
`updated_at` integer
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `name` ON `ocr_provider` (`name`);
|
||||||
114
migrations/sqlite-drizzle/meta/0000_snapshot.json
Normal file
114
migrations/sqlite-drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
},
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"enums": {},
|
||||||
|
"id": "de8009d7-95b9-4f99-99fa-4b8795708f21",
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
},
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"app_state": {
|
||||||
|
"checkConstraints": {},
|
||||||
|
"columns": {
|
||||||
|
"created_at": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "created_at",
|
||||||
|
"notNull": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "description",
|
||||||
|
"notNull": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "key",
|
||||||
|
"notNull": true,
|
||||||
|
"primaryKey": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "updated_at",
|
||||||
|
"notNull": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "value",
|
||||||
|
"notNull": true,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"indexes": {},
|
||||||
|
"name": "app_state",
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"preference": {
|
||||||
|
"checkConstraints": {},
|
||||||
|
"columns": {
|
||||||
|
"created_at": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "created_at",
|
||||||
|
"notNull": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "key",
|
||||||
|
"notNull": true,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "scope",
|
||||||
|
"notNull": true,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "updated_at",
|
||||||
|
"notNull": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"autoincrement": false,
|
||||||
|
"name": "value",
|
||||||
|
"notNull": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"indexes": {
|
||||||
|
"scope_name_idx": {
|
||||||
|
"columns": ["scope", "key"],
|
||||||
|
"isUnique": false,
|
||||||
|
"name": "scope_name_idx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "preference",
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "6",
|
||||||
|
"views": {}
|
||||||
|
}
|
||||||
172
migrations/sqlite-drizzle/meta/0001_snapshot.json
Normal file
172
migrations/sqlite-drizzle/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "64f7ad88-7111-4574-988c-d7ef429e375d",
|
||||||
|
"prevId": "de8009d7-95b9-4f99-99fa-4b8795708f21",
|
||||||
|
"tables": {
|
||||||
|
"app_state": {
|
||||||
|
"name": "app_state",
|
||||||
|
"columns": {
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"ocr_provider": {
|
||||||
|
"name": "ocr_provider",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"name": "capabilities",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"columns": ["name"],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"preference": {
|
||||||
|
"name": "preference",
|
||||||
|
"columns": {
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"name": "key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"scope_name_idx": {
|
||||||
|
"name": "scope_name_idx",
|
||||||
|
"columns": ["scope", "key"],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
migrations/sqlite-drizzle/meta/_journal.json
Normal file
20
migrations/sqlite-drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"breakpoints": true,
|
||||||
|
"idx": 0,
|
||||||
|
"tag": "0000_solid_lord_hawal",
|
||||||
|
"version": "6",
|
||||||
|
"when": 1754745234572
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1760969721294,
|
||||||
|
"tag": "0001_previous_sir_ram",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": "7"
|
||||||
|
}
|
||||||
53
package.json
53
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "CherryStudio",
|
"name": "CherryStudio",
|
||||||
"version": "1.7.0-alpha.5",
|
"version": "2.0.0-alpha",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A powerful AI assistant for producer.",
|
"description": "A powerful AI assistant for producer.",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
@@ -50,9 +50,10 @@
|
|||||||
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
|
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
|
||||||
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
||||||
"analyze:main": "VISUALIZER_MAIN=true yarn build",
|
"analyze:main": "VISUALIZER_MAIN=true yarn build",
|
||||||
"typecheck": "concurrently -n \"node,web\" -c \"cyan,magenta\" \"npm run typecheck:node\" \"npm run typecheck:web\"",
|
"typecheck": "concurrently -n \"node,web,ui\" -c \"cyan,magenta,green\" \"npm run typecheck:node\" \"npm run typecheck:web\" \"npm run typecheck:ui\"",
|
||||||
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
|
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
|
||||||
"typecheck:web": "tsgo --noEmit -p tsconfig.web.json --composite false",
|
"typecheck:web": "tsgo --noEmit -p tsconfig.web.json --composite false",
|
||||||
|
"typecheck:ui": "cd packages/ui && npm run type-check",
|
||||||
"check:i18n": "dotenv -e .env -- tsx scripts/check-i18n.ts",
|
"check:i18n": "dotenv -e .env -- tsx scripts/check-i18n.ts",
|
||||||
"sync:i18n": "dotenv -e .env -- tsx scripts/sync-i18n.ts",
|
"sync:i18n": "dotenv -e .env -- tsx scripts/sync-i18n.ts",
|
||||||
"update:i18n": "dotenv -e .env -- tsx scripts/update-i18n.ts",
|
"update:i18n": "dotenv -e .env -- tsx scripts/update-i18n.ts",
|
||||||
@@ -68,11 +69,13 @@
|
|||||||
"test:e2e": "yarn playwright test",
|
"test:e2e": "yarn playwright test",
|
||||||
"test:lint": "oxlint --deny-warnings && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --cache",
|
"test:lint": "oxlint --deny-warnings && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --cache",
|
||||||
"test:scripts": "vitest scripts",
|
"test:scripts": "vitest scripts",
|
||||||
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && yarn typecheck && yarn check:i18n && yarn format:check",
|
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && biome lint --write && biome format --write && yarn typecheck && yarn check:i18n && yarn format:check",
|
||||||
|
"lint:ox": "oxlint --fix && biome lint --write && biome format --write",
|
||||||
"format": "biome format --write && biome lint --write",
|
"format": "biome format --write && biome lint --write",
|
||||||
"format:check": "biome format && biome lint",
|
"format:check": "biome format && biome lint",
|
||||||
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
|
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
|
||||||
"claude": "dotenv -e .env -- claude",
|
"claude": "dotenv -e .env -- claude",
|
||||||
|
"migrations:generate": "drizzle-kit generate --config ./migrations/sqlite-drizzle.config.ts",
|
||||||
"release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public",
|
"release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public",
|
||||||
"release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public",
|
"release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --immediate && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public",
|
||||||
"release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core npm publish --access public"
|
"release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core npm publish --access public"
|
||||||
@@ -82,7 +85,9 @@
|
|||||||
"@libsql/client": "0.14.0",
|
"@libsql/client": "0.14.0",
|
||||||
"@libsql/win32-x64-msvc": "^0.4.7",
|
"@libsql/win32-x64-msvc": "^0.4.7",
|
||||||
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||||
|
"express": "^5.1.0",
|
||||||
"font-list": "^2.0.0",
|
"font-list": "^2.0.0",
|
||||||
"graceful-fs": "^4.2.11",
|
"graceful-fs": "^4.2.11",
|
||||||
"jsdom": "26.1.0",
|
"jsdom": "26.1.0",
|
||||||
@@ -90,8 +95,9 @@
|
|||||||
"officeparser": "^4.2.0",
|
"officeparser": "^4.2.0",
|
||||||
"os-proxy-config": "^1.1.2",
|
"os-proxy-config": "^1.1.2",
|
||||||
"selection-hook": "^1.0.12",
|
"selection-hook": "^1.0.12",
|
||||||
"sharp": "^0.34.3",
|
"sharp": "0.34.4",
|
||||||
"swagger-jsdoc": "^6.2.8",
|
"swagger-jsdoc": "^6.2.8",
|
||||||
|
"swagger-ui-express": "^5.0.1",
|
||||||
"tesseract.js": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
"tesseract.js": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
||||||
"turndown": "7.2.0"
|
"turndown": "7.2.0"
|
||||||
},
|
},
|
||||||
@@ -101,6 +107,7 @@
|
|||||||
"@agentic/tavily": "^7.3.3",
|
"@agentic/tavily": "^7.3.3",
|
||||||
"@ai-sdk/amazon-bedrock": "^3.0.35",
|
"@ai-sdk/amazon-bedrock": "^3.0.35",
|
||||||
"@ai-sdk/google-vertex": "^3.0.40",
|
"@ai-sdk/google-vertex": "^3.0.40",
|
||||||
|
"@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.4#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.4-8080836bc1.patch",
|
||||||
"@ai-sdk/mistral": "^2.0.19",
|
"@ai-sdk/mistral": "^2.0.19",
|
||||||
"@ai-sdk/perplexity": "^2.0.13",
|
"@ai-sdk/perplexity": "^2.0.13",
|
||||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||||
@@ -124,6 +131,8 @@
|
|||||||
"@cherrystudio/embedjs-ollama": "^0.1.31",
|
"@cherrystudio/embedjs-ollama": "^0.1.31",
|
||||||
"@cherrystudio/embedjs-openai": "^0.1.31",
|
"@cherrystudio/embedjs-openai": "^0.1.31",
|
||||||
"@cherrystudio/extension-table-plus": "workspace:^",
|
"@cherrystudio/extension-table-plus": "workspace:^",
|
||||||
|
"@cherrystudio/openai": "^6.5.0",
|
||||||
|
"@cherrystudio/ui": "workspace:*",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/modifiers": "^9.0.0",
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
@@ -139,20 +148,19 @@
|
|||||||
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
|
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@heroui/react": "^2.8.3",
|
"@heroui/react": "^2.8.3",
|
||||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
|
||||||
"@langchain/community": "^0.3.50",
|
"@langchain/community": "^0.3.50",
|
||||||
"@mistralai/mistralai": "^1.7.5",
|
"@mistralai/mistralai": "^1.7.5",
|
||||||
"@modelcontextprotocol/sdk": "^1.17.5",
|
"@modelcontextprotocol/sdk": "^1.17.5",
|
||||||
"@mozilla/readability": "^0.6.0",
|
"@mozilla/readability": "^0.6.0",
|
||||||
"@notionhq/client": "^2.2.15",
|
"@notionhq/client": "^2.2.15",
|
||||||
"@openrouter/ai-sdk-provider": "^1.1.2",
|
"@openrouter/ai-sdk-provider": "^1.2.0",
|
||||||
"@opentelemetry/api": "^1.9.0",
|
"@opentelemetry/api": "^1.9.0",
|
||||||
"@opentelemetry/core": "2.0.0",
|
"@opentelemetry/core": "2.0.0",
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
|
||||||
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
||||||
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
||||||
"@opentelemetry/sdk-trace-web": "^2.0.0",
|
"@opentelemetry/sdk-trace-web": "^2.0.0",
|
||||||
"@opeoginni/github-copilot-openai-compatible": "0.1.18",
|
"@opeoginni/github-copilot-openai-compatible": "0.1.19",
|
||||||
"@playwright/test": "^1.52.0",
|
"@playwright/test": "^1.52.0",
|
||||||
"@radix-ui/react-context-menu": "^2.2.16",
|
"@radix-ui/react-context-menu": "^2.2.16",
|
||||||
"@reduxjs/toolkit": "^2.2.5",
|
"@reduxjs/toolkit": "^2.2.5",
|
||||||
@@ -245,13 +253,13 @@
|
|||||||
"dotenv-cli": "^7.4.2",
|
"dotenv-cli": "^7.4.2",
|
||||||
"drizzle-kit": "^0.31.4",
|
"drizzle-kit": "^0.31.4",
|
||||||
"drizzle-orm": "^0.44.5",
|
"drizzle-orm": "^0.44.5",
|
||||||
"electron": "37.6.0",
|
"electron": "38.4.0",
|
||||||
"electron-builder": "26.0.15",
|
"electron-builder": "26.0.15",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-reload": "^2.0.0-alpha.1",
|
"electron-reload": "^2.0.0-alpha.1",
|
||||||
"electron-store": "^8.2.0",
|
"electron-store": "^8.2.0",
|
||||||
"electron-updater": "6.6.4",
|
"electron-updater": "6.6.4",
|
||||||
"electron-vite": "4.0.0",
|
"electron-vite": "4.0.1",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"emittery": "^1.0.3",
|
"emittery": "^1.0.3",
|
||||||
"emoji-picker-element": "^1.22.1",
|
"emoji-picker-element": "^1.22.1",
|
||||||
@@ -262,7 +270,6 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"express": "^5.1.0",
|
|
||||||
"express-validator": "^7.2.1",
|
"express-validator": "^7.2.1",
|
||||||
"fast-diff": "^1.3.0",
|
"fast-diff": "^1.3.0",
|
||||||
"fast-xml-parser": "^5.2.0",
|
"fast-xml-parser": "^5.2.0",
|
||||||
@@ -279,6 +286,7 @@
|
|||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"i18next": "^23.11.5",
|
"i18next": "^23.11.5",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
|
"ipaddr.js": "^2.2.0",
|
||||||
"isbinaryfile": "5.0.4",
|
"isbinaryfile": "5.0.4",
|
||||||
"jaison": "^2.0.2",
|
"jaison": "^2.0.2",
|
||||||
"jest-styled-components": "^7.2.0",
|
"jest-styled-components": "^7.2.0",
|
||||||
@@ -295,16 +303,15 @@
|
|||||||
"motion": "^12.10.5",
|
"motion": "^12.10.5",
|
||||||
"notion-helper": "^1.3.22",
|
"notion-helper": "^1.3.22",
|
||||||
"npx-scope-finder": "^1.2.0",
|
"npx-scope-finder": "^1.2.0",
|
||||||
"openai": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch",
|
|
||||||
"oxlint": "^1.22.0",
|
"oxlint": "^1.22.0",
|
||||||
"oxlint-tsgolint": "^0.2.0",
|
"oxlint-tsgolint": "^0.2.0",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdf-parse": "^1.1.1",
|
"pdf-parse": "^1.1.1",
|
||||||
"playwright": "^1.52.0",
|
"playwright": "^1.55.1",
|
||||||
"proxy-agent": "^6.5.0",
|
"proxy-agent": "^6.5.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-error-boundary": "^6.0.0",
|
"react-error-boundary": "^6.0.0",
|
||||||
"react-hotkeys-hook": "^4.6.1",
|
"react-hotkeys-hook": "^4.6.1",
|
||||||
"react-i18next": "^14.1.2",
|
"react-i18next": "^14.1.2",
|
||||||
@@ -336,7 +343,6 @@
|
|||||||
"string-width": "^7.2.0",
|
"string-width": "^7.2.0",
|
||||||
"striptags": "^3.2.0",
|
"striptags": "^3.2.0",
|
||||||
"styled-components": "^6.1.11",
|
"styled-components": "^6.1.11",
|
||||||
"swagger-ui-express": "^5.0.1",
|
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
"tailwindcss": "^4.1.13",
|
"tailwindcss": "^4.1.13",
|
||||||
"tar": "^7.4.3",
|
"tar": "^7.4.3",
|
||||||
@@ -349,7 +355,7 @@
|
|||||||
"undici": "6.21.2",
|
"undici": "6.21.2",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
"vite": "npm:rolldown-vite@latest",
|
"vite": "npm:rolldown-vite@7.1.5",
|
||||||
"vitest": "^3.2.4",
|
"vitest": "^3.2.4",
|
||||||
"webdav": "^5.8.0",
|
"webdav": "^5.8.0",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
@@ -376,15 +382,22 @@
|
|||||||
"file-stream-rotator@npm:^0.6.1": "patch:file-stream-rotator@npm%3A0.6.1#~/.yarn/patches/file-stream-rotator-npm-0.6.1-eab45fb13d.patch",
|
"file-stream-rotator@npm:^0.6.1": "patch:file-stream-rotator@npm%3A0.6.1#~/.yarn/patches/file-stream-rotator-npm-0.6.1-eab45fb13d.patch",
|
||||||
"libsql@npm:^0.4.4": "patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch",
|
"libsql@npm:^0.4.4": "patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch",
|
||||||
"node-abi": "4.12.0",
|
"node-abi": "4.12.0",
|
||||||
"openai@npm:^4.77.0": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch",
|
"openai@npm:^4.77.0": "npm:@cherrystudio/openai@6.5.0",
|
||||||
"openai@npm:^4.87.3": "patch:openai@npm%3A5.12.2#~/.yarn/patches/openai-npm-5.12.2-30b075401c.patch",
|
"openai@npm:^4.87.3": "npm:@cherrystudio/openai@6.5.0",
|
||||||
"pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
|
"pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
|
||||||
"pkce-challenge@npm:^4.1.0": "patch:pkce-challenge@npm%3A4.1.0#~/.yarn/patches/pkce-challenge-npm-4.1.0-fbc51695a3.patch",
|
"pkce-challenge@npm:^4.1.0": "patch:pkce-challenge@npm%3A4.1.0#~/.yarn/patches/pkce-challenge-npm-4.1.0-fbc51695a3.patch",
|
||||||
"tar-fs": "^2.1.4",
|
"tar-fs": "^2.1.4",
|
||||||
"undici": "6.21.2",
|
"undici": "6.21.2",
|
||||||
"vite": "npm:rolldown-vite@latest",
|
"vite": "npm:rolldown-vite@7.1.5",
|
||||||
"tesseract.js@npm:*": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
"tesseract.js@npm:*": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
||||||
"@ai-sdk/google@npm:2.0.20": "patch:@ai-sdk/google@npm%3A2.0.20#~/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch"
|
"@ai-sdk/google@npm:2.0.20": "patch:@ai-sdk/google@npm%3A2.0.20#~/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch",
|
||||||
|
"@img/sharp-darwin-arm64": "0.34.4",
|
||||||
|
"@img/sharp-darwin-x64": "0.34.4",
|
||||||
|
"@img/sharp-linux-arm": "0.34.4",
|
||||||
|
"@img/sharp-linux-arm64": "0.34.4",
|
||||||
|
"@img/sharp-linux-x64": "0.34.4",
|
||||||
|
"@img/sharp-win32-x64": "0.34.4",
|
||||||
|
"openai@npm:5.12.2": "npm:@cherrystudio/openai@6.5.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.9.1",
|
"packageManager": "yarn@4.9.1",
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* 中间件管理器
|
* 中间件管理器
|
||||||
* 专注于 AI SDK 中间件的管理,与插件系统分离
|
* 专注于 AI SDK 中间件的管理,与插件系统分离
|
||||||
*/
|
*/
|
||||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建中间件列表
|
* 创建中间件列表
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* 中间件系统类型定义
|
* 中间件系统类型定义
|
||||||
*/
|
*/
|
||||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 具名中间件接口
|
* 具名中间件接口
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* 模型包装工具函数
|
* 模型包装工具函数
|
||||||
* 用于将中间件应用到LanguageModel上
|
* 用于将中间件应用到LanguageModel上
|
||||||
*/
|
*/
|
||||||
import { LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
import type { LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||||
import { wrapLanguageModel } from 'ai'
|
import { wrapLanguageModel } from 'ai'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* 集成了来自 ModelCreator 的特殊处理逻辑
|
* 集成了来自 ModelCreator 的特殊处理逻辑
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EmbeddingModelV2, ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
import type { EmbeddingModelV2, ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||||
|
|
||||||
import { wrapModelWithMiddlewares } from '../middleware/wrapper'
|
import { wrapModelWithMiddlewares } from '../middleware/wrapper'
|
||||||
import { DEFAULT_SEPARATOR, globalRegistryManagement } from '../providers/RegistryManagement'
|
import { DEFAULT_SEPARATOR, globalRegistryManagement } from '../providers/RegistryManagement'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Creation 模块类型定义
|
* Creation 模块类型定义
|
||||||
*/
|
*/
|
||||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||||
|
|
||||||
import type { ProviderId, ProviderSettingsMap } from '../providers/types'
|
import type { ProviderId, ProviderSettingsMap } from '../providers/types'
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ExtractProviderOptions, ProviderOptionsMap, TypedProviderOptions } from './types'
|
import type { ExtractProviderOptions, ProviderOptionsMap, TypedProviderOptions } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建特定供应商的选项
|
* 创建特定供应商的选项
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { AiRequestContext } from '../../types'
|
|||||||
import { StreamEventManager } from './StreamEventManager'
|
import { StreamEventManager } from './StreamEventManager'
|
||||||
import { type TagConfig, TagExtractor } from './tagExtraction'
|
import { type TagConfig, TagExtractor } from './tagExtraction'
|
||||||
import { ToolExecutor } from './ToolExecutor'
|
import { ToolExecutor } from './ToolExecutor'
|
||||||
import { PromptToolUseConfig, ToolUseResult } from './type'
|
import type { PromptToolUseConfig, ToolUseResult } from './type'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具使用标签配置
|
* 工具使用标签配置
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ToolSet } from 'ai'
|
import type { ToolSet } from 'ai'
|
||||||
|
|
||||||
import { AiRequestContext } from '../..'
|
import type { AiRequestContext } from '../..'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析结果类型
|
* 解析结果类型
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { anthropic } from '@ai-sdk/anthropic'
|
import type { anthropic } from '@ai-sdk/anthropic'
|
||||||
import { google } from '@ai-sdk/google'
|
import type { google } from '@ai-sdk/google'
|
||||||
import { openai } from '@ai-sdk/openai'
|
import type { openai } from '@ai-sdk/openai'
|
||||||
import { InferToolInput, InferToolOutput, type Tool } from 'ai'
|
import type { InferToolInput, InferToolOutput, Tool } from 'ai'
|
||||||
|
|
||||||
import { ProviderOptionsMap } from '../../../options/types'
|
import type { ProviderOptionsMap } from '../../../options/types'
|
||||||
import { OpenRouterSearchConfig } from './openrouter'
|
import type { OpenRouterSearchConfig } from './openrouter'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 AI SDK 的工具函数中提取参数类型,以确保类型安全。
|
* 从 AI SDK 的工具函数中提取参数类型,以确保类型安全。
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import { openai } from '@ai-sdk/openai'
|
|||||||
import { createOpenRouterOptions, createXaiOptions, mergeProviderOptions } from '../../../options'
|
import { createOpenRouterOptions, createXaiOptions, mergeProviderOptions } from '../../../options'
|
||||||
import { definePlugin } from '../../'
|
import { definePlugin } from '../../'
|
||||||
import type { AiRequestContext } from '../../types'
|
import type { AiRequestContext } from '../../types'
|
||||||
import { DEFAULT_WEB_SEARCH_CONFIG, WebSearchPluginConfig } from './helper'
|
import type { WebSearchPluginConfig } from './helper'
|
||||||
|
import { DEFAULT_WEB_SEARCH_CONFIG } from './helper'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络搜索插件
|
* 网络搜索插件
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AiPlugin, AiRequestContext } from './types'
|
import type { AiPlugin, AiRequestContext } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* 例如: aihubmix:anthropic:claude-3.5-sonnet
|
* 例如: aihubmix:anthropic:claude-3.5-sonnet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ProviderV2 } from '@ai-sdk/provider'
|
import type { ProviderV2 } from '@ai-sdk/provider'
|
||||||
import { customProvider } from 'ai'
|
import { customProvider } from 'ai'
|
||||||
|
|
||||||
import { globalRegistryManagement } from './RegistryManagement'
|
import { globalRegistryManagement } from './RegistryManagement'
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* 基于 AI SDK 原生的 createProviderRegistry
|
* 基于 AI SDK 原生的 createProviderRegistry
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EmbeddingModelV2, ImageModelV2, LanguageModelV2, ProviderV2 } from '@ai-sdk/provider'
|
import type { EmbeddingModelV2, ImageModelV2, LanguageModelV2, ProviderV2 } from '@ai-sdk/provider'
|
||||||
import { createProviderRegistry, type ProviderRegistryProvider } from 'ai'
|
import { createProviderRegistry, type ProviderRegistryProvider } from 'ai'
|
||||||
|
|
||||||
type PROVIDERS = Record<string, ProviderV2>
|
type PROVIDERS = Record<string, ProviderV2>
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import { createAzure } from '@ai-sdk/azure'
|
|||||||
import { type AzureOpenAIProviderSettings } from '@ai-sdk/azure'
|
import { type AzureOpenAIProviderSettings } from '@ai-sdk/azure'
|
||||||
import { createDeepSeek } from '@ai-sdk/deepseek'
|
import { createDeepSeek } from '@ai-sdk/deepseek'
|
||||||
import { createGoogleGenerativeAI } from '@ai-sdk/google'
|
import { createGoogleGenerativeAI } from '@ai-sdk/google'
|
||||||
|
import { createHuggingFace } from '@ai-sdk/huggingface'
|
||||||
import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai'
|
import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
|
||||||
import { LanguageModelV2 } from '@ai-sdk/provider'
|
import type { LanguageModelV2 } from '@ai-sdk/provider'
|
||||||
import { createXai } from '@ai-sdk/xai'
|
import { createXai } from '@ai-sdk/xai'
|
||||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
|
||||||
import { customProvider, Provider } from 'ai'
|
import type { Provider } from 'ai'
|
||||||
|
import { customProvider } from 'ai'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,7 +30,8 @@ export const baseProviderIds = [
|
|||||||
'azure',
|
'azure',
|
||||||
'azure-responses',
|
'azure-responses',
|
||||||
'deepseek',
|
'deepseek',
|
||||||
'openrouter'
|
'openrouter',
|
||||||
|
'huggingface'
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,6 +135,12 @@ export const baseProviders = [
|
|||||||
name: 'OpenRouter',
|
name: 'OpenRouter',
|
||||||
creator: createOpenRouter,
|
creator: createOpenRouter,
|
||||||
supportsImageGeneration: true
|
supportsImageGeneration: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'huggingface',
|
||||||
|
name: 'HuggingFace',
|
||||||
|
creator: createHuggingFace,
|
||||||
|
supportsImageGeneration: true
|
||||||
}
|
}
|
||||||
] as const satisfies BaseProvider[]
|
] as const satisfies BaseProvider[]
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { type DeepSeekProviderSettings } from '@ai-sdk/deepseek'
|
|||||||
import { type GoogleGenerativeAIProviderSettings } from '@ai-sdk/google'
|
import { type GoogleGenerativeAIProviderSettings } from '@ai-sdk/google'
|
||||||
import { type OpenAIProviderSettings } from '@ai-sdk/openai'
|
import { type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||||
import { type OpenAICompatibleProviderSettings } from '@ai-sdk/openai-compatible'
|
import { type OpenAICompatibleProviderSettings } from '@ai-sdk/openai-compatible'
|
||||||
import {
|
import type {
|
||||||
EmbeddingModelV2 as EmbeddingModel,
|
EmbeddingModelV2 as EmbeddingModel,
|
||||||
ImageModelV2 as ImageModel,
|
ImageModelV2 as ImageModel,
|
||||||
LanguageModelV2 as LanguageModel,
|
LanguageModelV2 as LanguageModel,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ImageModelV2 } from '@ai-sdk/provider'
|
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||||
import { experimental_generateImage as aiGenerateImage, NoImageGeneratedError } from 'ai'
|
import { experimental_generateImage as aiGenerateImage, NoImageGeneratedError } from 'ai'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
* 运行时执行器
|
* 运行时执行器
|
||||||
* 专注于插件化的AI调用处理
|
* 专注于插件化的AI调用处理
|
||||||
*/
|
*/
|
||||||
import { ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
import type { ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||||
|
import type { LanguageModel } from 'ai'
|
||||||
import {
|
import {
|
||||||
experimental_generateImage as _generateImage,
|
experimental_generateImage as _generateImage,
|
||||||
generateObject as _generateObject,
|
generateObject as _generateObject,
|
||||||
generateText as _generateText,
|
generateText as _generateText,
|
||||||
LanguageModel,
|
|
||||||
streamObject as _streamObject,
|
streamObject as _streamObject,
|
||||||
streamText as _streamText
|
streamText as _streamText
|
||||||
} from 'ai'
|
} from 'ai'
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export type { RuntimeConfig } from './types'
|
|||||||
|
|
||||||
// === 便捷工厂函数 ===
|
// === 便捷工厂函数 ===
|
||||||
|
|
||||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||||
|
|
||||||
import { type AiPlugin } from '../plugins'
|
import { type AiPlugin } from '../plugins'
|
||||||
import { type ProviderId, type ProviderSettingsMap } from '../providers/types'
|
import { type ProviderId, type ProviderSettingsMap } from '../providers/types'
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
/* eslint-disable @eslint-react/naming-convention/context-name */
|
/* eslint-disable @eslint-react/naming-convention/context-name */
|
||||||
import { ImageModelV2 } from '@ai-sdk/provider'
|
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||||
import { experimental_generateImage, generateObject, generateText, LanguageModel, streamObject, streamText } from 'ai'
|
import type {
|
||||||
|
experimental_generateImage,
|
||||||
|
generateObject,
|
||||||
|
generateText,
|
||||||
|
LanguageModel,
|
||||||
|
streamObject,
|
||||||
|
streamText
|
||||||
|
} from 'ai'
|
||||||
|
|
||||||
import { type AiPlugin, createContext, PluginManager } from '../plugins'
|
import { type AiPlugin, createContext, PluginManager } from '../plugins'
|
||||||
import { type ProviderId } from '../providers/types'
|
import { type ProviderId } from '../providers/types'
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Runtime 层类型定义
|
* Runtime 层类型定义
|
||||||
*/
|
*/
|
||||||
import { ImageModelV2 } from '@ai-sdk/provider'
|
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||||
import { experimental_generateImage, generateObject, generateText, streamObject, streamText } from 'ai'
|
import type { experimental_generateImage, generateObject, generateText, streamObject, streamText } from 'ai'
|
||||||
|
|
||||||
import { type ModelConfig } from '../models/types'
|
import { type ModelConfig } from '../models/types'
|
||||||
import { type AiPlugin } from '../plugins'
|
import { type AiPlugin } from '../plugins'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Extension, Node } from '@tiptap/core'
|
import type { Node } from '@tiptap/core'
|
||||||
|
import { Extension } from '@tiptap/core'
|
||||||
|
|
||||||
import type { TableCellOptions } from '../cell/index.js'
|
import type { TableCellOptions } from '../cell/index.js'
|
||||||
import { TableCell } from '../cell/index.js'
|
import { TableCell } from '../cell/index.js'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
|
import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
|
||||||
import { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||||
|
|
||||||
import { SpanEntity } from '../types/config'
|
import type { SpanEntity } from '../types/config'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert ReadableSpan to SpanEntity
|
* convert ReadableSpan to SpanEntity
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||||
|
|
||||||
export interface TraceCache {
|
export interface TraceCache {
|
||||||
createSpan: (span: ReadableSpan) => void
|
createSpan: (span: ReadableSpan) => void
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ExportResult, ExportResultCode } from '@opentelemetry/core'
|
import type { ExportResult } from '@opentelemetry/core'
|
||||||
import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
import { ExportResultCode } from '@opentelemetry/core'
|
||||||
|
import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||||
|
|
||||||
export type SaveFunction = (spans: ReadableSpan[]) => Promise<void>
|
export type SaveFunction = (spans: ReadableSpan[]) => Promise<void>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Context, trace } from '@opentelemetry/api'
|
import type { Context } from '@opentelemetry/api'
|
||||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
import { trace } from '@opentelemetry/api'
|
||||||
|
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||||
|
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||||
|
|
||||||
import { TraceCache } from '../core/traceCache'
|
import type { TraceCache } from '../core/traceCache'
|
||||||
|
|
||||||
export class CacheBatchSpanProcessor extends BatchSpanProcessor {
|
export class CacheBatchSpanProcessor extends BatchSpanProcessor {
|
||||||
private cache: TraceCache
|
private cache: TraceCache
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Context } from '@opentelemetry/api'
|
import type { Context } from '@opentelemetry/api'
|
||||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||||
import { EventEmitter } from 'stream'
|
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||||
|
import type { EventEmitter } from 'stream'
|
||||||
|
|
||||||
import { convertSpanToSpanEntity } from '../core/spanConvert'
|
import { convertSpanToSpanEntity } from '../core/spanConvert'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Context, trace } from '@opentelemetry/api'
|
import type { Context } from '@opentelemetry/api'
|
||||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
import { trace } from '@opentelemetry/api'
|
||||||
|
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||||
|
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||||
|
|
||||||
export type SpanFunction = (span: ReadableSpan) => void
|
export type SpanFunction = (span: ReadableSpan) => void
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Link } from '@opentelemetry/api'
|
import type { Link } from '@opentelemetry/api'
|
||||||
import { TimedEvent } from '@opentelemetry/sdk-trace-base'
|
import type { TimedEvent } from '@opentelemetry/sdk-trace-base'
|
||||||
|
|
||||||
export type AttributeValue =
|
export type AttributeValue =
|
||||||
| string
|
| string
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { trace, Tracer } from '@opentelemetry/api'
|
import type { Tracer } from '@opentelemetry/api'
|
||||||
|
import { trace } from '@opentelemetry/api'
|
||||||
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'
|
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'
|
||||||
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
||||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
||||||
import { BatchSpanProcessor, ConsoleSpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||||
|
import { BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||||
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
||||||
|
|
||||||
import { defaultConfig, TraceConfig } from '../trace-core/types/config'
|
import type { TraceConfig } from '../trace-core/types/config'
|
||||||
|
import { defaultConfig } from '../trace-core/types/config'
|
||||||
|
|
||||||
export class NodeTracer {
|
export class NodeTracer {
|
||||||
private static provider: NodeTracerProvider
|
private static provider: NodeTracerProvider
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Context, ContextManager, ROOT_CONTEXT } from '@opentelemetry/api'
|
import type { Context, ContextManager } from '@opentelemetry/api'
|
||||||
|
import { ROOT_CONTEXT } from '@opentelemetry/api'
|
||||||
|
|
||||||
export class TopicContextManager implements ContextManager {
|
export class TopicContextManager implements ContextManager {
|
||||||
private topicContextStack: Map<string, Context[]>
|
private topicContextStack: Map<string, Context[]>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Context, context } from '@opentelemetry/api'
|
import type { Context } from '@opentelemetry/api'
|
||||||
|
import { context } from '@opentelemetry/api'
|
||||||
|
|
||||||
const originalPromise = globalThis.Promise
|
const originalPromise = globalThis.Promise
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
||||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
||||||
import { BatchSpanProcessor, ConsoleSpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||||
|
import { BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||||
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
|
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
|
||||||
|
|
||||||
import { defaultConfig, TraceConfig } from '../trace-core/types/config'
|
import type { TraceConfig } from '../trace-core/types/config'
|
||||||
|
import { defaultConfig } from '../trace-core/types/config'
|
||||||
import { TopicContextManager } from './TopicContextManager'
|
import { TopicContextManager } from './TopicContextManager'
|
||||||
|
|
||||||
export const contextManager = new TopicContextManager()
|
export const contextManager = new TopicContextManager()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export enum IpcChannel {
|
|||||||
App_GetCacheSize = 'app:get-cache-size',
|
App_GetCacheSize = 'app:get-cache-size',
|
||||||
App_ClearCache = 'app:clear-cache',
|
App_ClearCache = 'app:clear-cache',
|
||||||
App_SetLaunchOnBoot = 'app:set-launch-on-boot',
|
App_SetLaunchOnBoot = 'app:set-launch-on-boot',
|
||||||
App_SetLanguage = 'app:set-language',
|
// App_SetLanguage = 'app:set-language',
|
||||||
App_SetEnableSpellCheck = 'app:set-enable-spell-check',
|
App_SetEnableSpellCheck = 'app:set-enable-spell-check',
|
||||||
App_SetSpellCheckLanguages = 'app:set-spell-check-languages',
|
App_SetSpellCheckLanguages = 'app:set-spell-check-languages',
|
||||||
App_CheckForUpdate = 'app:check-for-update',
|
App_CheckForUpdate = 'app:check-for-update',
|
||||||
@@ -14,7 +14,7 @@ export enum IpcChannel {
|
|||||||
App_SetLaunchToTray = 'app:set-launch-to-tray',
|
App_SetLaunchToTray = 'app:set-launch-to-tray',
|
||||||
App_SetTray = 'app:set-tray',
|
App_SetTray = 'app:set-tray',
|
||||||
App_SetTrayOnClose = 'app:set-tray-on-close',
|
App_SetTrayOnClose = 'app:set-tray-on-close',
|
||||||
App_SetTheme = 'app:set-theme',
|
// App_SetTheme = 'app:set-theme',
|
||||||
App_SetAutoUpdate = 'app:set-auto-update',
|
App_SetAutoUpdate = 'app:set-auto-update',
|
||||||
App_SetTestPlan = 'app:set-test-plan',
|
App_SetTestPlan = 'app:set-test-plan',
|
||||||
App_SetTestChannel = 'app:set-test-channel',
|
App_SetTestChannel = 'app:set-test-channel',
|
||||||
@@ -46,7 +46,7 @@ export enum IpcChannel {
|
|||||||
App_MacRequestProcessTrust = 'app:mac-request-process-trust',
|
App_MacRequestProcessTrust = 'app:mac-request-process-trust',
|
||||||
|
|
||||||
App_QuoteToMain = 'app:quote-to-main',
|
App_QuoteToMain = 'app:quote-to-main',
|
||||||
App_SetDisableHardwareAcceleration = 'app:set-disable-hardware-acceleration',
|
// App_SetDisableHardwareAcceleration = 'app:set-disable-hardware-acceleration',
|
||||||
|
|
||||||
Notification_Send = 'notification:send',
|
Notification_Send = 'notification:send',
|
||||||
Notification_OnClick = 'notification:on-click',
|
Notification_OnClick = 'notification:on-click',
|
||||||
@@ -138,6 +138,7 @@ export enum IpcChannel {
|
|||||||
Windows_Close = 'window:close',
|
Windows_Close = 'window:close',
|
||||||
Windows_IsMaximized = 'window:is-maximized',
|
Windows_IsMaximized = 'window:is-maximized',
|
||||||
Windows_MaximizedChanged = 'window:maximized-changed',
|
Windows_MaximizedChanged = 'window:maximized-changed',
|
||||||
|
Windows_NavigateToAbout = 'window:navigate-to-about',
|
||||||
|
|
||||||
KnowledgeBase_Create = 'knowledge-base:create',
|
KnowledgeBase_Create = 'knowledge-base:create',
|
||||||
KnowledgeBase_Reset = 'knowledge-base:reset',
|
KnowledgeBase_Reset = 'knowledge-base:reset',
|
||||||
@@ -220,6 +221,22 @@ export enum IpcChannel {
|
|||||||
Backup_DeleteS3File = 'backup:deleteS3File',
|
Backup_DeleteS3File = 'backup:deleteS3File',
|
||||||
Backup_CheckS3Connection = 'backup:checkS3Connection',
|
Backup_CheckS3Connection = 'backup:checkS3Connection',
|
||||||
|
|
||||||
|
// data migration
|
||||||
|
DataMigrate_CheckNeeded = 'data-migrate:check-needed',
|
||||||
|
DataMigrate_GetProgress = 'data-migrate:get-progress',
|
||||||
|
DataMigrate_Cancel = 'data-migrate:cancel',
|
||||||
|
DataMigrate_RequireBackup = 'data-migrate:require-backup',
|
||||||
|
DataMigrate_BackupCompleted = 'data-migrate:backup-completed',
|
||||||
|
DataMigrate_ShowBackupDialog = 'data-migrate:show-backup-dialog',
|
||||||
|
DataMigrate_StartFlow = 'data-migrate:start-flow',
|
||||||
|
DataMigrate_ProceedToBackup = 'data-migrate:proceed-to-backup',
|
||||||
|
DataMigrate_StartMigration = 'data-migrate:start-migration',
|
||||||
|
DataMigrate_RetryMigration = 'data-migrate:retry-migration',
|
||||||
|
DataMigrate_RestartApp = 'data-migrate:restart-app',
|
||||||
|
DataMigrate_CloseWindow = 'data-migrate:close-window',
|
||||||
|
DataMigrate_SendReduxData = 'data-migrate:send-redux-data',
|
||||||
|
DataMigrate_GetReduxData = 'data-migrate:get-redux-data',
|
||||||
|
|
||||||
// zip
|
// zip
|
||||||
Zip_Compress = 'zip:compress',
|
Zip_Compress = 'zip:compress',
|
||||||
Zip_Decompress = 'zip:decompress',
|
Zip_Decompress = 'zip:decompress',
|
||||||
@@ -234,7 +251,8 @@ export enum IpcChannel {
|
|||||||
|
|
||||||
// events
|
// events
|
||||||
BackupProgress = 'backup-progress',
|
BackupProgress = 'backup-progress',
|
||||||
ThemeUpdated = 'theme:updated',
|
DataMigrateProgress = 'data-migrate-progress',
|
||||||
|
NativeThemeUpdated = 'native-theme:updated',
|
||||||
RestoreProgress = 'restore-progress',
|
RestoreProgress = 'restore-progress',
|
||||||
UpdateError = 'update-error',
|
UpdateError = 'update-error',
|
||||||
UpdateAvailable = 'update-available',
|
UpdateAvailable = 'update-available',
|
||||||
@@ -273,12 +291,6 @@ export enum IpcChannel {
|
|||||||
Selection_ToolbarVisibilityChange = 'selection:toolbar-visibility-change',
|
Selection_ToolbarVisibilityChange = 'selection:toolbar-visibility-change',
|
||||||
Selection_ToolbarDetermineSize = 'selection:toolbar-determine-size',
|
Selection_ToolbarDetermineSize = 'selection:toolbar-determine-size',
|
||||||
Selection_WriteToClipboard = 'selection:write-to-clipboard',
|
Selection_WriteToClipboard = 'selection:write-to-clipboard',
|
||||||
Selection_SetEnabled = 'selection:set-enabled',
|
|
||||||
Selection_SetTriggerMode = 'selection:set-trigger-mode',
|
|
||||||
Selection_SetFilterMode = 'selection:set-filter-mode',
|
|
||||||
Selection_SetFilterList = 'selection:set-filter-list',
|
|
||||||
Selection_SetFollowToolbar = 'selection:set-follow-toolbar',
|
|
||||||
Selection_SetRemeberWinSize = 'selection:set-remeber-win-size',
|
|
||||||
Selection_ActionWindowClose = 'selection:action-window-close',
|
Selection_ActionWindowClose = 'selection:action-window-close',
|
||||||
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
||||||
Selection_ActionWindowPin = 'selection:action-window-pin',
|
Selection_ActionWindowPin = 'selection:action-window-pin',
|
||||||
@@ -297,6 +309,27 @@ export enum IpcChannel {
|
|||||||
Memory_DeleteAllMemoriesForUser = 'memory:delete-all-memories-for-user',
|
Memory_DeleteAllMemoriesForUser = 'memory:delete-all-memories-for-user',
|
||||||
Memory_GetUsersList = 'memory:get-users-list',
|
Memory_GetUsersList = 'memory:get-users-list',
|
||||||
|
|
||||||
|
// Data: Preference
|
||||||
|
Preference_Get = 'preference:get',
|
||||||
|
Preference_Set = 'preference:set',
|
||||||
|
Preference_GetMultiple = 'preference:get-multiple',
|
||||||
|
Preference_SetMultiple = 'preference:set-multiple',
|
||||||
|
Preference_GetAll = 'preference:get-all',
|
||||||
|
Preference_Subscribe = 'preference:subscribe',
|
||||||
|
Preference_Changed = 'preference:changed',
|
||||||
|
|
||||||
|
// Data: Cache
|
||||||
|
Cache_Sync = 'cache:sync',
|
||||||
|
Cache_SyncBatch = 'cache:sync-batch',
|
||||||
|
|
||||||
|
// Data: API Channels
|
||||||
|
DataApi_Request = 'data-api:request',
|
||||||
|
DataApi_Batch = 'data-api:batch',
|
||||||
|
DataApi_Transaction = 'data-api:transaction',
|
||||||
|
DataApi_Subscribe = 'data-api:subscribe',
|
||||||
|
DataApi_Unsubscribe = 'data-api:unsubscribe',
|
||||||
|
DataApi_Stream = 'data-api:stream',
|
||||||
|
|
||||||
// TRACE
|
// TRACE
|
||||||
TRACE_SAVE_DATA = 'trace:saveData',
|
TRACE_SAVE_DATA = 'trace:saveData',
|
||||||
TRACE_GET_DATA = 'trace:getData',
|
TRACE_GET_DATA = 'trace:getData',
|
||||||
@@ -317,6 +350,7 @@ export enum IpcChannel {
|
|||||||
ApiServer_Stop = 'api-server:stop',
|
ApiServer_Stop = 'api-server:stop',
|
||||||
ApiServer_Restart = 'api-server:restart',
|
ApiServer_Restart = 'api-server:restart',
|
||||||
ApiServer_GetStatus = 'api-server:get-status',
|
ApiServer_GetStatus = 'api-server:get-status',
|
||||||
|
// NOTE: This api is not be used.
|
||||||
ApiServer_GetConfig = 'api-server:get-config',
|
ApiServer_GetConfig = 'api-server:get-config',
|
||||||
|
|
||||||
// Anthropic OAuth
|
// Anthropic OAuth
|
||||||
@@ -335,7 +369,7 @@ export enum IpcChannel {
|
|||||||
CodeTools_RemoveCustomTerminalPath = 'code-tools:remove-custom-terminal-path',
|
CodeTools_RemoveCustomTerminalPath = 'code-tools:remove-custom-terminal-path',
|
||||||
|
|
||||||
// OCR
|
// OCR
|
||||||
OCR_ocr = 'ocr:ocr',
|
OCR_Ocr = 'ocr:ocr',
|
||||||
|
|
||||||
// OVMS
|
// OVMS
|
||||||
Ovms_AddModel = 'ovms:add-model',
|
Ovms_AddModel = 'ovms:add-model',
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Anthropic from '@anthropic-ai/sdk'
|
import Anthropic from '@anthropic-ai/sdk'
|
||||||
import { TextBlockParam } from '@anthropic-ai/sdk/resources'
|
import type { TextBlockParam } from '@anthropic-ai/sdk/resources'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { Provider } from '@types'
|
import type { Provider } from '@types'
|
||||||
import type { ModelMessage } from 'ai'
|
import type { ModelMessage } from 'ai'
|
||||||
|
|
||||||
const logger = loggerService.withContext('anthropic-sdk')
|
const logger = loggerService.withContext('anthropic-sdk')
|
||||||
|
|||||||
@@ -197,11 +197,11 @@ export enum FeedUrl {
|
|||||||
GITHUB_LATEST = 'https://github.com/CherryHQ/cherry-studio/releases/latest/download'
|
GITHUB_LATEST = 'https://github.com/CherryHQ/cherry-studio/releases/latest/download'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UpgradeChannel {
|
// export enum UpgradeChannel {
|
||||||
LATEST = 'latest', // 最新稳定版本
|
// LATEST = 'latest', // 最新稳定版本
|
||||||
RC = 'rc', // 公测版本
|
// RC = 'rc', // 公测版本
|
||||||
BETA = 'beta' // 预览版本
|
// BETA = 'beta' // 预览版本
|
||||||
}
|
// }
|
||||||
|
|
||||||
export const defaultTimeout = 10 * 1000 * 60
|
export const defaultTimeout = 10 * 1000 * 60
|
||||||
|
|
||||||
|
|||||||
176
packages/shared/config/ocr.ts
Normal file
176
packages/shared/config/ocr.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import type {
|
||||||
|
BuiltinOcrProvider,
|
||||||
|
BuiltinOcrProviderId,
|
||||||
|
OcrOvProvider,
|
||||||
|
OcrPpocrProvider,
|
||||||
|
OcrSystemProvider,
|
||||||
|
OcrTesseractProvider,
|
||||||
|
TesseractLangCode
|
||||||
|
} from '@types'
|
||||||
|
|
||||||
|
import type { TranslateLanguageCode } from '../../../src/renderer/src/types/translate'
|
||||||
|
|
||||||
|
export const tesseract: OcrTesseractProvider = {
|
||||||
|
id: 'tesseract',
|
||||||
|
name: 'Tesseract',
|
||||||
|
capabilities: {
|
||||||
|
image: true
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
langs: {
|
||||||
|
chi_sim: true,
|
||||||
|
chi_tra: true,
|
||||||
|
eng: true
|
||||||
|
},
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const systemOcr: OcrSystemProvider = {
|
||||||
|
id: 'system',
|
||||||
|
name: 'System',
|
||||||
|
capabilities: {
|
||||||
|
image: true
|
||||||
|
// pdf: true
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
langs: ['en-us'],
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
} as const satisfies OcrSystemProvider
|
||||||
|
|
||||||
|
export const ppocrOcr: OcrPpocrProvider = {
|
||||||
|
id: 'paddleocr',
|
||||||
|
name: 'PaddleOCR',
|
||||||
|
capabilities: {
|
||||||
|
image: true
|
||||||
|
// pdf: true
|
||||||
|
},
|
||||||
|
config: { apiUrl: '', enabled: false }
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const ovOcr: OcrOvProvider = {
|
||||||
|
id: 'ovocr',
|
||||||
|
name: 'Intel OV(NPU) OCR',
|
||||||
|
capabilities: {
|
||||||
|
image: true
|
||||||
|
// pdf: true
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
} as const satisfies OcrOvProvider
|
||||||
|
|
||||||
|
export const INITIAL_BUILTIN_OCR_PROVIDER_MAP = {
|
||||||
|
tesseract,
|
||||||
|
system: systemOcr,
|
||||||
|
paddleocr: ppocrOcr,
|
||||||
|
ovocr: ovOcr
|
||||||
|
} as const satisfies Record<BuiltinOcrProviderId, BuiltinOcrProvider>
|
||||||
|
|
||||||
|
export const BUILTIN_OCR_PROVIDERS: BuiltinOcrProvider[] = Object.values(INITIAL_BUILTIN_OCR_PROVIDER_MAP)
|
||||||
|
|
||||||
|
export const TESSERACT_LANG_MAP: Record<TranslateLanguageCode, TesseractLangCode> = {
|
||||||
|
'af-za': 'afr',
|
||||||
|
'am-et': 'amh',
|
||||||
|
'ar-sa': 'ara',
|
||||||
|
'as-in': 'asm',
|
||||||
|
'az-az': 'aze',
|
||||||
|
'az-cyrl-az': 'aze_cyrl',
|
||||||
|
'be-by': 'bel',
|
||||||
|
'bn-bd': 'ben',
|
||||||
|
'bo-cn': 'bod',
|
||||||
|
'bs-ba': 'bos',
|
||||||
|
'bg-bg': 'bul',
|
||||||
|
'ca-es': 'cat',
|
||||||
|
'ceb-ph': 'ceb',
|
||||||
|
'cs-cz': 'ces',
|
||||||
|
'zh-cn': 'chi_sim',
|
||||||
|
'zh-tw': 'chi_tra',
|
||||||
|
'chr-us': 'chr',
|
||||||
|
'cy-gb': 'cym',
|
||||||
|
'da-dk': 'dan',
|
||||||
|
'de-de': 'deu',
|
||||||
|
'dz-bt': 'dzo',
|
||||||
|
'el-gr': 'ell',
|
||||||
|
'en-us': 'eng',
|
||||||
|
'enm-gb': 'enm',
|
||||||
|
'eo-world': 'epo',
|
||||||
|
'et-ee': 'est',
|
||||||
|
'eu-es': 'eus',
|
||||||
|
'fa-ir': 'fas',
|
||||||
|
'fi-fi': 'fin',
|
||||||
|
'fr-fr': 'fra',
|
||||||
|
'frk-de': 'frk',
|
||||||
|
'frm-fr': 'frm',
|
||||||
|
'ga-ie': 'gle',
|
||||||
|
'gl-es': 'glg',
|
||||||
|
'grc-gr': 'grc',
|
||||||
|
'gu-in': 'guj',
|
||||||
|
'ht-ht': 'hat',
|
||||||
|
'he-il': 'heb',
|
||||||
|
'hi-in': 'hin',
|
||||||
|
'hr-hr': 'hrv',
|
||||||
|
'hu-hu': 'hun',
|
||||||
|
'iu-ca': 'iku',
|
||||||
|
'id-id': 'ind',
|
||||||
|
'is-is': 'isl',
|
||||||
|
'it-it': 'ita',
|
||||||
|
'ita-it': 'ita_old',
|
||||||
|
'jv-id': 'jav',
|
||||||
|
'ja-jp': 'jpn',
|
||||||
|
'kn-in': 'kan',
|
||||||
|
'ka-ge': 'kat',
|
||||||
|
'kat-ge': 'kat_old',
|
||||||
|
'kk-kz': 'kaz',
|
||||||
|
'km-kh': 'khm',
|
||||||
|
'ky-kg': 'kir',
|
||||||
|
'ko-kr': 'kor',
|
||||||
|
'ku-tr': 'kur',
|
||||||
|
'la-la': 'lao',
|
||||||
|
'la-va': 'lat',
|
||||||
|
'lv-lv': 'lav',
|
||||||
|
'lt-lt': 'lit',
|
||||||
|
'ml-in': 'mal',
|
||||||
|
'mr-in': 'mar',
|
||||||
|
'mk-mk': 'mkd',
|
||||||
|
'mt-mt': 'mlt',
|
||||||
|
'ms-my': 'msa',
|
||||||
|
'my-mm': 'mya',
|
||||||
|
'ne-np': 'nep',
|
||||||
|
'nl-nl': 'nld',
|
||||||
|
'no-no': 'nor',
|
||||||
|
'or-in': 'ori',
|
||||||
|
'pa-in': 'pan',
|
||||||
|
'pl-pl': 'pol',
|
||||||
|
'pt-pt': 'por',
|
||||||
|
'ps-af': 'pus',
|
||||||
|
'ro-ro': 'ron',
|
||||||
|
'ru-ru': 'rus',
|
||||||
|
'sa-in': 'san',
|
||||||
|
'si-lk': 'sin',
|
||||||
|
'sk-sk': 'slk',
|
||||||
|
'sl-si': 'slv',
|
||||||
|
'es-es': 'spa',
|
||||||
|
'spa-es': 'spa_old',
|
||||||
|
'sq-al': 'sqi',
|
||||||
|
'sr-rs': 'srp',
|
||||||
|
'sr-latn-rs': 'srp_latn',
|
||||||
|
'sw-tz': 'swa',
|
||||||
|
'sv-se': 'swe',
|
||||||
|
'syr-sy': 'syr',
|
||||||
|
'ta-in': 'tam',
|
||||||
|
'te-in': 'tel',
|
||||||
|
'tg-tj': 'tgk',
|
||||||
|
'tl-ph': 'tgl',
|
||||||
|
'th-th': 'tha',
|
||||||
|
'ti-er': 'tir',
|
||||||
|
'tr-tr': 'tur',
|
||||||
|
'ug-cn': 'uig',
|
||||||
|
'uk-ua': 'ukr',
|
||||||
|
'ur-pk': 'urd',
|
||||||
|
'uz-uz': 'uzb',
|
||||||
|
'uz-cyrl-uz': 'uzb_cyrl',
|
||||||
|
'vi-vn': 'vie',
|
||||||
|
'yi-us': 'yid'
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ProcessingStatus } from '@types'
|
import type { ProcessingStatus } from '@types'
|
||||||
|
|
||||||
export type LoaderReturn = {
|
export type LoaderReturn = {
|
||||||
entriesAdded: number
|
entriesAdded: number
|
||||||
|
|||||||
106
packages/shared/data/README.md
Normal file
106
packages/shared/data/README.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Cherry Studio Shared Data
|
||||||
|
|
||||||
|
This directory contains shared type definitions and schemas for the Cherry Studio data management systems. These files provide type safety and consistency across the entire application.
|
||||||
|
|
||||||
|
## 📁 Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/shared/data/
|
||||||
|
├── api/ # Data API type system
|
||||||
|
│ ├── index.ts # Barrel exports for clean imports
|
||||||
|
│ ├── apiSchemas.ts # API endpoint definitions and mappings
|
||||||
|
│ ├── apiTypes.ts # Core request/response infrastructure types
|
||||||
|
│ ├── apiModels.ts # Business entity types and DTOs
|
||||||
|
│ ├── apiPaths.ts # API path definitions and utilities
|
||||||
|
│ └── errorCodes.ts # Standardized error handling
|
||||||
|
├── cache/ # Cache system type definitions
|
||||||
|
│ ├── cacheTypes.ts # Core cache infrastructure types
|
||||||
|
│ ├── cacheSchemas.ts # Cache key schemas and type mappings
|
||||||
|
│ └── cacheValueTypes.ts # Cache value type definitions
|
||||||
|
├── preference/ # Preference system type definitions
|
||||||
|
│ ├── preferenceTypes.ts # Core preference system types
|
||||||
|
│ └── preferenceSchemas.ts # Preference schemas and default values
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ System Overview
|
||||||
|
|
||||||
|
This directory provides type definitions for three main data management systems:
|
||||||
|
|
||||||
|
### API System (`api/`)
|
||||||
|
- **Purpose**: Type-safe IPC communication between Main and Renderer processes
|
||||||
|
- **Features**: RESTful patterns, error handling, business entity definitions
|
||||||
|
- **Usage**: Ensures type safety for all data API operations
|
||||||
|
|
||||||
|
### Cache System (`cache/`)
|
||||||
|
- **Purpose**: Type definitions for three-layer caching architecture
|
||||||
|
- **Features**: Memory/shared/persist cache schemas, TTL support, hook integration
|
||||||
|
- **Usage**: Type-safe caching operations across the application
|
||||||
|
|
||||||
|
### Preference System (`preference/`)
|
||||||
|
- **Purpose**: User configuration and settings management
|
||||||
|
- **Features**: 158 configuration items, default values, nested key support
|
||||||
|
- **Usage**: Type-safe preference access and synchronization
|
||||||
|
|
||||||
|
## 📋 File Categories
|
||||||
|
|
||||||
|
**Framework Infrastructure** - These are TypeScript type definitions that:
|
||||||
|
- ✅ Exist only at compile time
|
||||||
|
- ✅ Provide type safety and IntelliSense support
|
||||||
|
- ✅ Define contracts between application layers
|
||||||
|
- ✅ Enable static analysis and error detection
|
||||||
|
|
||||||
|
## 📖 Usage Examples
|
||||||
|
|
||||||
|
### API Types
|
||||||
|
```typescript
|
||||||
|
// Import API types
|
||||||
|
import type { DataRequest, DataResponse, ApiSchemas } from '@shared/data/api'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cache Types
|
||||||
|
```typescript
|
||||||
|
// Import cache types
|
||||||
|
import type { UseCacheKey, UseSharedCacheKey } from '@shared/data/cache'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preference Types
|
||||||
|
```typescript
|
||||||
|
// Import preference types
|
||||||
|
import type { PreferenceKeyType, PreferenceDefaultScopeType } from '@shared/data/preference'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Development Guidelines
|
||||||
|
|
||||||
|
### Adding Cache Types
|
||||||
|
1. Add cache key to `cache/cacheSchemas.ts`
|
||||||
|
2. Define value type in `cache/cacheValueTypes.ts`
|
||||||
|
3. Update type mappings for type safety
|
||||||
|
|
||||||
|
### Adding Preference Types
|
||||||
|
1. Add preference key to `preference/preferenceSchemas.ts`
|
||||||
|
2. Define default value and type
|
||||||
|
3. Preference system automatically picks up new keys
|
||||||
|
|
||||||
|
### Adding API Types
|
||||||
|
1. Define business entities in `api/apiModels.ts`
|
||||||
|
2. Add endpoint definitions to `api/apiSchemas.ts`
|
||||||
|
3. Export types from `api/index.ts`
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
- Use `import type` for type-only imports
|
||||||
|
- Follow existing naming conventions
|
||||||
|
- Document complex types with JSDoc
|
||||||
|
- Maintain type safety across all imports
|
||||||
|
|
||||||
|
## 🔗 Related Implementation
|
||||||
|
|
||||||
|
### Main Process Services
|
||||||
|
- `src/main/data/CacheService.ts` - Main process cache management
|
||||||
|
- `src/main/data/PreferenceService.ts` - Preference management service
|
||||||
|
- `src/main/data/DataApiService.ts` - Data API coordination service
|
||||||
|
|
||||||
|
### Renderer Process Services
|
||||||
|
- `src/renderer/src/data/CacheService.ts` - Renderer cache service
|
||||||
|
- `src/renderer/src/data/PreferenceService.ts` - Renderer preference service
|
||||||
|
- `src/renderer/src/data/DataApiService.ts` - Renderer API client
|
||||||
107
packages/shared/data/api/apiModels.ts
Normal file
107
packages/shared/data/api/apiModels.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* Generic test model definitions
|
||||||
|
* Contains flexible types for comprehensive API testing
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic test item entity - flexible structure for testing various scenarios
|
||||||
|
*/
|
||||||
|
export interface TestItem {
|
||||||
|
/** Unique identifier */
|
||||||
|
id: string
|
||||||
|
/** Item title */
|
||||||
|
title: string
|
||||||
|
/** Optional description */
|
||||||
|
description?: string
|
||||||
|
/** Type category */
|
||||||
|
type: string
|
||||||
|
/** Current status */
|
||||||
|
status: string
|
||||||
|
/** Priority level */
|
||||||
|
priority: string
|
||||||
|
/** Associated tags */
|
||||||
|
tags: string[]
|
||||||
|
/** Creation timestamp */
|
||||||
|
createdAt: string
|
||||||
|
/** Last update timestamp */
|
||||||
|
updatedAt: string
|
||||||
|
/** Additional metadata */
|
||||||
|
metadata: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Transfer Objects (DTOs) for test operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for creating a new test item
|
||||||
|
*/
|
||||||
|
export interface CreateTestItemDto {
|
||||||
|
/** Item title */
|
||||||
|
title: string
|
||||||
|
/** Optional description */
|
||||||
|
description?: string
|
||||||
|
/** Type category */
|
||||||
|
type?: string
|
||||||
|
/** Current status */
|
||||||
|
status?: string
|
||||||
|
/** Priority level */
|
||||||
|
priority?: string
|
||||||
|
/** Associated tags */
|
||||||
|
tags?: string[]
|
||||||
|
/** Additional metadata */
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for updating an existing test item
|
||||||
|
*/
|
||||||
|
export interface UpdateTestItemDto {
|
||||||
|
/** Updated title */
|
||||||
|
title?: string
|
||||||
|
/** Updated description */
|
||||||
|
description?: string
|
||||||
|
/** Updated type */
|
||||||
|
type?: string
|
||||||
|
/** Updated status */
|
||||||
|
status?: string
|
||||||
|
/** Updated priority */
|
||||||
|
priority?: string
|
||||||
|
/** Updated tags */
|
||||||
|
tags?: string[]
|
||||||
|
/** Updated metadata */
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk operation types for batch processing
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for bulk operations on multiple items
|
||||||
|
*/
|
||||||
|
export interface BulkOperationRequest<TData = any> {
|
||||||
|
/** Type of bulk operation to perform */
|
||||||
|
operation: 'create' | 'update' | 'delete' | 'archive' | 'restore'
|
||||||
|
/** Array of data items to process */
|
||||||
|
data: TData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response from a bulk operation
|
||||||
|
*/
|
||||||
|
export interface BulkOperationResponse {
|
||||||
|
/** Number of successfully processed items */
|
||||||
|
successful: number
|
||||||
|
/** Number of items that failed processing */
|
||||||
|
failed: number
|
||||||
|
/** Array of errors that occurred during processing */
|
||||||
|
errors: Array<{
|
||||||
|
/** Index of the item that failed */
|
||||||
|
index: number
|
||||||
|
/** Error message */
|
||||||
|
error: string
|
||||||
|
/** Optional additional error data */
|
||||||
|
data?: any
|
||||||
|
}>
|
||||||
|
}
|
||||||
60
packages/shared/data/api/apiPaths.ts
Normal file
60
packages/shared/data/api/apiPaths.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import type { ApiSchemas } from './apiSchemas'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template literal type utilities for converting parameterized paths to concrete paths
|
||||||
|
* This enables type-safe API calls with actual paths like '/test/items/123' instead of '/test/items/:id'
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert parameterized path templates to concrete path types
|
||||||
|
* @example '/test/items/:id' -> '/test/items/${string}'
|
||||||
|
* @example '/topics/:id/messages' -> '/topics/${string}/messages'
|
||||||
|
*/
|
||||||
|
export type ResolvedPath<T extends string> = T extends `${infer Prefix}/:${string}/${infer Suffix}`
|
||||||
|
? `${Prefix}/${string}/${ResolvedPath<Suffix>}`
|
||||||
|
: T extends `${infer Prefix}/:${string}`
|
||||||
|
? `${Prefix}/${string}`
|
||||||
|
: T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate all possible concrete paths from ApiSchemas
|
||||||
|
* This creates a union type of all valid API paths
|
||||||
|
*/
|
||||||
|
export type ConcreteApiPaths = {
|
||||||
|
[K in keyof ApiSchemas]: ResolvedPath<K & string>
|
||||||
|
}[keyof ApiSchemas]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse lookup: from concrete path back to original template path
|
||||||
|
* Used to determine which ApiSchema entry matches a concrete path
|
||||||
|
*/
|
||||||
|
export type MatchApiPath<Path extends string> = {
|
||||||
|
[K in keyof ApiSchemas]: Path extends ResolvedPath<K & string> ? K : never
|
||||||
|
}[keyof ApiSchemas]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract query parameters type for a given concrete path
|
||||||
|
*/
|
||||||
|
export type QueryParamsForPath<Path extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||||
|
? ApiSchemas[MatchApiPath<Path>] extends { GET: { query?: infer Q } }
|
||||||
|
? Q
|
||||||
|
: Record<string, any>
|
||||||
|
: Record<string, any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract request body type for a given concrete path and HTTP method
|
||||||
|
*/
|
||||||
|
export type BodyForPath<Path extends string, Method extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||||
|
? ApiSchemas[MatchApiPath<Path>] extends { [M in Method]: { body: infer B } }
|
||||||
|
? B
|
||||||
|
: any
|
||||||
|
: any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract response type for a given concrete path and HTTP method
|
||||||
|
*/
|
||||||
|
export type ResponseForPath<Path extends string, Method extends string> = MatchApiPath<Path> extends keyof ApiSchemas
|
||||||
|
? ApiSchemas[MatchApiPath<Path>] extends { [M in Method]: { response: infer R } }
|
||||||
|
? R
|
||||||
|
: any
|
||||||
|
: any
|
||||||
532
packages/shared/data/api/apiSchemas.ts
Normal file
532
packages/shared/data/api/apiSchemas.ts
Normal file
@@ -0,0 +1,532 @@
|
|||||||
|
// NOTE: Types are defined inline in the schema for simplicity
|
||||||
|
// If needed, specific types can be imported from './apiModels'
|
||||||
|
import type {
|
||||||
|
CreateOcrProviderRequest,
|
||||||
|
CreateOcrProviderResponse,
|
||||||
|
GetOcrProviderResponse,
|
||||||
|
ListOcrProvidersQuery,
|
||||||
|
ListOcrProvidersResponse,
|
||||||
|
OcrProviderId,
|
||||||
|
ReplaceOcrProviderRequest,
|
||||||
|
ReplaceOcrProviderResponse,
|
||||||
|
UpdateOcrProviderRequest,
|
||||||
|
UpdateOcrProviderResponse
|
||||||
|
} from '@types'
|
||||||
|
|
||||||
|
import type { BodyForPath, ConcreteApiPaths, QueryParamsForPath, ResponseForPath } from './apiPaths'
|
||||||
|
import type { HttpMethod, PaginatedResponse, PaginationParams } from './apiTypes'
|
||||||
|
|
||||||
|
// Re-export for external use
|
||||||
|
export type { ConcreteApiPaths } from './apiPaths'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete API Schema definitions for Test API
|
||||||
|
*
|
||||||
|
* Each path defines the supported HTTP methods with their:
|
||||||
|
* - Request parameters (params, query, body)
|
||||||
|
* - Response types
|
||||||
|
* - Type safety guarantees
|
||||||
|
*
|
||||||
|
* This schema serves as the contract between renderer and main processes,
|
||||||
|
* enabling full TypeScript type checking across IPC boundaries.
|
||||||
|
*/
|
||||||
|
export interface ApiSchemas {
|
||||||
|
/**
|
||||||
|
* Test items collection endpoint
|
||||||
|
* @example GET /test/items?page=1&limit=10&search=hello
|
||||||
|
* @example POST /test/items { "title": "New Test Item" }
|
||||||
|
*/
|
||||||
|
'/test/items': {
|
||||||
|
/** List all test items with optional filtering and pagination */
|
||||||
|
GET: {
|
||||||
|
query?: PaginationParams & {
|
||||||
|
/** Search items by title or description */
|
||||||
|
search?: string
|
||||||
|
/** Filter by item type */
|
||||||
|
type?: string
|
||||||
|
/** Filter by status */
|
||||||
|
status?: string
|
||||||
|
}
|
||||||
|
response: PaginatedResponse<any>
|
||||||
|
}
|
||||||
|
/** Create a new test item */
|
||||||
|
POST: {
|
||||||
|
body: {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
type?: string
|
||||||
|
status?: string
|
||||||
|
priority?: string
|
||||||
|
tags?: string[]
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
|
response: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Individual test item endpoint
|
||||||
|
* @example GET /test/items/123
|
||||||
|
* @example PUT /test/items/123 { "title": "Updated Title" }
|
||||||
|
* @example DELETE /test/items/123
|
||||||
|
*/
|
||||||
|
'/test/items/:id': {
|
||||||
|
/** Get a specific test item by ID */
|
||||||
|
GET: {
|
||||||
|
params: { id: string }
|
||||||
|
response: any
|
||||||
|
}
|
||||||
|
/** Update a specific test item */
|
||||||
|
PUT: {
|
||||||
|
params: { id: string }
|
||||||
|
body: {
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
type?: string
|
||||||
|
status?: string
|
||||||
|
priority?: string
|
||||||
|
tags?: string[]
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
|
response: any
|
||||||
|
}
|
||||||
|
/** Delete a specific test item */
|
||||||
|
DELETE: {
|
||||||
|
params: { id: string }
|
||||||
|
response: void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test search endpoint
|
||||||
|
* @example GET /test/search?query=hello&page=1&limit=20
|
||||||
|
*/
|
||||||
|
'/test/search': {
|
||||||
|
/** Search test items */
|
||||||
|
GET: {
|
||||||
|
query: {
|
||||||
|
/** Search query string */
|
||||||
|
query: string
|
||||||
|
/** Page number for pagination */
|
||||||
|
page?: number
|
||||||
|
/** Number of results per page */
|
||||||
|
limit?: number
|
||||||
|
/** Additional filters */
|
||||||
|
type?: string
|
||||||
|
status?: string
|
||||||
|
}
|
||||||
|
response: PaginatedResponse<any>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test statistics endpoint
|
||||||
|
* @example GET /test/stats
|
||||||
|
*/
|
||||||
|
'/test/stats': {
|
||||||
|
/** Get comprehensive test statistics */
|
||||||
|
GET: {
|
||||||
|
response: {
|
||||||
|
/** Total number of items */
|
||||||
|
total: number
|
||||||
|
/** Item count grouped by type */
|
||||||
|
byType: Record<string, number>
|
||||||
|
/** Item count grouped by status */
|
||||||
|
byStatus: Record<string, number>
|
||||||
|
/** Item count grouped by priority */
|
||||||
|
byPriority: Record<string, number>
|
||||||
|
/** Recent activity timeline */
|
||||||
|
recentActivity: Array<{
|
||||||
|
/** Date of activity */
|
||||||
|
date: string
|
||||||
|
/** Number of items on that date */
|
||||||
|
count: number
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test bulk operations endpoint
|
||||||
|
* @example POST /test/bulk { "operation": "create", "data": [...] }
|
||||||
|
*/
|
||||||
|
'/test/bulk': {
|
||||||
|
/** Perform bulk operations on test items */
|
||||||
|
POST: {
|
||||||
|
body: {
|
||||||
|
/** Operation type */
|
||||||
|
operation: 'create' | 'update' | 'delete'
|
||||||
|
/** Array of data items to process */
|
||||||
|
data: any[]
|
||||||
|
}
|
||||||
|
response: {
|
||||||
|
successful: number
|
||||||
|
failed: number
|
||||||
|
errors: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test error simulation endpoint
|
||||||
|
* @example POST /test/error { "errorType": "timeout" }
|
||||||
|
*/
|
||||||
|
'/test/error': {
|
||||||
|
/** Simulate various error scenarios for testing */
|
||||||
|
POST: {
|
||||||
|
body: {
|
||||||
|
/** Type of error to simulate */
|
||||||
|
errorType:
|
||||||
|
| 'timeout'
|
||||||
|
| 'network'
|
||||||
|
| 'server'
|
||||||
|
| 'notfound'
|
||||||
|
| 'validation'
|
||||||
|
| 'unauthorized'
|
||||||
|
| 'ratelimit'
|
||||||
|
| 'generic'
|
||||||
|
}
|
||||||
|
response: never
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test slow response endpoint
|
||||||
|
* @example POST /test/slow { "delay": 2000 }
|
||||||
|
*/
|
||||||
|
'/test/slow': {
|
||||||
|
/** Test slow response for performance testing */
|
||||||
|
POST: {
|
||||||
|
body: {
|
||||||
|
/** Delay in milliseconds */
|
||||||
|
delay: number
|
||||||
|
}
|
||||||
|
response: {
|
||||||
|
message: string
|
||||||
|
delay: number
|
||||||
|
timestamp: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test data reset endpoint
|
||||||
|
* @example POST /test/reset
|
||||||
|
*/
|
||||||
|
'/test/reset': {
|
||||||
|
/** Reset all test data to initial state */
|
||||||
|
POST: {
|
||||||
|
response: {
|
||||||
|
message: string
|
||||||
|
timestamp: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test config endpoint
|
||||||
|
* @example GET /test/config
|
||||||
|
* @example PUT /test/config { "setting": "value" }
|
||||||
|
*/
|
||||||
|
'/test/config': {
|
||||||
|
/** Get test configuration */
|
||||||
|
GET: {
|
||||||
|
response: Record<string, any>
|
||||||
|
}
|
||||||
|
/** Update test configuration */
|
||||||
|
PUT: {
|
||||||
|
body: Record<string, any>
|
||||||
|
response: Record<string, any>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test status endpoint
|
||||||
|
* @example GET /test/status
|
||||||
|
*/
|
||||||
|
'/test/status': {
|
||||||
|
/** Get system test status */
|
||||||
|
GET: {
|
||||||
|
response: {
|
||||||
|
status: string
|
||||||
|
timestamp: string
|
||||||
|
version: string
|
||||||
|
uptime: number
|
||||||
|
environment: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test performance endpoint
|
||||||
|
* @example GET /test/performance
|
||||||
|
*/
|
||||||
|
'/test/performance': {
|
||||||
|
/** Get performance metrics */
|
||||||
|
GET: {
|
||||||
|
response: {
|
||||||
|
requestsPerSecond: number
|
||||||
|
averageLatency: number
|
||||||
|
memoryUsage: number
|
||||||
|
cpuUsage: number
|
||||||
|
uptime: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch execution of multiple requests
|
||||||
|
* @example POST /batch { "requests": [...], "parallel": true }
|
||||||
|
*/
|
||||||
|
'/batch': {
|
||||||
|
/** Execute multiple API requests in a single call */
|
||||||
|
POST: {
|
||||||
|
body: {
|
||||||
|
/** Array of requests to execute */
|
||||||
|
requests: Array<{
|
||||||
|
/** HTTP method for the request */
|
||||||
|
method: HttpMethod
|
||||||
|
/** API path for the request */
|
||||||
|
path: string
|
||||||
|
/** URL parameters */
|
||||||
|
params?: any
|
||||||
|
/** Request body */
|
||||||
|
body?: any
|
||||||
|
}>
|
||||||
|
/** Execute requests in parallel vs sequential */
|
||||||
|
parallel?: boolean
|
||||||
|
}
|
||||||
|
response: {
|
||||||
|
/** Results array matching input order */
|
||||||
|
results: Array<{
|
||||||
|
/** HTTP status code */
|
||||||
|
status: number
|
||||||
|
/** Response data if successful */
|
||||||
|
data?: any
|
||||||
|
/** Error information if failed */
|
||||||
|
error?: any
|
||||||
|
}>
|
||||||
|
/** Batch execution metadata */
|
||||||
|
metadata: {
|
||||||
|
/** Total execution duration in ms */
|
||||||
|
duration: number
|
||||||
|
/** Number of successful requests */
|
||||||
|
successCount: number
|
||||||
|
/** Number of failed requests */
|
||||||
|
errorCount: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomic transaction of multiple operations
|
||||||
|
* @example POST /transaction { "operations": [...], "options": { "rollbackOnError": true } }
|
||||||
|
*/
|
||||||
|
'/transaction': {
|
||||||
|
/** Execute multiple operations in a database transaction */
|
||||||
|
POST: {
|
||||||
|
body: {
|
||||||
|
/** Array of operations to execute atomically */
|
||||||
|
operations: Array<{
|
||||||
|
/** HTTP method for the operation */
|
||||||
|
method: HttpMethod
|
||||||
|
/** API path for the operation */
|
||||||
|
path: string
|
||||||
|
/** URL parameters */
|
||||||
|
params?: any
|
||||||
|
/** Request body */
|
||||||
|
body?: any
|
||||||
|
}>
|
||||||
|
/** Transaction configuration options */
|
||||||
|
options?: {
|
||||||
|
/** Database isolation level */
|
||||||
|
isolation?: 'read-uncommitted' | 'read-committed' | 'repeatable-read' | 'serializable'
|
||||||
|
/** Rollback all operations on any error */
|
||||||
|
rollbackOnError?: boolean
|
||||||
|
/** Transaction timeout in milliseconds */
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response: Array<{
|
||||||
|
/** HTTP status code */
|
||||||
|
status: number
|
||||||
|
/** Response data if successful */
|
||||||
|
data?: any
|
||||||
|
/** Error information if failed */
|
||||||
|
error?: any
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'/ocr/providers': {
|
||||||
|
GET: {
|
||||||
|
query: ListOcrProvidersQuery
|
||||||
|
response: ListOcrProvidersResponse
|
||||||
|
}
|
||||||
|
POST: {
|
||||||
|
body: CreateOcrProviderRequest
|
||||||
|
response: CreateOcrProviderResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
'/ocr/providers/:id': {
|
||||||
|
GET: {
|
||||||
|
params: { id: OcrProviderId }
|
||||||
|
response: GetOcrProviderResponse
|
||||||
|
}
|
||||||
|
PATCH: {
|
||||||
|
params: { id: OcrProviderId }
|
||||||
|
body: UpdateOcrProviderRequest
|
||||||
|
response: UpdateOcrProviderResponse
|
||||||
|
}
|
||||||
|
PUT: {
|
||||||
|
params: { id: OcrProviderId }
|
||||||
|
body: ReplaceOcrProviderRequest
|
||||||
|
response: ReplaceOcrProviderResponse
|
||||||
|
}
|
||||||
|
DELETE: {
|
||||||
|
params: { id: OcrProviderId }
|
||||||
|
response: void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified type extraction helpers
|
||||||
|
*/
|
||||||
|
export type ApiPaths = keyof ApiSchemas
|
||||||
|
export type ApiMethods<TPath extends ApiPaths> = keyof ApiSchemas[TPath] & HttpMethod
|
||||||
|
export type ApiResponse<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
|
||||||
|
? TMethod extends keyof ApiSchemas[TPath]
|
||||||
|
? ApiSchemas[TPath][TMethod] extends { response: infer R }
|
||||||
|
? R
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type ApiParams<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
|
||||||
|
? TMethod extends keyof ApiSchemas[TPath]
|
||||||
|
? ApiSchemas[TPath][TMethod] extends { params: infer P }
|
||||||
|
? P
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type ApiQuery<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
|
||||||
|
? TMethod extends keyof ApiSchemas[TPath]
|
||||||
|
? ApiSchemas[TPath][TMethod] extends { query: infer Q }
|
||||||
|
? Q
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type ApiBody<TPath extends ApiPaths, TMethod extends string> = TPath extends keyof ApiSchemas
|
||||||
|
? TMethod extends keyof ApiSchemas[TPath]
|
||||||
|
? ApiSchemas[TPath][TMethod] extends { body: infer B }
|
||||||
|
? B
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe API client interface using concrete paths
|
||||||
|
* Accepts actual paths like '/test/items/123' instead of '/test/items/:id'
|
||||||
|
* Automatically infers query, body, and response types from ApiSchemas
|
||||||
|
*/
|
||||||
|
export interface ApiClient {
|
||||||
|
get<TPath extends ConcreteApiPaths>(
|
||||||
|
path: TPath,
|
||||||
|
options?: {
|
||||||
|
query?: QueryParamsForPath<TPath>
|
||||||
|
headers?: Record<string, string>
|
||||||
|
}
|
||||||
|
): Promise<ResponseForPath<TPath, 'GET'>>
|
||||||
|
|
||||||
|
post<TPath extends ConcreteApiPaths>(
|
||||||
|
path: TPath,
|
||||||
|
options: {
|
||||||
|
body?: BodyForPath<TPath, 'POST'>
|
||||||
|
query?: Record<string, any>
|
||||||
|
headers?: Record<string, string>
|
||||||
|
}
|
||||||
|
): Promise<ResponseForPath<TPath, 'POST'>>
|
||||||
|
|
||||||
|
put<TPath extends ConcreteApiPaths>(
|
||||||
|
path: TPath,
|
||||||
|
options: {
|
||||||
|
body: BodyForPath<TPath, 'PUT'>
|
||||||
|
query?: Record<string, any>
|
||||||
|
headers?: Record<string, string>
|
||||||
|
}
|
||||||
|
): Promise<ResponseForPath<TPath, 'PUT'>>
|
||||||
|
|
||||||
|
delete<TPath extends ConcreteApiPaths>(
|
||||||
|
path: TPath,
|
||||||
|
options?: {
|
||||||
|
query?: Record<string, any>
|
||||||
|
headers?: Record<string, string>
|
||||||
|
}
|
||||||
|
): Promise<ResponseForPath<TPath, 'DELETE'>>
|
||||||
|
|
||||||
|
patch<TPath extends ConcreteApiPaths>(
|
||||||
|
path: TPath,
|
||||||
|
options: {
|
||||||
|
body?: BodyForPath<TPath, 'PATCH'>
|
||||||
|
query?: Record<string, any>
|
||||||
|
headers?: Record<string, string>
|
||||||
|
}
|
||||||
|
): Promise<ResponseForPath<TPath, 'PATCH'>>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper types to determine if parameters are required based on schema
|
||||||
|
*/
|
||||||
|
type HasRequiredQuery<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
|
||||||
|
? Method extends keyof ApiSchemas[Path]
|
||||||
|
? ApiSchemas[Path][Method] extends { query: any }
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false
|
||||||
|
: false
|
||||||
|
|
||||||
|
type HasRequiredBody<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
|
||||||
|
? Method extends keyof ApiSchemas[Path]
|
||||||
|
? ApiSchemas[Path][Method] extends { body: any }
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false
|
||||||
|
: false
|
||||||
|
|
||||||
|
type HasRequiredParams<Path extends ApiPaths, Method extends ApiMethods<Path>> = Path extends keyof ApiSchemas
|
||||||
|
? Method extends keyof ApiSchemas[Path]
|
||||||
|
? ApiSchemas[Path][Method] extends { params: any }
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false
|
||||||
|
: false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler function for a specific API endpoint
|
||||||
|
* Provides type-safe parameter extraction based on ApiSchemas
|
||||||
|
* Parameters are required or optional based on the schema definition
|
||||||
|
*/
|
||||||
|
export type ApiHandler<Path extends ApiPaths, Method extends ApiMethods<Path>> = (
|
||||||
|
params: (HasRequiredParams<Path, Method> extends true
|
||||||
|
? { params: ApiParams<Path, Method> }
|
||||||
|
: { params?: ApiParams<Path, Method> }) &
|
||||||
|
(HasRequiredQuery<Path, Method> extends true
|
||||||
|
? { query: ApiQuery<Path, Method> }
|
||||||
|
: { query?: ApiQuery<Path, Method> }) &
|
||||||
|
(HasRequiredBody<Path, Method> extends true ? { body: ApiBody<Path, Method> } : { body?: ApiBody<Path, Method> })
|
||||||
|
) => Promise<ApiResponse<Path, Method>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete API implementation that must match ApiSchemas structure
|
||||||
|
* TypeScript will error if any endpoint is missing - this ensures exhaustive coverage
|
||||||
|
*/
|
||||||
|
export type ApiImplementation = {
|
||||||
|
[Path in ApiPaths]: {
|
||||||
|
[Method in ApiMethods<Path>]: ApiHandler<Path, Method>
|
||||||
|
}
|
||||||
|
}
|
||||||
289
packages/shared/data/api/apiTypes.ts
Normal file
289
packages/shared/data/api/apiTypes.ts
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
/**
|
||||||
|
* Core types for the Data API system
|
||||||
|
* Provides type definitions for request/response handling across renderer-main IPC communication
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard HTTP methods supported by the Data API
|
||||||
|
*/
|
||||||
|
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request object structure for Data API calls
|
||||||
|
*/
|
||||||
|
export interface DataRequest<T = any> {
|
||||||
|
/** Unique request identifier for tracking and correlation */
|
||||||
|
id: string
|
||||||
|
/** HTTP method for the request */
|
||||||
|
method: HttpMethod
|
||||||
|
/** API path (e.g., '/topics', '/topics/123') */
|
||||||
|
path: string
|
||||||
|
/** URL parameters for the request */
|
||||||
|
params?: Record<string, any>
|
||||||
|
/** Request body data */
|
||||||
|
body?: T
|
||||||
|
/** Request headers */
|
||||||
|
headers?: Record<string, string>
|
||||||
|
/** Additional metadata for request processing */
|
||||||
|
metadata?: {
|
||||||
|
/** Request timestamp */
|
||||||
|
timestamp: number
|
||||||
|
/** OpenTelemetry span context for tracing */
|
||||||
|
spanContext?: any
|
||||||
|
/** Cache options for this specific request */
|
||||||
|
cache?: CacheOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response object structure for Data API calls
|
||||||
|
*/
|
||||||
|
export interface DataResponse<T = any> {
|
||||||
|
/** Request ID that this response corresponds to */
|
||||||
|
id: string
|
||||||
|
/** HTTP status code */
|
||||||
|
status: number
|
||||||
|
/** Response data if successful */
|
||||||
|
data?: T
|
||||||
|
/** Error information if request failed */
|
||||||
|
error?: DataApiError
|
||||||
|
/** Response metadata */
|
||||||
|
metadata?: {
|
||||||
|
/** Request processing duration in milliseconds */
|
||||||
|
duration: number
|
||||||
|
/** Whether response was served from cache */
|
||||||
|
cached?: boolean
|
||||||
|
/** Cache TTL if applicable */
|
||||||
|
cacheTtl?: number
|
||||||
|
/** Response timestamp */
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standardized error structure for Data API
|
||||||
|
*/
|
||||||
|
export interface DataApiError {
|
||||||
|
/** Error code for programmatic handling */
|
||||||
|
code: string
|
||||||
|
/** Human-readable error message */
|
||||||
|
message: string
|
||||||
|
/** HTTP status code */
|
||||||
|
status: number
|
||||||
|
/** Additional error details */
|
||||||
|
details?: any
|
||||||
|
/** Error stack trace (development mode only) */
|
||||||
|
stack?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard error codes for Data API
|
||||||
|
*/
|
||||||
|
export enum ErrorCode {
|
||||||
|
// Client errors (4xx)
|
||||||
|
BAD_REQUEST = 'BAD_REQUEST',
|
||||||
|
UNAUTHORIZED = 'UNAUTHORIZED',
|
||||||
|
FORBIDDEN = 'FORBIDDEN',
|
||||||
|
NOT_FOUND = 'NOT_FOUND',
|
||||||
|
METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED',
|
||||||
|
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
||||||
|
RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
|
||||||
|
|
||||||
|
// Server errors (5xx)
|
||||||
|
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||||
|
DATABASE_ERROR = 'DATABASE_ERROR',
|
||||||
|
SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',
|
||||||
|
|
||||||
|
// Custom application errors
|
||||||
|
MIGRATION_ERROR = 'MIGRATION_ERROR',
|
||||||
|
PERMISSION_DENIED = 'PERMISSION_DENIED',
|
||||||
|
RESOURCE_LOCKED = 'RESOURCE_LOCKED',
|
||||||
|
CONCURRENT_MODIFICATION = 'CONCURRENT_MODIFICATION'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache configuration options
|
||||||
|
*/
|
||||||
|
export interface CacheOptions {
|
||||||
|
/** Cache TTL in seconds */
|
||||||
|
ttl?: number
|
||||||
|
/** Return stale data while revalidating in background */
|
||||||
|
staleWhileRevalidate?: boolean
|
||||||
|
/** Custom cache key override */
|
||||||
|
cacheKey?: string
|
||||||
|
/** Operations that should invalidate this cache entry */
|
||||||
|
invalidateOn?: string[]
|
||||||
|
/** Whether to bypass cache entirely */
|
||||||
|
noCache?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction request wrapper for atomic operations
|
||||||
|
*/
|
||||||
|
export interface TransactionRequest {
|
||||||
|
/** List of operations to execute in transaction */
|
||||||
|
operations: DataRequest[]
|
||||||
|
/** Transaction options */
|
||||||
|
options?: {
|
||||||
|
/** Database isolation level */
|
||||||
|
isolation?: 'read-uncommitted' | 'read-committed' | 'repeatable-read' | 'serializable'
|
||||||
|
/** Whether to rollback entire transaction on any error */
|
||||||
|
rollbackOnError?: boolean
|
||||||
|
/** Transaction timeout in milliseconds */
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch request for multiple operations
|
||||||
|
*/
|
||||||
|
export interface BatchRequest {
|
||||||
|
/** List of requests to execute */
|
||||||
|
requests: DataRequest[]
|
||||||
|
/** Whether to execute requests in parallel */
|
||||||
|
parallel?: boolean
|
||||||
|
/** Stop on first error */
|
||||||
|
stopOnError?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch response containing results for all requests
|
||||||
|
*/
|
||||||
|
export interface BatchResponse {
|
||||||
|
/** Individual response for each request */
|
||||||
|
results: DataResponse[]
|
||||||
|
/** Overall batch execution metadata */
|
||||||
|
metadata: {
|
||||||
|
/** Total execution time */
|
||||||
|
duration: number
|
||||||
|
/** Number of successful operations */
|
||||||
|
successCount: number
|
||||||
|
/** Number of failed operations */
|
||||||
|
errorCount: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination parameters for list operations
|
||||||
|
*/
|
||||||
|
export interface PaginationParams {
|
||||||
|
/** Page number (1-based) */
|
||||||
|
page?: number
|
||||||
|
/** Items per page */
|
||||||
|
limit?: number
|
||||||
|
/** Cursor for cursor-based pagination */
|
||||||
|
cursor?: string
|
||||||
|
/** Sort field and direction */
|
||||||
|
sort?: {
|
||||||
|
field: string
|
||||||
|
order: 'asc' | 'desc'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paginated response wrapper
|
||||||
|
*/
|
||||||
|
export interface PaginatedResponse<T> {
|
||||||
|
/** Items for current page */
|
||||||
|
items: T[]
|
||||||
|
/** Total number of items */
|
||||||
|
total: number
|
||||||
|
/** Current page number */
|
||||||
|
page: number
|
||||||
|
/** Total number of pages */
|
||||||
|
pageCount: number
|
||||||
|
/** Whether there are more pages */
|
||||||
|
hasNext: boolean
|
||||||
|
/** Whether there are previous pages */
|
||||||
|
hasPrev: boolean
|
||||||
|
/** Next cursor for cursor-based pagination */
|
||||||
|
nextCursor?: string
|
||||||
|
/** Previous cursor for cursor-based pagination */
|
||||||
|
prevCursor?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription options for real-time data updates
|
||||||
|
*/
|
||||||
|
export interface SubscriptionOptions {
|
||||||
|
/** Path pattern to subscribe to */
|
||||||
|
path: string
|
||||||
|
/** Filters to apply to subscription */
|
||||||
|
filters?: Record<string, any>
|
||||||
|
/** Whether to receive initial data */
|
||||||
|
includeInitial?: boolean
|
||||||
|
/** Custom subscription metadata */
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription callback function
|
||||||
|
*/
|
||||||
|
export type SubscriptionCallback<T = any> = (data: T, event: SubscriptionEvent) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription event types
|
||||||
|
*/
|
||||||
|
export enum SubscriptionEvent {
|
||||||
|
CREATED = 'created',
|
||||||
|
UPDATED = 'updated',
|
||||||
|
DELETED = 'deleted',
|
||||||
|
INITIAL = 'initial',
|
||||||
|
ERROR = 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware interface
|
||||||
|
*/
|
||||||
|
export interface Middleware {
|
||||||
|
/** Middleware name */
|
||||||
|
name: string
|
||||||
|
/** Execution priority (lower = earlier) */
|
||||||
|
priority?: number
|
||||||
|
/** Middleware execution function */
|
||||||
|
execute(req: DataRequest, res: DataResponse, next: () => Promise<void>): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request context passed through middleware chain
|
||||||
|
*/
|
||||||
|
export interface RequestContext {
|
||||||
|
/** Original request */
|
||||||
|
request: DataRequest
|
||||||
|
/** Response being built */
|
||||||
|
response: DataResponse
|
||||||
|
/** Path that matched this request */
|
||||||
|
path?: string
|
||||||
|
/** HTTP method */
|
||||||
|
method?: HttpMethod
|
||||||
|
/** Authenticated user (if any) */
|
||||||
|
user?: any
|
||||||
|
/** Additional context data */
|
||||||
|
data: Map<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base options for service operations
|
||||||
|
*/
|
||||||
|
export interface ServiceOptions {
|
||||||
|
/** Database transaction to use */
|
||||||
|
transaction?: any
|
||||||
|
/** User context for authorization */
|
||||||
|
user?: any
|
||||||
|
/** Additional service-specific options */
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard service response wrapper
|
||||||
|
*/
|
||||||
|
export interface ServiceResult<T = any> {
|
||||||
|
/** Whether operation was successful */
|
||||||
|
success: boolean
|
||||||
|
/** Result data if successful */
|
||||||
|
data?: T
|
||||||
|
/** Error information if failed */
|
||||||
|
error?: DataApiError
|
||||||
|
/** Additional metadata */
|
||||||
|
metadata?: Record<string, any>
|
||||||
|
}
|
||||||
194
packages/shared/data/api/errorCodes.ts
Normal file
194
packages/shared/data/api/errorCodes.ts
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* Centralized error code definitions for the Data API system
|
||||||
|
* Provides consistent error handling across renderer and main processes
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { DataApiError } from './apiTypes'
|
||||||
|
import { ErrorCode } from './apiTypes'
|
||||||
|
|
||||||
|
// Re-export ErrorCode for convenience
|
||||||
|
export { ErrorCode } from './apiTypes'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error code to HTTP status mapping
|
||||||
|
*/
|
||||||
|
export const ERROR_STATUS_MAP: Record<ErrorCode, number> = {
|
||||||
|
// Client errors (4xx)
|
||||||
|
[ErrorCode.BAD_REQUEST]: 400,
|
||||||
|
[ErrorCode.UNAUTHORIZED]: 401,
|
||||||
|
[ErrorCode.FORBIDDEN]: 403,
|
||||||
|
[ErrorCode.NOT_FOUND]: 404,
|
||||||
|
[ErrorCode.METHOD_NOT_ALLOWED]: 405,
|
||||||
|
[ErrorCode.VALIDATION_ERROR]: 422,
|
||||||
|
[ErrorCode.RATE_LIMIT_EXCEEDED]: 429,
|
||||||
|
|
||||||
|
// Server errors (5xx)
|
||||||
|
[ErrorCode.INTERNAL_SERVER_ERROR]: 500,
|
||||||
|
[ErrorCode.DATABASE_ERROR]: 500,
|
||||||
|
[ErrorCode.SERVICE_UNAVAILABLE]: 503,
|
||||||
|
|
||||||
|
// Custom application errors (5xx)
|
||||||
|
[ErrorCode.MIGRATION_ERROR]: 500,
|
||||||
|
[ErrorCode.PERMISSION_DENIED]: 403,
|
||||||
|
[ErrorCode.RESOURCE_LOCKED]: 423,
|
||||||
|
[ErrorCode.CONCURRENT_MODIFICATION]: 409
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default error messages for each error code
|
||||||
|
*/
|
||||||
|
export const ERROR_MESSAGES: Record<ErrorCode, string> = {
|
||||||
|
[ErrorCode.BAD_REQUEST]: 'Bad request: Invalid request format or parameters',
|
||||||
|
[ErrorCode.UNAUTHORIZED]: 'Unauthorized: Authentication required',
|
||||||
|
[ErrorCode.FORBIDDEN]: 'Forbidden: Insufficient permissions',
|
||||||
|
[ErrorCode.NOT_FOUND]: 'Not found: Requested resource does not exist',
|
||||||
|
[ErrorCode.METHOD_NOT_ALLOWED]: 'Method not allowed: HTTP method not supported for this endpoint',
|
||||||
|
[ErrorCode.VALIDATION_ERROR]: 'Validation error: Request data does not meet requirements',
|
||||||
|
[ErrorCode.RATE_LIMIT_EXCEEDED]: 'Rate limit exceeded: Too many requests',
|
||||||
|
|
||||||
|
[ErrorCode.INTERNAL_SERVER_ERROR]: 'Internal server error: An unexpected error occurred',
|
||||||
|
[ErrorCode.DATABASE_ERROR]: 'Database error: Failed to access or modify data',
|
||||||
|
[ErrorCode.SERVICE_UNAVAILABLE]: 'Service unavailable: The service is temporarily unavailable',
|
||||||
|
|
||||||
|
[ErrorCode.MIGRATION_ERROR]: 'Migration error: Failed to migrate data',
|
||||||
|
[ErrorCode.PERMISSION_DENIED]: 'Permission denied: Operation not allowed for current user',
|
||||||
|
[ErrorCode.RESOURCE_LOCKED]: 'Resource locked: Resource is currently locked by another operation',
|
||||||
|
[ErrorCode.CONCURRENT_MODIFICATION]: 'Concurrent modification: Resource was modified by another user'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for creating standardized Data API errors
|
||||||
|
*/
|
||||||
|
export class DataApiErrorFactory {
|
||||||
|
/**
|
||||||
|
* Create a DataApiError with standard properties
|
||||||
|
*/
|
||||||
|
static create(code: ErrorCode, customMessage?: string, details?: any, stack?: string): DataApiError {
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
message: customMessage || ERROR_MESSAGES[code],
|
||||||
|
status: ERROR_STATUS_MAP[code],
|
||||||
|
details,
|
||||||
|
stack: stack || undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a validation error with field-specific details
|
||||||
|
*/
|
||||||
|
static validation(fieldErrors: Record<string, string[]>, message?: string): DataApiError {
|
||||||
|
return this.create(ErrorCode.VALIDATION_ERROR, message || 'Request validation failed', { fieldErrors })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a not found error for specific resource
|
||||||
|
*/
|
||||||
|
static notFound(resource: string, id?: string): DataApiError {
|
||||||
|
const message = id ? `${resource} with id '${id}' not found` : `${resource} not found`
|
||||||
|
|
||||||
|
return this.create(ErrorCode.NOT_FOUND, message, { resource, id })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a database error with query details
|
||||||
|
*/
|
||||||
|
static database(originalError: Error, operation?: string): DataApiError {
|
||||||
|
return this.create(
|
||||||
|
ErrorCode.DATABASE_ERROR,
|
||||||
|
`Database operation failed${operation ? `: ${operation}` : ''}`,
|
||||||
|
{
|
||||||
|
originalError: originalError.message,
|
||||||
|
operation
|
||||||
|
},
|
||||||
|
originalError.stack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a permission denied error
|
||||||
|
*/
|
||||||
|
static permissionDenied(action: string, resource?: string): DataApiError {
|
||||||
|
const message = resource ? `Permission denied: Cannot ${action} ${resource}` : `Permission denied: Cannot ${action}`
|
||||||
|
|
||||||
|
return this.create(ErrorCode.PERMISSION_DENIED, message, { action, resource })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an internal server error from an unexpected error
|
||||||
|
*/
|
||||||
|
static internal(originalError: Error, context?: string): DataApiError {
|
||||||
|
const message = context
|
||||||
|
? `Internal error in ${context}: ${originalError.message}`
|
||||||
|
: `Internal error: ${originalError.message}`
|
||||||
|
|
||||||
|
return this.create(
|
||||||
|
ErrorCode.INTERNAL_SERVER_ERROR,
|
||||||
|
message,
|
||||||
|
{ originalError: originalError.message, context },
|
||||||
|
originalError.stack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a rate limit exceeded error
|
||||||
|
*/
|
||||||
|
static rateLimit(limit: number, windowMs: number): DataApiError {
|
||||||
|
return this.create(ErrorCode.RATE_LIMIT_EXCEEDED, `Rate limit exceeded: ${limit} requests per ${windowMs}ms`, {
|
||||||
|
limit,
|
||||||
|
windowMs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a resource locked error
|
||||||
|
*/
|
||||||
|
static resourceLocked(resource: string, id: string, lockedBy?: string): DataApiError {
|
||||||
|
const message = lockedBy
|
||||||
|
? `${resource} '${id}' is locked by ${lockedBy}`
|
||||||
|
: `${resource} '${id}' is currently locked`
|
||||||
|
|
||||||
|
return this.create(ErrorCode.RESOURCE_LOCKED, message, { resource, id, lockedBy })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a concurrent modification error
|
||||||
|
*/
|
||||||
|
static concurrentModification(resource: string, id: string): DataApiError {
|
||||||
|
return this.create(ErrorCode.CONCURRENT_MODIFICATION, `${resource} '${id}' was modified by another user`, {
|
||||||
|
resource,
|
||||||
|
id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an error is a Data API error
|
||||||
|
*/
|
||||||
|
export function isDataApiError(error: any): error is DataApiError {
|
||||||
|
return (
|
||||||
|
error &&
|
||||||
|
typeof error === 'object' &&
|
||||||
|
typeof error.code === 'string' &&
|
||||||
|
typeof error.message === 'string' &&
|
||||||
|
typeof error.status === 'number'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a generic error to a DataApiError
|
||||||
|
*/
|
||||||
|
export function toDataApiError(error: unknown, context?: string): DataApiError {
|
||||||
|
if (isDataApiError(error)) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error) {
|
||||||
|
return DataApiErrorFactory.internal(error, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
return DataApiErrorFactory.create(
|
||||||
|
ErrorCode.INTERNAL_SERVER_ERROR,
|
||||||
|
`Unknown error${context ? ` in ${context}` : ''}: ${String(error)}`,
|
||||||
|
{ originalError: error, context }
|
||||||
|
)
|
||||||
|
}
|
||||||
121
packages/shared/data/api/index.ts
Normal file
121
packages/shared/data/api/index.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* Cherry Studio Data API - Barrel Exports
|
||||||
|
*
|
||||||
|
* This file provides a centralized entry point for all data API types,
|
||||||
|
* schemas, and utilities. Import everything you need from this single location.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* import { Topic, CreateTopicDto, ApiSchemas, DataRequest, ErrorCode } from '@/shared/data'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Core data API types and infrastructure
|
||||||
|
export type {
|
||||||
|
BatchRequest,
|
||||||
|
BatchResponse,
|
||||||
|
CacheOptions,
|
||||||
|
DataApiError,
|
||||||
|
DataRequest,
|
||||||
|
DataResponse,
|
||||||
|
HttpMethod,
|
||||||
|
Middleware,
|
||||||
|
PaginatedResponse,
|
||||||
|
PaginationParams,
|
||||||
|
RequestContext,
|
||||||
|
ServiceOptions,
|
||||||
|
ServiceResult,
|
||||||
|
SubscriptionCallback,
|
||||||
|
SubscriptionOptions,
|
||||||
|
TransactionRequest
|
||||||
|
} from './apiTypes'
|
||||||
|
export { ErrorCode, SubscriptionEvent } from './apiTypes'
|
||||||
|
|
||||||
|
// Domain models and DTOs
|
||||||
|
export type {
|
||||||
|
BulkOperationRequest,
|
||||||
|
BulkOperationResponse,
|
||||||
|
CreateTestItemDto,
|
||||||
|
TestItem,
|
||||||
|
UpdateTestItemDto
|
||||||
|
} from './apiModels'
|
||||||
|
|
||||||
|
// API schema definitions and type helpers
|
||||||
|
export type {
|
||||||
|
ApiBody,
|
||||||
|
ApiClient,
|
||||||
|
ApiMethods,
|
||||||
|
ApiParams,
|
||||||
|
ApiPaths,
|
||||||
|
ApiQuery,
|
||||||
|
ApiResponse,
|
||||||
|
ApiSchemas
|
||||||
|
} from './apiSchemas'
|
||||||
|
|
||||||
|
// Path type utilities for template literal types
|
||||||
|
export type {
|
||||||
|
BodyForPath,
|
||||||
|
ConcreteApiPaths,
|
||||||
|
MatchApiPath,
|
||||||
|
QueryParamsForPath,
|
||||||
|
ResolvedPath,
|
||||||
|
ResponseForPath
|
||||||
|
} from './apiPaths'
|
||||||
|
|
||||||
|
// Error handling utilities
|
||||||
|
export {
|
||||||
|
ErrorCode as DataApiErrorCode,
|
||||||
|
DataApiErrorFactory,
|
||||||
|
ERROR_MESSAGES,
|
||||||
|
ERROR_STATUS_MAP,
|
||||||
|
isDataApiError,
|
||||||
|
toDataApiError
|
||||||
|
} from './errorCodes'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export commonly used type combinations for convenience
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Import types for re-export convenience types
|
||||||
|
import type { CreateTestItemDto, TestItem, UpdateTestItemDto } from './apiModels'
|
||||||
|
import type {
|
||||||
|
BatchRequest,
|
||||||
|
BatchResponse,
|
||||||
|
DataApiError,
|
||||||
|
DataRequest,
|
||||||
|
DataResponse,
|
||||||
|
ErrorCode,
|
||||||
|
PaginatedResponse,
|
||||||
|
PaginationParams,
|
||||||
|
TransactionRequest
|
||||||
|
} from './apiTypes'
|
||||||
|
import type { DataApiErrorFactory } from './errorCodes'
|
||||||
|
|
||||||
|
/** All test item-related types */
|
||||||
|
export type TestItemTypes = {
|
||||||
|
TestItem: TestItem
|
||||||
|
CreateTestItemDto: CreateTestItemDto
|
||||||
|
UpdateTestItemDto: UpdateTestItemDto
|
||||||
|
}
|
||||||
|
|
||||||
|
/** All error-related types and utilities */
|
||||||
|
export type ErrorTypes = {
|
||||||
|
DataApiError: DataApiError
|
||||||
|
ErrorCode: ErrorCode
|
||||||
|
ErrorFactory: typeof DataApiErrorFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
/** All request/response types */
|
||||||
|
export type RequestTypes = {
|
||||||
|
DataRequest: DataRequest
|
||||||
|
DataResponse: DataResponse
|
||||||
|
BatchRequest: BatchRequest
|
||||||
|
BatchResponse: BatchResponse
|
||||||
|
TransactionRequest: TransactionRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
/** All pagination-related types */
|
||||||
|
export type PaginationTypes = {
|
||||||
|
PaginationParams: PaginationParams
|
||||||
|
PaginatedResponse: PaginatedResponse<any>
|
||||||
|
}
|
||||||
144
packages/shared/data/cache/cacheSchemas.ts
vendored
Normal file
144
packages/shared/data/cache/cacheSchemas.ts
vendored
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import type * as CacheValueTypes from './cacheValueTypes'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use cache schema for renderer hook
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type UseCacheSchema = {
|
||||||
|
// App state
|
||||||
|
'app.dist.update_state': CacheValueTypes.CacheAppUpdateState
|
||||||
|
'app.user.avatar': string
|
||||||
|
|
||||||
|
// Chat context
|
||||||
|
'chat.multi_select_mode': boolean
|
||||||
|
'chat.selected_message_ids': string[]
|
||||||
|
'chat.generating': boolean
|
||||||
|
'chat.websearch.searching': boolean
|
||||||
|
'chat.websearch.active_searches': CacheValueTypes.CacheActiveSearches
|
||||||
|
|
||||||
|
// Minapp management
|
||||||
|
'minapp.opened_keep_alive': CacheValueTypes.CacheMinAppType[]
|
||||||
|
'minapp.current_id': string
|
||||||
|
'minapp.show': boolean
|
||||||
|
'minapp.opened_oneoff': CacheValueTypes.CacheMinAppType | null
|
||||||
|
|
||||||
|
// Topic management
|
||||||
|
'topic.active': CacheValueTypes.CacheTopic | null
|
||||||
|
'topic.renaming': string[]
|
||||||
|
'topic.newly_renamed': string[]
|
||||||
|
|
||||||
|
// Test keys (for dataRefactorTest window)
|
||||||
|
// TODO: remove after testing
|
||||||
|
'test-hook-memory-1': string
|
||||||
|
'test-ttl-cache': string
|
||||||
|
'test-protected-cache': string
|
||||||
|
'test-deep-equal': { nested: { count: number }; tags: string[] }
|
||||||
|
'test-performance': number
|
||||||
|
'test-multi-hook': string
|
||||||
|
'concurrent-test-1': number
|
||||||
|
'concurrent-test-2': number
|
||||||
|
'large-data-test': Record<string, any>
|
||||||
|
'test-number-cache': number
|
||||||
|
'test-object-cache': { name: string; count: number; active: boolean }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultUseCache: UseCacheSchema = {
|
||||||
|
// App state
|
||||||
|
'app.dist.update_state': {
|
||||||
|
info: null,
|
||||||
|
checking: false,
|
||||||
|
downloading: false,
|
||||||
|
downloaded: false,
|
||||||
|
downloadProgress: 0,
|
||||||
|
available: false
|
||||||
|
},
|
||||||
|
'app.user.avatar': '',
|
||||||
|
|
||||||
|
// Chat context
|
||||||
|
'chat.multi_select_mode': false,
|
||||||
|
'chat.selected_message_ids': [],
|
||||||
|
'chat.generating': false,
|
||||||
|
'chat.websearch.searching': false,
|
||||||
|
'chat.websearch.active_searches': {},
|
||||||
|
|
||||||
|
// Minapp management
|
||||||
|
'minapp.opened_keep_alive': [],
|
||||||
|
'minapp.current_id': '',
|
||||||
|
'minapp.show': false,
|
||||||
|
'minapp.opened_oneoff': null,
|
||||||
|
|
||||||
|
// Topic management
|
||||||
|
'topic.active': null,
|
||||||
|
'topic.renaming': [],
|
||||||
|
'topic.newly_renamed': [],
|
||||||
|
|
||||||
|
// Test keys (for dataRefactorTest window)
|
||||||
|
// TODO: remove after testing
|
||||||
|
'test-hook-memory-1': 'default-memory-value',
|
||||||
|
'test-ttl-cache': 'test-ttl-cache',
|
||||||
|
'test-protected-cache': 'protected-value',
|
||||||
|
'test-deep-equal': { nested: { count: 0 }, tags: ['initial'] },
|
||||||
|
'test-performance': 0,
|
||||||
|
'test-multi-hook': 'hook-1-default',
|
||||||
|
'concurrent-test-1': 0,
|
||||||
|
'concurrent-test-2': 0,
|
||||||
|
'large-data-test': {},
|
||||||
|
'test-number-cache': 42,
|
||||||
|
'test-object-cache': { name: 'test', count: 0, active: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use shared cache schema for renderer hook
|
||||||
|
*/
|
||||||
|
export type UseSharedCacheSchema = {
|
||||||
|
'example-key': string
|
||||||
|
|
||||||
|
// Test keys (for dataRefactorTest window)
|
||||||
|
// TODO: remove after testing
|
||||||
|
'test-hook-shared-1': string
|
||||||
|
'test-multi-hook': string
|
||||||
|
'concurrent-shared': number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultUseSharedCache: UseSharedCacheSchema = {
|
||||||
|
'example-key': 'example default value',
|
||||||
|
|
||||||
|
// Test keys (for dataRefactorTest window)
|
||||||
|
// TODO: remove after testing
|
||||||
|
'concurrent-shared': 0,
|
||||||
|
'test-hook-shared-1': 'default-shared-value',
|
||||||
|
'test-multi-hook': 'hook-3-shared'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist cache schema defining allowed keys and their value types
|
||||||
|
* This ensures type safety and prevents key conflicts
|
||||||
|
*/
|
||||||
|
export type RendererPersistCacheSchema = {
|
||||||
|
'example-key': string
|
||||||
|
|
||||||
|
// Test keys (for dataRefactorTest window)
|
||||||
|
// TODO: remove after testing
|
||||||
|
'example-1': string
|
||||||
|
'example-2': string
|
||||||
|
'example-3': string
|
||||||
|
'example-4': string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultRendererPersistCache: RendererPersistCacheSchema = {
|
||||||
|
'example-key': 'example default value',
|
||||||
|
|
||||||
|
// Test keys (for dataRefactorTest window)
|
||||||
|
// TODO: remove after testing
|
||||||
|
'example-1': 'example default value',
|
||||||
|
'example-2': 'example default value',
|
||||||
|
'example-3': 'example default value',
|
||||||
|
'example-4': 'example default value'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe cache key
|
||||||
|
*/
|
||||||
|
export type RendererPersistCacheKey = keyof RendererPersistCacheSchema
|
||||||
|
export type UseCacheKey = keyof UseCacheSchema
|
||||||
|
export type UseSharedCacheKey = keyof UseSharedCacheSchema
|
||||||
43
packages/shared/data/cache/cacheTypes.ts
vendored
Normal file
43
packages/shared/data/cache/cacheTypes.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Cache types and interfaces for CacheService
|
||||||
|
*
|
||||||
|
* Supports three-layer caching architecture:
|
||||||
|
* 1. Memory cache (cross-component within renderer)
|
||||||
|
* 2. Shared cache (cross-window via IPC)
|
||||||
|
* 3. Persist cache (cross-window with localStorage persistence)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache entry with optional TTL support
|
||||||
|
*/
|
||||||
|
export interface CacheEntry<T = any> {
|
||||||
|
value: T
|
||||||
|
expireAt?: number // Unix timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache synchronization message for IPC communication
|
||||||
|
*/
|
||||||
|
export interface CacheSyncMessage {
|
||||||
|
type: 'shared' | 'persist'
|
||||||
|
key: string
|
||||||
|
value: any
|
||||||
|
ttl?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch cache synchronization message
|
||||||
|
*/
|
||||||
|
export interface CacheSyncBatchMessage {
|
||||||
|
type: 'shared' | 'persist'
|
||||||
|
entries: Array<{
|
||||||
|
key: string
|
||||||
|
value: any
|
||||||
|
ttl?: number
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache subscription callback
|
||||||
|
*/
|
||||||
|
export type CacheSubscriber = () => void
|
||||||
18
packages/shared/data/cache/cacheValueTypes.ts
vendored
Normal file
18
packages/shared/data/cache/cacheValueTypes.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { MinAppType, Topic, WebSearchStatus } from '@types'
|
||||||
|
import type { UpdateInfo } from 'builder-util-runtime'
|
||||||
|
|
||||||
|
export type CacheAppUpdateState = {
|
||||||
|
info: UpdateInfo | null
|
||||||
|
checking: boolean
|
||||||
|
downloading: boolean
|
||||||
|
downloaded: boolean
|
||||||
|
downloadProgress: number
|
||||||
|
available: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CacheActiveSearches = Record<string, WebSearchStatus>
|
||||||
|
|
||||||
|
// For cache schema, we use any for complex types to avoid circular dependencies
|
||||||
|
// The actual type checking will be done at runtime by the cache system
|
||||||
|
export type CacheMinAppType = MinAppType
|
||||||
|
export type CacheTopic = Topic
|
||||||
690
packages/shared/data/preference/preferenceSchemas.ts
Normal file
690
packages/shared/data/preference/preferenceSchemas.ts
Normal file
@@ -0,0 +1,690 @@
|
|||||||
|
/**
|
||||||
|
* Auto-generated preferences configuration
|
||||||
|
* Generated at: 2025-09-16T03:17:03.354Z
|
||||||
|
*
|
||||||
|
* This file is automatically generated from classification.json
|
||||||
|
* To update this file, modify classification.json and run:
|
||||||
|
* node .claude/data-classify/scripts/generate-preferences.js
|
||||||
|
*
|
||||||
|
* === AUTO-GENERATED CONTENT START ===
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TRANSLATE_PROMPT } from '@shared/config/prompts'
|
||||||
|
import * as PreferenceTypes from '@shared/data/preference/preferenceTypes'
|
||||||
|
|
||||||
|
/* eslint @typescript-eslint/member-ordering: ["error", {
|
||||||
|
"interfaces": { "order": "alphabetically" },
|
||||||
|
"typeLiterals": { "order": "alphabetically" }
|
||||||
|
}] */
|
||||||
|
|
||||||
|
export interface PreferenceSchemas {
|
||||||
|
default: {
|
||||||
|
// redux/settings/enableDeveloperMode
|
||||||
|
'app.developer_mode.enabled': boolean
|
||||||
|
// redux/settings/disableHardwareAcceleration
|
||||||
|
'app.disable_hardware_acceleration': boolean
|
||||||
|
// redux/settings/autoCheckUpdate
|
||||||
|
'app.dist.auto_update.enabled': boolean
|
||||||
|
// redux/settings/testChannel
|
||||||
|
'app.dist.test_plan.channel': PreferenceTypes.UpgradeChannel
|
||||||
|
// redux/settings/testPlan
|
||||||
|
'app.dist.test_plan.enabled': boolean
|
||||||
|
// redux/settings/language
|
||||||
|
'app.language': PreferenceTypes.LanguageVarious | null
|
||||||
|
// redux/settings/launchOnBoot
|
||||||
|
'app.launch_on_boot': boolean
|
||||||
|
// redux/settings/notification.assistant
|
||||||
|
'app.notification.assistant.enabled': boolean
|
||||||
|
// redux/settings/notification.backup
|
||||||
|
'app.notification.backup.enabled': boolean
|
||||||
|
// redux/settings/notification.knowledge
|
||||||
|
'app.notification.knowledge.enabled': boolean
|
||||||
|
// redux/settings/enableDataCollection
|
||||||
|
'app.privacy.data_collection.enabled': boolean
|
||||||
|
// redux/settings/proxyBypassRules
|
||||||
|
'app.proxy.bypass_rules': string
|
||||||
|
// redux/settings/proxyMode
|
||||||
|
'app.proxy.mode': PreferenceTypes.ProxyMode
|
||||||
|
// redux/settings/proxyUrl
|
||||||
|
'app.proxy.url': string
|
||||||
|
// redux/settings/enableSpellCheck
|
||||||
|
'app.spell_check.enabled': boolean
|
||||||
|
// redux/settings/spellCheckLanguages
|
||||||
|
'app.spell_check.languages': string[]
|
||||||
|
// redux/settings/tray
|
||||||
|
'app.tray.enabled': boolean
|
||||||
|
// redux/settings/trayOnClose
|
||||||
|
'app.tray.on_close': boolean
|
||||||
|
// redux/settings/launchToTray
|
||||||
|
'app.tray.on_launch': boolean
|
||||||
|
// redux/settings/userId
|
||||||
|
'app.user.id': string
|
||||||
|
// redux/settings/userName
|
||||||
|
'app.user.name': string
|
||||||
|
// electronStore/ZoomFactor/ZoomFactor
|
||||||
|
'app.zoom_factor': number
|
||||||
|
// redux/settings/clickAssistantToShowTopic
|
||||||
|
'assistant.click_to_show_topic': boolean
|
||||||
|
// redux/settings/assistantIconType
|
||||||
|
'assistant.icon_type': PreferenceTypes.AssistantIconType
|
||||||
|
// redux/settings/showAssistants
|
||||||
|
'assistant.tab.show': boolean
|
||||||
|
// redux/settings/assistantsTabSortType
|
||||||
|
'assistant.tab.sort_type': PreferenceTypes.AssistantTabSortType
|
||||||
|
// redux/settings/codeCollapsible
|
||||||
|
'chat.code.collapsible': boolean
|
||||||
|
// redux/settings/codeEditor.autocompletion
|
||||||
|
'chat.code.editor.autocompletion': boolean
|
||||||
|
// redux/settings/codeEditor.enabled
|
||||||
|
'chat.code.editor.enabled': boolean
|
||||||
|
// redux/settings/codeEditor.foldGutter
|
||||||
|
'chat.code.editor.fold_gutter': boolean
|
||||||
|
// redux/settings/codeEditor.highlightActiveLine
|
||||||
|
'chat.code.editor.highlight_active_line': boolean
|
||||||
|
// redux/settings/codeEditor.keymap
|
||||||
|
'chat.code.editor.keymap': boolean
|
||||||
|
// redux/settings/codeEditor.themeDark
|
||||||
|
'chat.code.editor.theme_dark': string
|
||||||
|
// redux/settings/codeEditor.themeLight
|
||||||
|
'chat.code.editor.theme_light': string
|
||||||
|
// redux/settings/codeExecution.enabled
|
||||||
|
'chat.code.execution.enabled': boolean
|
||||||
|
// redux/settings/codeExecution.timeoutMinutes
|
||||||
|
'chat.code.execution.timeout_minutes': number
|
||||||
|
// redux/settings/codeFancyBlock
|
||||||
|
'chat.code.fancy_block': boolean
|
||||||
|
// redux/settings/codeImageTools
|
||||||
|
'chat.code.image_tools': boolean
|
||||||
|
// redux/settings/codePreview.themeDark
|
||||||
|
'chat.code.preview.theme_dark': string
|
||||||
|
// redux/settings/codePreview.themeLight
|
||||||
|
'chat.code.preview.theme_light': string
|
||||||
|
// redux/settings/codeShowLineNumbers
|
||||||
|
'chat.code.show_line_numbers': boolean
|
||||||
|
// redux/settings/codeViewer.themeDark
|
||||||
|
'chat.code.viewer.theme_dark': string
|
||||||
|
// redux/settings/codeViewer.themeLight
|
||||||
|
'chat.code.viewer.theme_light': string
|
||||||
|
// redux/settings/codeWrappable
|
||||||
|
'chat.code.wrappable': boolean
|
||||||
|
// redux/settings/pasteLongTextAsFile
|
||||||
|
'chat.input.paste_long_text_as_file': boolean
|
||||||
|
// redux/settings/pasteLongTextThreshold
|
||||||
|
'chat.input.paste_long_text_threshold': number
|
||||||
|
// redux/settings/enableQuickPanelTriggers
|
||||||
|
'chat.input.quick_panel.triggers_enabled': boolean
|
||||||
|
// redux/settings/sendMessageShortcut
|
||||||
|
'chat.input.send_message_shortcut': PreferenceTypes.SendMessageShortcut
|
||||||
|
// redux/settings/showInputEstimatedTokens
|
||||||
|
'chat.input.show_estimated_tokens': boolean
|
||||||
|
// redux/settings/autoTranslateWithSpace
|
||||||
|
'chat.input.translate.auto_translate_with_space': boolean
|
||||||
|
// redux/settings/showTranslateConfirm
|
||||||
|
'chat.input.translate.show_confirm': boolean
|
||||||
|
// redux/settings/confirmDeleteMessage
|
||||||
|
'chat.message.confirm_delete': boolean
|
||||||
|
// redux/settings/confirmRegenerateMessage
|
||||||
|
'chat.message.confirm_regenerate': boolean
|
||||||
|
// redux/settings/messageFont
|
||||||
|
'chat.message.font': string
|
||||||
|
// redux/settings/fontSize
|
||||||
|
'chat.message.font_size': number
|
||||||
|
// redux/settings/mathEngine
|
||||||
|
'chat.message.math.engine': PreferenceTypes.MathEngine
|
||||||
|
// redux/settings/mathEnableSingleDollar
|
||||||
|
'chat.message.math.single_dollar': boolean
|
||||||
|
// redux/settings/foldDisplayMode
|
||||||
|
'chat.message.multi_model.fold_display_mode': PreferenceTypes.MultiModelFoldDisplayMode
|
||||||
|
// redux/settings/gridColumns
|
||||||
|
'chat.message.multi_model.grid_columns': number
|
||||||
|
// redux/settings/gridPopoverTrigger
|
||||||
|
'chat.message.multi_model.grid_popover_trigger': PreferenceTypes.MultiModelGridPopoverTrigger
|
||||||
|
// redux/settings/multiModelMessageStyle
|
||||||
|
'chat.message.multi_model.style': PreferenceTypes.MultiModelMessageStyle
|
||||||
|
// redux/settings/messageNavigation
|
||||||
|
'chat.message.navigation_mode': PreferenceTypes.ChatMessageNavigationMode
|
||||||
|
// redux/settings/renderInputMessageAsMarkdown
|
||||||
|
'chat.message.render_as_markdown': boolean
|
||||||
|
// redux/settings/showMessageDivider
|
||||||
|
'chat.message.show_divider': boolean
|
||||||
|
// redux/settings/showMessageOutline
|
||||||
|
'chat.message.show_outline': boolean
|
||||||
|
// redux/settings/showPrompt
|
||||||
|
'chat.message.show_prompt': boolean
|
||||||
|
// redux/settings/messageStyle
|
||||||
|
'chat.message.style': PreferenceTypes.ChatMessageStyle
|
||||||
|
// redux/settings/thoughtAutoCollapse
|
||||||
|
'chat.message.thought.auto_collapse': boolean
|
||||||
|
// redux/settings/narrowMode
|
||||||
|
'chat.narrow_mode': boolean
|
||||||
|
// redux/settings/skipBackupFile
|
||||||
|
'data.backup.general.skip_backup_file': boolean
|
||||||
|
// redux/settings/localBackupAutoSync
|
||||||
|
'data.backup.local.auto_sync': boolean
|
||||||
|
// redux/settings/localBackupDir
|
||||||
|
'data.backup.local.dir': string
|
||||||
|
// redux/settings/localBackupMaxBackups
|
||||||
|
'data.backup.local.max_backups': number
|
||||||
|
// redux/settings/localBackupSkipBackupFile
|
||||||
|
'data.backup.local.skip_backup_file': boolean
|
||||||
|
// redux/settings/localBackupSyncInterval
|
||||||
|
'data.backup.local.sync_interval': number
|
||||||
|
// redux/nutstore/nutstoreAutoSync
|
||||||
|
'data.backup.nutstore.auto_sync': boolean
|
||||||
|
// redux/nutstore/nutstoreMaxBackups
|
||||||
|
'data.backup.nutstore.max_backups': number
|
||||||
|
// redux/nutstore/nutstorePath
|
||||||
|
'data.backup.nutstore.path': string
|
||||||
|
// redux/nutstore/nutstoreSkipBackupFile
|
||||||
|
'data.backup.nutstore.skip_backup_file': boolean
|
||||||
|
// redux/nutstore/nutstoreSyncInterval
|
||||||
|
'data.backup.nutstore.sync_interval': number
|
||||||
|
// redux/nutstore/nutstoreToken
|
||||||
|
'data.backup.nutstore.token': string
|
||||||
|
// redux/settings/s3.accessKeyId
|
||||||
|
'data.backup.s3.access_key_id': string
|
||||||
|
// redux/settings/s3.autoSync
|
||||||
|
'data.backup.s3.auto_sync': boolean
|
||||||
|
// redux/settings/s3.bucket
|
||||||
|
'data.backup.s3.bucket': string
|
||||||
|
// redux/settings/s3.endpoint
|
||||||
|
'data.backup.s3.endpoint': string
|
||||||
|
// redux/settings/s3.maxBackups
|
||||||
|
'data.backup.s3.max_backups': number
|
||||||
|
// redux/settings/s3.region
|
||||||
|
'data.backup.s3.region': string
|
||||||
|
// redux/settings/s3.root
|
||||||
|
'data.backup.s3.root': string
|
||||||
|
// redux/settings/s3.secretAccessKey
|
||||||
|
'data.backup.s3.secret_access_key': string
|
||||||
|
// redux/settings/s3.skipBackupFile
|
||||||
|
'data.backup.s3.skip_backup_file': boolean
|
||||||
|
// redux/settings/s3.syncInterval
|
||||||
|
'data.backup.s3.sync_interval': number
|
||||||
|
// redux/settings/webdavAutoSync
|
||||||
|
'data.backup.webdav.auto_sync': boolean
|
||||||
|
// redux/settings/webdavDisableStream
|
||||||
|
'data.backup.webdav.disable_stream': boolean
|
||||||
|
// redux/settings/webdavHost
|
||||||
|
'data.backup.webdav.host': string
|
||||||
|
// redux/settings/webdavMaxBackups
|
||||||
|
'data.backup.webdav.max_backups': number
|
||||||
|
// redux/settings/webdavPass
|
||||||
|
'data.backup.webdav.pass': string
|
||||||
|
// redux/settings/webdavPath
|
||||||
|
'data.backup.webdav.path': string
|
||||||
|
// redux/settings/webdavSkipBackupFile
|
||||||
|
'data.backup.webdav.skip_backup_file': boolean
|
||||||
|
// redux/settings/webdavSyncInterval
|
||||||
|
'data.backup.webdav.sync_interval': number
|
||||||
|
// redux/settings/webdavUser
|
||||||
|
'data.backup.webdav.user': string
|
||||||
|
// redux/settings/excludeCitationsInExport
|
||||||
|
'data.export.markdown.exclude_citations': boolean
|
||||||
|
// redux/settings/forceDollarMathInMarkdown
|
||||||
|
'data.export.markdown.force_dollar_math': boolean
|
||||||
|
// redux/settings/markdownExportPath
|
||||||
|
'data.export.markdown.path': string | null
|
||||||
|
// redux/settings/showModelNameInMarkdown
|
||||||
|
'data.export.markdown.show_model_name': boolean
|
||||||
|
// redux/settings/showModelProviderInMarkdown
|
||||||
|
'data.export.markdown.show_model_provider': boolean
|
||||||
|
// redux/settings/standardizeCitationsInExport
|
||||||
|
'data.export.markdown.standardize_citations': boolean
|
||||||
|
// redux/settings/useTopicNamingForMessageTitle
|
||||||
|
'data.export.markdown.use_topic_naming_for_message_title': boolean
|
||||||
|
// redux/settings/exportMenuOptions.docx
|
||||||
|
'data.export.menus.docx': boolean
|
||||||
|
// redux/settings/exportMenuOptions.image
|
||||||
|
'data.export.menus.image': boolean
|
||||||
|
// redux/settings/exportMenuOptions.joplin
|
||||||
|
'data.export.menus.joplin': boolean
|
||||||
|
// redux/settings/exportMenuOptions.markdown
|
||||||
|
'data.export.menus.markdown': boolean
|
||||||
|
// redux/settings/exportMenuOptions.markdown_reason
|
||||||
|
'data.export.menus.markdown_reason': boolean
|
||||||
|
// redux/settings/exportMenuOptions.notes
|
||||||
|
'data.export.menus.notes': boolean
|
||||||
|
// redux/settings/exportMenuOptions.notion
|
||||||
|
'data.export.menus.notion': boolean
|
||||||
|
// redux/settings/exportMenuOptions.obsidian
|
||||||
|
'data.export.menus.obsidian': boolean
|
||||||
|
// redux/settings/exportMenuOptions.plain_text
|
||||||
|
'data.export.menus.plain_text': boolean
|
||||||
|
// redux/settings/exportMenuOptions.siyuan
|
||||||
|
'data.export.menus.siyuan': boolean
|
||||||
|
// redux/settings/exportMenuOptions.yuque
|
||||||
|
'data.export.menus.yuque': boolean
|
||||||
|
// redux/settings/joplinExportReasoning
|
||||||
|
'data.integration.joplin.export_reasoning': boolean
|
||||||
|
// redux/settings/joplinToken
|
||||||
|
'data.integration.joplin.token': string
|
||||||
|
// redux/settings/joplinUrl
|
||||||
|
'data.integration.joplin.url': string
|
||||||
|
// redux/settings/notionApiKey
|
||||||
|
'data.integration.notion.api_key': string
|
||||||
|
// redux/settings/notionDatabaseID
|
||||||
|
'data.integration.notion.database_id': string
|
||||||
|
// redux/settings/notionExportReasoning
|
||||||
|
'data.integration.notion.export_reasoning': boolean
|
||||||
|
// redux/settings/notionPageNameKey
|
||||||
|
'data.integration.notion.page_name_key': string
|
||||||
|
// redux/settings/defaultObsidianVault
|
||||||
|
'data.integration.obsidian.default_vault': string
|
||||||
|
// redux/settings/siyuanApiUrl
|
||||||
|
'data.integration.siyuan.api_url': string | null
|
||||||
|
// redux/settings/siyuanBoxId
|
||||||
|
'data.integration.siyuan.box_id': string | null
|
||||||
|
// redux/settings/siyuanRootPath
|
||||||
|
'data.integration.siyuan.root_path': string | null
|
||||||
|
// redux/settings/siyuanToken
|
||||||
|
'data.integration.siyuan.token': string | null
|
||||||
|
// redux/settings/yuqueRepoId
|
||||||
|
'data.integration.yuque.repo_id': string
|
||||||
|
// redux/settings/yuqueToken
|
||||||
|
'data.integration.yuque.token': string
|
||||||
|
// redux/settings/yuqueUrl
|
||||||
|
'data.integration.yuque.url': string
|
||||||
|
// redux/settings/apiServer.apiKey
|
||||||
|
'feature.csaas.api_key': string
|
||||||
|
// redux/settings/apiServer.enabled
|
||||||
|
'feature.csaas.enabled': boolean
|
||||||
|
// redux/settings/apiServer.host
|
||||||
|
'feature.csaas.host': string
|
||||||
|
// redux/settings/apiServer.port
|
||||||
|
'feature.csaas.port': number
|
||||||
|
// redux/settings/maxKeepAliveMinapps
|
||||||
|
'feature.minapp.max_keep_alive': number
|
||||||
|
// redux/settings/minappsOpenLinkExternal
|
||||||
|
'feature.minapp.open_link_external': boolean
|
||||||
|
// redux/settings/showOpenedMinappsInSidebar
|
||||||
|
'feature.minapp.show_opened_in_sidebar': boolean
|
||||||
|
// redux/note/settings.defaultEditMode
|
||||||
|
'feature.notes.default_edit_mode': string
|
||||||
|
// redux/note/settings.defaultViewMode
|
||||||
|
'feature.notes.default_view_mode': string
|
||||||
|
// redux/note/settings.fontFamily
|
||||||
|
'feature.notes.font_family': string
|
||||||
|
// redux/note/settings.fontSize
|
||||||
|
'feature.notes.font_size': number
|
||||||
|
// redux/note/settings.isFullWidth
|
||||||
|
'feature.notes.full_width': boolean
|
||||||
|
// redux/note/notesPath
|
||||||
|
'feature.notes.path': string
|
||||||
|
// redux/note/settings.showTabStatus
|
||||||
|
'feature.notes.show_tab_status': boolean
|
||||||
|
// redux/note/settings.showTableOfContents
|
||||||
|
'feature.notes.show_table_of_contents': boolean
|
||||||
|
// redux/note/settings.showWorkspace
|
||||||
|
'feature.notes.show_workspace': boolean
|
||||||
|
// redux/note/sortType
|
||||||
|
'feature.notes.sort_type': string
|
||||||
|
// redux/settings/clickTrayToShowQuickAssistant
|
||||||
|
'feature.quick_assistant.click_tray_to_show': boolean
|
||||||
|
// redux/settings/enableQuickAssistant
|
||||||
|
'feature.quick_assistant.enabled': boolean
|
||||||
|
// redux/settings/readClipboardAtStartup
|
||||||
|
'feature.quick_assistant.read_clipboard_at_startup': boolean
|
||||||
|
// redux/selectionStore/actionItems
|
||||||
|
'feature.selection.action_items': PreferenceTypes.SelectionActionItem[]
|
||||||
|
// redux/selectionStore/actionWindowOpacity
|
||||||
|
'feature.selection.action_window_opacity': number
|
||||||
|
// redux/selectionStore/isAutoClose
|
||||||
|
'feature.selection.auto_close': boolean
|
||||||
|
// redux/selectionStore/isAutoPin
|
||||||
|
'feature.selection.auto_pin': boolean
|
||||||
|
// redux/selectionStore/isCompact
|
||||||
|
'feature.selection.compact': boolean
|
||||||
|
// redux/selectionStore/selectionEnabled
|
||||||
|
'feature.selection.enabled': boolean
|
||||||
|
// redux/selectionStore/filterList
|
||||||
|
'feature.selection.filter_list': string[]
|
||||||
|
// redux/selectionStore/filterMode
|
||||||
|
'feature.selection.filter_mode': PreferenceTypes.SelectionFilterMode
|
||||||
|
// redux/selectionStore/isFollowToolbar
|
||||||
|
'feature.selection.follow_toolbar': boolean
|
||||||
|
// redux/selectionStore/isRemeberWinSize
|
||||||
|
'feature.selection.remember_win_size': boolean
|
||||||
|
// redux/selectionStore/triggerMode
|
||||||
|
'feature.selection.trigger_mode': PreferenceTypes.SelectionTriggerMode
|
||||||
|
// redux/settings/translateModelPrompt
|
||||||
|
'feature.translate.model_prompt': string
|
||||||
|
// redux/settings/targetLanguage
|
||||||
|
'feature.translate.target_language': string
|
||||||
|
// redux/ocr/imageProviderId
|
||||||
|
'ocr.settings.image_provider_id': string | null
|
||||||
|
// redux/shortcuts/shortcuts.exit_fullscreen
|
||||||
|
'shortcut.app.exit_fullscreen': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.search_message
|
||||||
|
'shortcut.app.search_message': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.show_app
|
||||||
|
'shortcut.app.show_main_window': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.mini_window
|
||||||
|
'shortcut.app.show_mini_window': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.show_settings
|
||||||
|
'shortcut.app.show_settings': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.toggle_show_assistants
|
||||||
|
'shortcut.app.toggle_show_assistants': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.zoom_in
|
||||||
|
'shortcut.app.zoom_in': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.zoom_out
|
||||||
|
'shortcut.app.zoom_out': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.zoom_reset
|
||||||
|
'shortcut.app.zoom_reset': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.clear_topic
|
||||||
|
'shortcut.chat.clear': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.copy_last_message
|
||||||
|
'shortcut.chat.copy_last_message': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.search_message_in_chat
|
||||||
|
'shortcut.chat.search_message': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.toggle_new_context
|
||||||
|
'shortcut.chat.toggle_new_context': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.selection_assistant_select_text
|
||||||
|
'shortcut.selection.get_text': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.selection_assistant_toggle
|
||||||
|
'shortcut.selection.toggle_enabled': Record<string, unknown>
|
||||||
|
// redux/shortcuts/shortcuts.new_topic
|
||||||
|
'shortcut.topic.new': Record<string, unknown>
|
||||||
|
// redux/settings/enableTopicNaming
|
||||||
|
'topic.naming.enabled': boolean
|
||||||
|
// redux/settings/topicNamingPrompt
|
||||||
|
'topic.naming_prompt': string
|
||||||
|
// redux/settings/topicPosition
|
||||||
|
'topic.position': string
|
||||||
|
// redux/settings/pinTopicsToTop
|
||||||
|
'topic.tab.pin_to_top': boolean
|
||||||
|
// redux/settings/showTopics
|
||||||
|
'topic.tab.show': boolean
|
||||||
|
// redux/settings/showTopicTime
|
||||||
|
'topic.tab.show_time': boolean
|
||||||
|
// redux/settings/customCss
|
||||||
|
'ui.custom_css': string
|
||||||
|
// redux/settings/navbarPosition
|
||||||
|
'ui.navbar.position': 'left' | 'top'
|
||||||
|
// redux/settings/sidebarIcons.disabled
|
||||||
|
'ui.sidebar.icons.invisible': PreferenceTypes.SidebarIcon[]
|
||||||
|
// redux/settings/sidebarIcons.visible
|
||||||
|
'ui.sidebar.icons.visible': PreferenceTypes.SidebarIcon[]
|
||||||
|
// redux/settings/theme
|
||||||
|
'ui.theme_mode': PreferenceTypes.ThemeMode
|
||||||
|
// redux/settings/userTheme.userCodeFontFamily
|
||||||
|
'ui.theme_user.code_font_family': string
|
||||||
|
// redux/settings/userTheme.colorPrimary
|
||||||
|
'ui.theme_user.color_primary': string
|
||||||
|
// redux/settings/userTheme.userFontFamily
|
||||||
|
'ui.theme_user.font_family': string
|
||||||
|
// redux/settings/windowStyle
|
||||||
|
'ui.window_style': PreferenceTypes.WindowStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint sort-keys: ["error", "asc", {"caseSensitive": true, "natural": false}] */
|
||||||
|
export const DefaultPreferences: PreferenceSchemas = {
|
||||||
|
default: {
|
||||||
|
'app.developer_mode.enabled': false,
|
||||||
|
'app.disable_hardware_acceleration': false,
|
||||||
|
'app.dist.auto_update.enabled': true,
|
||||||
|
'app.dist.test_plan.channel': PreferenceTypes.UpgradeChannel.LATEST,
|
||||||
|
'app.dist.test_plan.enabled': false,
|
||||||
|
'app.language': null,
|
||||||
|
'app.launch_on_boot': false,
|
||||||
|
'app.notification.assistant.enabled': false,
|
||||||
|
'app.notification.backup.enabled': false,
|
||||||
|
'app.notification.knowledge.enabled': false,
|
||||||
|
'app.privacy.data_collection.enabled': false,
|
||||||
|
'app.proxy.bypass_rules': '',
|
||||||
|
'app.proxy.mode': 'system',
|
||||||
|
'app.proxy.url': '',
|
||||||
|
'app.spell_check.enabled': false,
|
||||||
|
'app.spell_check.languages': [],
|
||||||
|
'app.tray.enabled': true,
|
||||||
|
'app.tray.on_close': true,
|
||||||
|
'app.tray.on_launch': false,
|
||||||
|
'app.user.id': 'uuid()',
|
||||||
|
'app.user.name': '',
|
||||||
|
'app.zoom_factor': 1,
|
||||||
|
'assistant.click_to_show_topic': true,
|
||||||
|
'assistant.icon_type': 'emoji',
|
||||||
|
'assistant.tab.show': true,
|
||||||
|
'assistant.tab.sort_type': 'list',
|
||||||
|
'chat.code.collapsible': false,
|
||||||
|
'chat.code.editor.autocompletion': true,
|
||||||
|
'chat.code.editor.enabled': false,
|
||||||
|
'chat.code.editor.fold_gutter': false,
|
||||||
|
'chat.code.editor.highlight_active_line': false,
|
||||||
|
'chat.code.editor.keymap': false,
|
||||||
|
'chat.code.editor.theme_dark': 'auto',
|
||||||
|
'chat.code.editor.theme_light': 'auto',
|
||||||
|
'chat.code.execution.enabled': false,
|
||||||
|
'chat.code.execution.timeout_minutes': 1,
|
||||||
|
'chat.code.fancy_block': true,
|
||||||
|
'chat.code.image_tools': false,
|
||||||
|
'chat.code.preview.theme_dark': 'auto',
|
||||||
|
'chat.code.preview.theme_light': 'auto',
|
||||||
|
'chat.code.show_line_numbers': false,
|
||||||
|
'chat.code.viewer.theme_dark': 'auto',
|
||||||
|
'chat.code.viewer.theme_light': 'auto',
|
||||||
|
'chat.code.wrappable': false,
|
||||||
|
'chat.input.paste_long_text_as_file': false,
|
||||||
|
'chat.input.paste_long_text_threshold': 1500,
|
||||||
|
'chat.input.quick_panel.triggers_enabled': false,
|
||||||
|
'chat.input.send_message_shortcut': 'Enter',
|
||||||
|
'chat.input.show_estimated_tokens': false,
|
||||||
|
'chat.input.translate.auto_translate_with_space': false,
|
||||||
|
'chat.input.translate.show_confirm': true,
|
||||||
|
'chat.message.confirm_delete': true,
|
||||||
|
'chat.message.confirm_regenerate': true,
|
||||||
|
'chat.message.font': 'system',
|
||||||
|
'chat.message.font_size': 14,
|
||||||
|
'chat.message.math.engine': 'KaTeX',
|
||||||
|
'chat.message.math.single_dollar': true,
|
||||||
|
'chat.message.multi_model.fold_display_mode': 'expanded',
|
||||||
|
'chat.message.multi_model.grid_columns': 2,
|
||||||
|
'chat.message.multi_model.grid_popover_trigger': 'click',
|
||||||
|
'chat.message.multi_model.style': 'horizontal',
|
||||||
|
'chat.message.navigation_mode': 'none',
|
||||||
|
'chat.message.render_as_markdown': false,
|
||||||
|
'chat.message.show_divider': true,
|
||||||
|
'chat.message.show_outline': false,
|
||||||
|
'chat.message.show_prompt': true,
|
||||||
|
'chat.message.style': 'plain',
|
||||||
|
'chat.message.thought.auto_collapse': true,
|
||||||
|
'chat.narrow_mode': false,
|
||||||
|
'data.backup.general.skip_backup_file': false,
|
||||||
|
'data.backup.local.auto_sync': false,
|
||||||
|
'data.backup.local.dir': '',
|
||||||
|
'data.backup.local.max_backups': 0,
|
||||||
|
'data.backup.local.skip_backup_file': false,
|
||||||
|
'data.backup.local.sync_interval': 0,
|
||||||
|
'data.backup.nutstore.auto_sync': false,
|
||||||
|
'data.backup.nutstore.max_backups': 0,
|
||||||
|
'data.backup.nutstore.path': '/cherry-studio',
|
||||||
|
'data.backup.nutstore.skip_backup_file': false,
|
||||||
|
'data.backup.nutstore.sync_interval': 0,
|
||||||
|
'data.backup.nutstore.token': '',
|
||||||
|
'data.backup.s3.access_key_id': '',
|
||||||
|
'data.backup.s3.auto_sync': false,
|
||||||
|
'data.backup.s3.bucket': '',
|
||||||
|
'data.backup.s3.endpoint': '',
|
||||||
|
'data.backup.s3.max_backups': 0,
|
||||||
|
'data.backup.s3.region': '',
|
||||||
|
'data.backup.s3.root': '',
|
||||||
|
'data.backup.s3.secret_access_key': '',
|
||||||
|
'data.backup.s3.skip_backup_file': false,
|
||||||
|
'data.backup.s3.sync_interval': 0,
|
||||||
|
'data.backup.webdav.auto_sync': false,
|
||||||
|
'data.backup.webdav.disable_stream': false,
|
||||||
|
'data.backup.webdav.host': '',
|
||||||
|
'data.backup.webdav.max_backups': 0,
|
||||||
|
'data.backup.webdav.pass': '',
|
||||||
|
'data.backup.webdav.path': '/cherry-studio',
|
||||||
|
'data.backup.webdav.skip_backup_file': false,
|
||||||
|
'data.backup.webdav.sync_interval': 0,
|
||||||
|
'data.backup.webdav.user': '',
|
||||||
|
'data.export.markdown.exclude_citations': false,
|
||||||
|
'data.export.markdown.force_dollar_math': false,
|
||||||
|
'data.export.markdown.path': null,
|
||||||
|
'data.export.markdown.show_model_name': false,
|
||||||
|
'data.export.markdown.show_model_provider': false,
|
||||||
|
'data.export.markdown.standardize_citations': false,
|
||||||
|
'data.export.markdown.use_topic_naming_for_message_title': false,
|
||||||
|
'data.export.menus.docx': true,
|
||||||
|
'data.export.menus.image': true,
|
||||||
|
'data.export.menus.joplin': true,
|
||||||
|
'data.export.menus.markdown': true,
|
||||||
|
'data.export.menus.markdown_reason': true,
|
||||||
|
'data.export.menus.notes': true,
|
||||||
|
'data.export.menus.notion': true,
|
||||||
|
'data.export.menus.obsidian': true,
|
||||||
|
'data.export.menus.plain_text': true,
|
||||||
|
'data.export.menus.siyuan': true,
|
||||||
|
'data.export.menus.yuque': true,
|
||||||
|
'data.integration.joplin.export_reasoning': false,
|
||||||
|
'data.integration.joplin.token': '',
|
||||||
|
'data.integration.joplin.url': '',
|
||||||
|
'data.integration.notion.api_key': '',
|
||||||
|
'data.integration.notion.database_id': '',
|
||||||
|
'data.integration.notion.export_reasoning': false,
|
||||||
|
'data.integration.notion.page_name_key': 'Name',
|
||||||
|
'data.integration.obsidian.default_vault': '',
|
||||||
|
'data.integration.siyuan.api_url': null,
|
||||||
|
'data.integration.siyuan.box_id': null,
|
||||||
|
'data.integration.siyuan.root_path': null,
|
||||||
|
'data.integration.siyuan.token': null,
|
||||||
|
'data.integration.yuque.repo_id': '',
|
||||||
|
'data.integration.yuque.token': '',
|
||||||
|
'data.integration.yuque.url': '',
|
||||||
|
'feature.csaas.api_key': '`cs-sk-${uuid()}`',
|
||||||
|
'feature.csaas.enabled': false,
|
||||||
|
'feature.csaas.host': 'localhost',
|
||||||
|
'feature.csaas.port': 23333,
|
||||||
|
'feature.minapp.max_keep_alive': 3,
|
||||||
|
'feature.minapp.open_link_external': false,
|
||||||
|
'feature.minapp.show_opened_in_sidebar': true,
|
||||||
|
'feature.notes.default_edit_mode': 'preview',
|
||||||
|
'feature.notes.default_view_mode': 'edit',
|
||||||
|
'feature.notes.font_family': 'default',
|
||||||
|
'feature.notes.font_size': 16,
|
||||||
|
'feature.notes.full_width': true,
|
||||||
|
'feature.notes.path': '',
|
||||||
|
'feature.notes.show_tab_status': true,
|
||||||
|
'feature.notes.show_table_of_contents': true,
|
||||||
|
'feature.notes.show_workspace': true,
|
||||||
|
'feature.notes.sort_type': 'sort_a2z',
|
||||||
|
'feature.quick_assistant.click_tray_to_show': false,
|
||||||
|
'feature.quick_assistant.enabled': false,
|
||||||
|
'feature.quick_assistant.read_clipboard_at_startup': true,
|
||||||
|
'feature.selection.action_items': [
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
icon: 'languages',
|
||||||
|
id: 'translate',
|
||||||
|
isBuiltIn: true,
|
||||||
|
name: 'selection.action.builtin.translate'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
icon: 'file-question',
|
||||||
|
id: 'explain',
|
||||||
|
isBuiltIn: true,
|
||||||
|
name: 'selection.action.builtin.explain'
|
||||||
|
},
|
||||||
|
{ enabled: true, icon: 'scan-text', id: 'summary', isBuiltIn: true, name: 'selection.action.builtin.summary' },
|
||||||
|
{
|
||||||
|
enabled: true,
|
||||||
|
icon: 'search',
|
||||||
|
id: 'search',
|
||||||
|
isBuiltIn: true,
|
||||||
|
name: 'selection.action.builtin.search',
|
||||||
|
searchEngine: 'Google|https://www.google.com/search?q={{queryString}}'
|
||||||
|
},
|
||||||
|
{ enabled: true, icon: 'clipboard-copy', id: 'copy', isBuiltIn: true, name: 'selection.action.builtin.copy' },
|
||||||
|
{ enabled: false, icon: 'wand-sparkles', id: 'refine', isBuiltIn: true, name: 'selection.action.builtin.refine' },
|
||||||
|
{ enabled: false, icon: 'quote', id: 'quote', isBuiltIn: true, name: 'selection.action.builtin.quote' }
|
||||||
|
],
|
||||||
|
'feature.selection.action_window_opacity': 100,
|
||||||
|
'feature.selection.auto_close': false,
|
||||||
|
'feature.selection.auto_pin': false,
|
||||||
|
'feature.selection.compact': false,
|
||||||
|
'feature.selection.enabled': false,
|
||||||
|
'feature.selection.filter_list': [],
|
||||||
|
'feature.selection.filter_mode': PreferenceTypes.SelectionFilterMode.Default,
|
||||||
|
'feature.selection.follow_toolbar': true,
|
||||||
|
'feature.selection.remember_win_size': false,
|
||||||
|
'feature.selection.trigger_mode': PreferenceTypes.SelectionTriggerMode.Selected,
|
||||||
|
'feature.translate.model_prompt': TRANSLATE_PROMPT,
|
||||||
|
'feature.translate.target_language': 'en-us',
|
||||||
|
'ocr.settings.image_provider_id': null,
|
||||||
|
'shortcut.app.exit_fullscreen': { editable: false, enabled: true, key: ['Escape'], system: true },
|
||||||
|
'shortcut.app.search_message': {
|
||||||
|
editable: true,
|
||||||
|
enabled: true,
|
||||||
|
key: ['CommandOrControl', 'Shift', 'F'],
|
||||||
|
system: false
|
||||||
|
},
|
||||||
|
'shortcut.app.show_main_window': { editable: true, enabled: true, key: [], system: true },
|
||||||
|
'shortcut.app.show_mini_window': { editable: true, enabled: false, key: ['CommandOrControl', 'E'], system: true },
|
||||||
|
'shortcut.app.show_settings': { editable: false, enabled: true, key: ['CommandOrControl', ','], system: true },
|
||||||
|
'shortcut.app.toggle_show_assistants': {
|
||||||
|
editable: true,
|
||||||
|
enabled: true,
|
||||||
|
key: ['CommandOrControl', '['],
|
||||||
|
system: false
|
||||||
|
},
|
||||||
|
'shortcut.app.zoom_in': { editable: false, enabled: true, key: ['CommandOrControl', '='], system: true },
|
||||||
|
'shortcut.app.zoom_out': { editable: false, enabled: true, key: ['CommandOrControl', '-'], system: true },
|
||||||
|
'shortcut.app.zoom_reset': { editable: false, enabled: true, key: ['CommandOrControl', '0'], system: true },
|
||||||
|
'shortcut.chat.clear': { editable: true, enabled: true, key: ['CommandOrControl', 'L'], system: false },
|
||||||
|
'shortcut.chat.copy_last_message': {
|
||||||
|
editable: true,
|
||||||
|
enabled: false,
|
||||||
|
key: ['CommandOrControl', 'Shift', 'C'],
|
||||||
|
system: false
|
||||||
|
},
|
||||||
|
'shortcut.chat.search_message': { editable: true, enabled: true, key: ['CommandOrControl', 'F'], system: false },
|
||||||
|
'shortcut.chat.toggle_new_context': {
|
||||||
|
editable: true,
|
||||||
|
enabled: true,
|
||||||
|
key: ['CommandOrControl', 'K'],
|
||||||
|
system: false
|
||||||
|
},
|
||||||
|
'shortcut.selection.get_text': { editable: true, enabled: false, key: [], system: true },
|
||||||
|
'shortcut.selection.toggle_enabled': { editable: true, enabled: false, key: [], system: true },
|
||||||
|
'shortcut.topic.new': { editable: true, enabled: true, key: ['CommandOrControl', 'N'], system: false },
|
||||||
|
'topic.naming.enabled': true,
|
||||||
|
'topic.naming_prompt': '',
|
||||||
|
'topic.position': 'left',
|
||||||
|
'topic.tab.pin_to_top': false,
|
||||||
|
'topic.tab.show': true,
|
||||||
|
'topic.tab.show_time': false,
|
||||||
|
'ui.custom_css': '',
|
||||||
|
'ui.navbar.position': 'top',
|
||||||
|
'ui.sidebar.icons.invisible': [],
|
||||||
|
'ui.sidebar.icons.visible': [
|
||||||
|
'assistants',
|
||||||
|
'store',
|
||||||
|
'paintings',
|
||||||
|
'translate',
|
||||||
|
'minapp',
|
||||||
|
'knowledge',
|
||||||
|
'files',
|
||||||
|
'code_tools',
|
||||||
|
'notes'
|
||||||
|
],
|
||||||
|
'ui.theme_mode': PreferenceTypes.ThemeMode.system,
|
||||||
|
'ui.theme_user.code_font_family': '',
|
||||||
|
'ui.theme_user.color_primary': '#00b96b',
|
||||||
|
'ui.theme_user.font_family': '',
|
||||||
|
'ui.window_style': 'opaque'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === AUTO-GENERATED CONTENT END ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成统计:
|
||||||
|
* - 总配置项: 197
|
||||||
|
* - electronStore项: 1
|
||||||
|
* - redux项: 196
|
||||||
|
* - localStorage项: 0
|
||||||
|
*/
|
||||||
97
packages/shared/data/preference/preferenceTypes.ts
Normal file
97
packages/shared/data/preference/preferenceTypes.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import type { PreferenceSchemas } from './preferenceSchemas'
|
||||||
|
|
||||||
|
export type PreferenceDefaultScopeType = PreferenceSchemas['default']
|
||||||
|
export type PreferenceKeyType = keyof PreferenceDefaultScopeType
|
||||||
|
|
||||||
|
export type PreferenceUpdateOptions = {
|
||||||
|
optimistic: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PreferenceShortcutType = {
|
||||||
|
key: string[]
|
||||||
|
editable: boolean
|
||||||
|
enabled: boolean
|
||||||
|
system: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SelectionTriggerMode {
|
||||||
|
Selected = 'selected',
|
||||||
|
Ctrlkey = 'ctrlkey',
|
||||||
|
Shortcut = 'shortcut'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SelectionFilterMode {
|
||||||
|
Default = 'default',
|
||||||
|
Whitelist = 'whitelist',
|
||||||
|
Blacklist = 'blacklist'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectionActionItem = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
enabled: boolean
|
||||||
|
isBuiltIn: boolean
|
||||||
|
icon?: string
|
||||||
|
prompt?: string
|
||||||
|
assistantId?: string
|
||||||
|
selectedText?: string
|
||||||
|
searchEngine?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ThemeMode {
|
||||||
|
light = 'light',
|
||||||
|
dark = 'dark',
|
||||||
|
system = 'system'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 有限的UI语言 */
|
||||||
|
export type LanguageVarious =
|
||||||
|
| 'zh-CN'
|
||||||
|
| 'zh-TW'
|
||||||
|
| 'el-GR'
|
||||||
|
| 'en-US'
|
||||||
|
| 'es-ES'
|
||||||
|
| 'fr-FR'
|
||||||
|
| 'ja-JP'
|
||||||
|
| 'pt-PT'
|
||||||
|
| 'ru-RU'
|
||||||
|
| 'de-DE'
|
||||||
|
|
||||||
|
export type WindowStyle = 'transparent' | 'opaque'
|
||||||
|
|
||||||
|
export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter' | 'Alt+Enter'
|
||||||
|
|
||||||
|
export type AssistantTabSortType = 'tags' | 'list'
|
||||||
|
|
||||||
|
export type SidebarIcon =
|
||||||
|
| 'assistants'
|
||||||
|
| 'store'
|
||||||
|
| 'paintings'
|
||||||
|
| 'translate'
|
||||||
|
| 'minapp'
|
||||||
|
| 'knowledge'
|
||||||
|
| 'files'
|
||||||
|
| 'code_tools'
|
||||||
|
| 'notes'
|
||||||
|
|
||||||
|
export type AssistantIconType = 'model' | 'emoji' | 'none'
|
||||||
|
|
||||||
|
export type ProxyMode = 'system' | 'custom' | 'none'
|
||||||
|
|
||||||
|
export type MultiModelFoldDisplayMode = 'expanded' | 'compact'
|
||||||
|
|
||||||
|
export type MathEngine = 'KaTeX' | 'MathJax' | 'none'
|
||||||
|
|
||||||
|
export enum UpgradeChannel {
|
||||||
|
LATEST = 'latest', // 最新稳定版本
|
||||||
|
RC = 'rc', // 公测版本
|
||||||
|
BETA = 'beta' // 预览版本
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChatMessageStyle = 'plain' | 'bubble'
|
||||||
|
|
||||||
|
export type ChatMessageNavigationMode = 'none' | 'buttons' | 'anchor'
|
||||||
|
|
||||||
|
export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid'
|
||||||
|
|
||||||
|
export type MultiModelGridPopoverTrigger = 'hover' | 'click'
|
||||||
2
packages/shared/utils/index.ts
Normal file
2
packages/shared/utils/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './json'
|
||||||
|
export * from './net'
|
||||||
7
packages/shared/utils/json.ts
Normal file
7
packages/shared/utils/json.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function safeParseJson(text: string): unknown | null {
|
||||||
|
try {
|
||||||
|
return JSON.parse(text)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
15
packages/ui/.gitignore
vendored
Normal file
15
packages/ui/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Storybook build output
|
||||||
|
storybook-static/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
17
packages/ui/.storybook/main.ts
Normal file
17
packages/ui/.storybook/main.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/react-vite'
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../stories/components/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||||
|
addons: ['@storybook/addon-docs', '@storybook/addon-themes'],
|
||||||
|
framework: '@storybook/react-vite',
|
||||||
|
viteFinal: async (config) => {
|
||||||
|
const { mergeConfig } = await import('vite')
|
||||||
|
// 动态导入 @tailwindcss/vite 以避免 ESM/CJS 兼容性问题
|
||||||
|
const tailwindPlugin = (await import('@tailwindcss/vite')).default
|
||||||
|
return mergeConfig(config, {
|
||||||
|
plugins: [tailwindPlugin()]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config
|
||||||
18
packages/ui/.storybook/preview.tsx
Normal file
18
packages/ui/.storybook/preview.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import '../stories/tailwind.css'
|
||||||
|
|
||||||
|
import { withThemeByClassName } from '@storybook/addon-themes'
|
||||||
|
import type { Preview } from '@storybook/react'
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
decorators: [
|
||||||
|
withThemeByClassName({
|
||||||
|
themes: {
|
||||||
|
light: '',
|
||||||
|
dark: 'dark'
|
||||||
|
},
|
||||||
|
defaultTheme: 'light'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default preview
|
||||||
151
packages/ui/MIGRATION_STATUS.md
Normal file
151
packages/ui/MIGRATION_STATUS.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# UI Component Library Migration Status
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Import components from @cherrystudio/ui
|
||||||
|
import { Spinner, DividerWithText, InfoTooltip } from '@cherrystudio/ui'
|
||||||
|
|
||||||
|
// Use in components
|
||||||
|
function MyComponent() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Spinner size={24} />
|
||||||
|
<DividerWithText text="Divider Text" />
|
||||||
|
<InfoTooltip content="Tooltip message" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
@packages/ui/
|
||||||
|
├── src/
|
||||||
|
│ ├── components/ # Main components directory
|
||||||
|
│ │ ├── base/ # Basic components (buttons, inputs, labels, etc.)
|
||||||
|
│ │ ├── display/ # Display components (cards, lists, tables, etc.)
|
||||||
|
│ │ ├── layout/ # Layout components (containers, grids, spacing, etc.)
|
||||||
|
│ │ ├── icons/ # Icon components
|
||||||
|
│ │ ├── interactive/ # Interactive components (modals, tooltips, dropdowns, etc.)
|
||||||
|
│ │ └── composite/ # Composite components (made from multiple base components)
|
||||||
|
│ ├── hooks/ # Custom React Hooks
|
||||||
|
│ └── types/ # TypeScript type definitions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Classification Guide
|
||||||
|
|
||||||
|
When submitting PRs, please place components in the correct directory based on their function:
|
||||||
|
|
||||||
|
- **base**: Most basic UI elements like buttons, inputs, switches, labels, etc.
|
||||||
|
- **display**: Components for displaying content like cards, lists, tables, tabs, etc.
|
||||||
|
- **layout**: Components for page layout like containers, grid systems, dividers, etc.
|
||||||
|
- **icons**: All icon-related components
|
||||||
|
- **interactive**: Components requiring user interaction like modals, drawers, tooltips, dropdowns, etc.
|
||||||
|
- **composite**: Composite components made from multiple base components
|
||||||
|
|
||||||
|
## Migration Overview
|
||||||
|
|
||||||
|
- **Total Components**: 236
|
||||||
|
- **Migrated**: 34
|
||||||
|
- **Refactored**: 18
|
||||||
|
- **Pending Migration**: 184
|
||||||
|
|
||||||
|
## Component Status Table
|
||||||
|
|
||||||
|
| Category | Component Name | Migration Status | Refactoring Status | Description |
|
||||||
|
| ----------------- | ------------------------- | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| **base** | | | | Base components |
|
||||||
|
| | CopyButton | ✅ | ✅ | Copy button |
|
||||||
|
| | CustomTag | ✅ | ✅ | Custom tag |
|
||||||
|
| | DividerWithText | ✅ | ✅ | Divider with text |
|
||||||
|
| | EmojiIcon | ✅ | ✅ | Emoji icon |
|
||||||
|
| | ErrorBoundary | ✅ | ✅ | Error boundary (decoupled via props) |
|
||||||
|
| | StatusTag | ✅ | ✅ | Unified status tag (merged ErrorTag, SuccessTag, WarnTag, InfoTag) |
|
||||||
|
| | IndicatorLight | ✅ | ✅ | Indicator light |
|
||||||
|
| | Spinner | ✅ | ✅ | Loading spinner |
|
||||||
|
| | TextBadge | ✅ | ✅ | Text badge |
|
||||||
|
| | CustomCollapse | ✅ | ✅ | Custom collapse panel |
|
||||||
|
| **display** | | | | Display components |
|
||||||
|
| | Ellipsis | ✅ | ✅ | Text ellipsis |
|
||||||
|
| | ExpandableText | ✅ | ✅ | Expandable text |
|
||||||
|
| | ThinkingEffect | ✅ | ✅ | Thinking effect animation |
|
||||||
|
| | EmojiAvatar | ✅ | ✅ | Emoji avatar |
|
||||||
|
| | ListItem | ✅ | ✅ | List item |
|
||||||
|
| | MaxContextCount | ✅ | ✅ | Max context count display |
|
||||||
|
| | ProviderAvatar | ✅ | ✅ | Provider avatar |
|
||||||
|
| | CodeViewer | ❌ | ❌ | Code viewer (external deps) |
|
||||||
|
| | OGCard | ❌ | ❌ | OG card |
|
||||||
|
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown renderer |
|
||||||
|
| | Preview/* | ❌ | ❌ | Preview components |
|
||||||
|
| **layout** | | | | Layout components |
|
||||||
|
| | HorizontalScrollContainer | ✅ | ❌ | Horizontal scroll container |
|
||||||
|
| | Scrollbar | ✅ | ❌ | Scrollbar |
|
||||||
|
| | Layout/* | ✅ | ✅ | Layout components |
|
||||||
|
| | Tab/* | ❌ | ❌ | Tab (Redux dependency) |
|
||||||
|
| | TopView | ❌ | ❌ | Top view (window.api dependency) |
|
||||||
|
| **icons** | | | | Icon components |
|
||||||
|
| | Icon | ✅ | ✅ | Icon factory function and predefined icons (merged CopyIcon, DeleteIcon, EditIcon, RefreshIcon, ResetIcon, ToolIcon, VisionIcon, WebSearchIcon, WrapIcon, UnWrapIcon, OcrIcon) |
|
||||||
|
| | FileIcons | ✅ | ❌ | File icons (FileSvgIcon, FilePngIcon) |
|
||||||
|
| | ReasoningIcon | ✅ | ❌ | Reasoning icon |
|
||||||
|
| | SvgSpinners180Ring | ✅ | ❌ | Spinner loading icon |
|
||||||
|
| | ToolsCallingIcon | ✅ | ❌ | Tools calling icon |
|
||||||
|
| **interactive** | | | | Interactive components |
|
||||||
|
| | InfoTooltip | ✅ | ❌ | Info tooltip |
|
||||||
|
| | HelpTooltip | ✅ | ❌ | Help tooltip |
|
||||||
|
| | WarnTooltip | ✅ | ❌ | Warning tooltip |
|
||||||
|
| | EditableNumber | ✅ | ❌ | Editable number |
|
||||||
|
| | InfoPopover | ✅ | ❌ | Info popover |
|
||||||
|
| | CollapsibleSearchBar | ✅ | ❌ | Collapsible search bar |
|
||||||
|
| | ImageToolButton | ✅ | ❌ | Image tool button |
|
||||||
|
| | DraggableList | ✅ | ❌ | Draggable list |
|
||||||
|
| | CodeEditor | ✅ | ❌ | Code editor |
|
||||||
|
| | EmojiPicker | ❌ | ❌ | Emoji picker (useTheme dependency) |
|
||||||
|
| | Selector | ✅ | ❌ | Selector (i18n dependency) |
|
||||||
|
| | ModelSelector | ❌ | ❌ | Model selector (Redux dependency) |
|
||||||
|
| | LanguageSelect | ❌ | ❌ | Language select |
|
||||||
|
| | TranslateButton | ❌ | ❌ | Translate button (window.api dependency) |
|
||||||
|
| **composite** | | | | Composite components |
|
||||||
|
| | - | - | - | No composite components yet |
|
||||||
|
| **Uncategorized** | | | | Components needing categorization |
|
||||||
|
| | Popups/* (16+ files) | ❌ | ❌ | Popup components (business coupled) |
|
||||||
|
| | RichEditor/* (30+ files) | ❌ | ❌ | Rich text editor |
|
||||||
|
| | MarkdownEditor/* | ❌ | ❌ | Markdown editor |
|
||||||
|
| | MinApp/* | ❌ | ❌ | Mini app (Redux dependency) |
|
||||||
|
| | Avatar/* | ❌ | ❌ | Avatar components |
|
||||||
|
| | ActionTools/* | ❌ | ❌ | Action tools |
|
||||||
|
| | CodeBlockView/* | ❌ | ❌ | Code block view (window.api dependency) |
|
||||||
|
| | ContextMenu | ❌ | ❌ | Context menu (Electron API) |
|
||||||
|
| | WindowControls | ❌ | ❌ | Window controls (Electron API) |
|
||||||
|
| | ErrorBoundary | ❌ | ❌ | Error boundary (window.api dependency) |
|
||||||
|
|
||||||
|
## Migration Steps
|
||||||
|
|
||||||
|
### Phase 1: Copy Migration (Current Phase)
|
||||||
|
|
||||||
|
- Copy components as-is to @packages/ui
|
||||||
|
- Retain original dependencies (antd, styled-components, etc.)
|
||||||
|
- Add original path comment at file top
|
||||||
|
|
||||||
|
### Phase 2: Refactor and Optimize
|
||||||
|
|
||||||
|
- Remove antd dependencies, replace with HeroUI
|
||||||
|
- Remove styled-components, replace with Tailwind CSS
|
||||||
|
- Optimize component APIs and type definitions
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
1. **Do NOT migrate** components with these dependencies (can be migrated after decoupling):
|
||||||
|
- window.api calls
|
||||||
|
- Redux (useSelector, useDispatch, etc.)
|
||||||
|
- Other external data sources
|
||||||
|
|
||||||
|
2. **Can migrate** but need decoupling later:
|
||||||
|
- Components using i18n (change i18n to props)
|
||||||
|
- Components using antd (replace with HeroUI later)
|
||||||
|
|
||||||
|
3. **Submission Guidelines**:
|
||||||
|
- Each PR should focus on one category of components
|
||||||
|
- Ensure all migrated components are exported
|
||||||
|
- Update migration status in this document
|
||||||
200
packages/ui/README.md
Normal file
200
packages/ui/README.md
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
# @cherrystudio/ui
|
||||||
|
|
||||||
|
Cherry Studio UI 组件库 - 为 Cherry Studio 设计的 React 组件集合
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- 🎨 基于 Tailwind CSS 的现代化设计
|
||||||
|
- 📦 支持 ESM 和 CJS 格式
|
||||||
|
- 🔷 完整的 TypeScript 支持
|
||||||
|
- 🚀 可以作为 npm 包发布
|
||||||
|
- 🔧 开箱即用的常用 hooks 和工具函数
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装组件库
|
||||||
|
npm install @cherrystudio/ui
|
||||||
|
|
||||||
|
# 安装必需的 peer dependencies
|
||||||
|
npm install @heroui/react framer-motion react react-dom tailwindcss
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
### 1. Tailwind CSS v4 配置
|
||||||
|
|
||||||
|
本组件库使用 Tailwind CSS v4,配置方式已改变。在你的主 CSS 文件(如 `src/styles/tailwind.css`)中:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
/* 必须扫描组件库文件以提取类名 */
|
||||||
|
@source '../node_modules/@cherrystudio/ui/dist/**/*.{js,mjs}';
|
||||||
|
|
||||||
|
/* 你的应用源文件 */
|
||||||
|
@source './src/**/*.{js,ts,jsx,tsx}';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 如果你的应用直接使用 HeroUI 组件,需要添加:
|
||||||
|
* @source '../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
|
||||||
|
* @plugin '@heroui/react/plugin';
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 自定义主题配置(可选) */
|
||||||
|
@theme {
|
||||||
|
/* 你的主题扩展 */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:Tailwind CSS v4 不再使用 `tailwind.config.js` 文件,所有配置都在 CSS 中完成。
|
||||||
|
|
||||||
|
### 2. Provider 配置
|
||||||
|
|
||||||
|
在你的 App 根组件中添加 HeroUI Provider:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { HeroUIProvider } from '@heroui/react'
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<HeroUIProvider>
|
||||||
|
{/* 你的应用内容 */}
|
||||||
|
</HeroUIProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
### 基础组件
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { Button, Input } from '@cherrystudio/ui'
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button variant="primary" size="md">
|
||||||
|
点击我
|
||||||
|
</Button>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入内容"
|
||||||
|
onChange={(value) => console.log(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分模块导入
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 只导入组件
|
||||||
|
import { Button } from '@cherrystudio/ui/components'
|
||||||
|
|
||||||
|
// 只导入 hooks
|
||||||
|
import { useDebounce, useLocalStorage } from '@cherrystudio/ui/hooks'
|
||||||
|
|
||||||
|
// 只导入工具函数
|
||||||
|
import { cn, formatFileSize } from '@cherrystudio/ui/utils'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# 开发模式(监听文件变化)
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
# 构建
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# 类型检查
|
||||||
|
yarn type-check
|
||||||
|
|
||||||
|
# 运行测试
|
||||||
|
yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── components/ # React 组件
|
||||||
|
│ ├── Button/ # 按钮组件
|
||||||
|
│ ├── Input/ # 输入框组件
|
||||||
|
│ └── index.ts # 组件导出
|
||||||
|
├── hooks/ # React Hooks
|
||||||
|
├── utils/ # 工具函数
|
||||||
|
├── types/ # 类型定义
|
||||||
|
└── index.ts # 主入口文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件列表
|
||||||
|
|
||||||
|
### Button 按钮
|
||||||
|
|
||||||
|
支持多种变体和尺寸的按钮组件。
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
|
||||||
|
- `variant`: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger'
|
||||||
|
- `size`: 'sm' | 'md' | 'lg'
|
||||||
|
- `loading`: boolean
|
||||||
|
- `fullWidth`: boolean
|
||||||
|
- `leftIcon` / `rightIcon`: React.ReactNode
|
||||||
|
|
||||||
|
### Input 输入框
|
||||||
|
|
||||||
|
带有错误处理和密码显示切换的输入框组件。
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
|
||||||
|
- `type`: 'text' | 'password' | 'email' | 'number'
|
||||||
|
- `error`: boolean
|
||||||
|
- `errorMessage`: string
|
||||||
|
- `onChange`: (value: string) => void
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
### useDebounce
|
||||||
|
|
||||||
|
防抖处理,延迟执行状态更新。
|
||||||
|
|
||||||
|
### useLocalStorage
|
||||||
|
|
||||||
|
本地存储的 React Hook 封装。
|
||||||
|
|
||||||
|
### useClickOutside
|
||||||
|
|
||||||
|
检测点击元素外部区域。
|
||||||
|
|
||||||
|
### useCopyToClipboard
|
||||||
|
|
||||||
|
复制文本到剪贴板。
|
||||||
|
|
||||||
|
## 工具函数
|
||||||
|
|
||||||
|
### cn(...inputs)
|
||||||
|
|
||||||
|
基于 clsx 的类名合并工具,支持条件类名。
|
||||||
|
|
||||||
|
### formatFileSize(bytes)
|
||||||
|
|
||||||
|
格式化文件大小显示。
|
||||||
|
|
||||||
|
### debounce(func, delay)
|
||||||
|
|
||||||
|
防抖函数。
|
||||||
|
|
||||||
|
### throttle(func, delay)
|
||||||
|
|
||||||
|
节流函数。
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT
|
||||||
21
packages/ui/components.json
Normal file
21
packages/ui/components.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"aliases": {
|
||||||
|
"components": "@cherrystudio/ui/components",
|
||||||
|
"hooks": "@cherrystudio/ui/hooks",
|
||||||
|
"lib": "@cherrystudio/ui/lib",
|
||||||
|
"ui": "@cherrystudio/ui/components/primitives",
|
||||||
|
"utils": "@cherrystudio/ui/utils"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
|
"rsc": false,
|
||||||
|
"style": "new-york",
|
||||||
|
"tailwind": {
|
||||||
|
"baseColor": "zinc",
|
||||||
|
"config": "",
|
||||||
|
"css": "src/styles/globals.css",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"tsx": true
|
||||||
|
}
|
||||||
130
packages/ui/package.json
Normal file
130
packages/ui/package.json
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
{
|
||||||
|
"name": "@cherrystudio/ui",
|
||||||
|
"version": "1.0.0-alpha.1",
|
||||||
|
"description": "Cherry Studio UI Component Library - React Components for Cherry Studio",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/index.mjs",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"react-native": "dist/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsdown",
|
||||||
|
"dev": "tsc -w",
|
||||||
|
"clean": "rm -rf dist",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"lint": "eslint src --ext .ts,.tsx --fix",
|
||||||
|
"type-check": "tsc --noEmit -p tsconfig.json --composite false",
|
||||||
|
"storybook": "storybook dev -p 6006",
|
||||||
|
"build-storybook": "storybook build"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ui",
|
||||||
|
"components",
|
||||||
|
"react",
|
||||||
|
"tailwindcss",
|
||||||
|
"typescript",
|
||||||
|
"cherry-studio"
|
||||||
|
],
|
||||||
|
"author": "Cherry Studio",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/CherryHQ/cherry-studio.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/CherryHQ/cherry-studio/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/CherryHQ/cherry-studio#readme",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@heroui/react": "^2.8.4",
|
||||||
|
"framer-motion": "^11.0.0 || ^12.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"tailwindcss": "^4.1.13"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
|
"@radix-ui/react-radio-group": "^1.3.8",
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
|
"lucide-react": "^0.545.0",
|
||||||
|
"react-dropzone": "^14.3.8",
|
||||||
|
"tailwind-merge": "^2.5.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@heroui/react": "^2.8.4",
|
||||||
|
"@storybook/addon-docs": "^9.1.6",
|
||||||
|
"@storybook/addon-themes": "^9.1.6",
|
||||||
|
"@storybook/react-vite": "^9.1.6",
|
||||||
|
"@types/react": "^19.0.12",
|
||||||
|
"@types/react-dom": "^19.0.4",
|
||||||
|
"@types/styled-components": "^5.1.34",
|
||||||
|
"@uiw/codemirror-extensions-langs": "^4.25.1",
|
||||||
|
"@uiw/codemirror-themes-all": "^4.25.1",
|
||||||
|
"@uiw/react-codemirror": "^4.25.1",
|
||||||
|
"antd": "^5.22.5",
|
||||||
|
"eslint-plugin-storybook": "9.1.6",
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
|
"linguist-languages": "^9.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"storybook": "^9.1.6",
|
||||||
|
"styled-components": "^6.1.15",
|
||||||
|
"tsdown": "^0.15.5",
|
||||||
|
"tsx": "^4.20.5",
|
||||||
|
"typescript": "^5.6.2",
|
||||||
|
"vitest": "^3.2.4"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@codemirror/language": "6.11.3",
|
||||||
|
"@codemirror/lint": "6.8.5",
|
||||||
|
"@codemirror/view": "6.38.1"
|
||||||
|
},
|
||||||
|
"sideEffects": false,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"react-native": "./dist/index.js",
|
||||||
|
"import": "./dist/index.mjs",
|
||||||
|
"require": "./dist/index.js",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./components": {
|
||||||
|
"types": "./dist/components/index.d.ts",
|
||||||
|
"react-native": "./dist/components/index.js",
|
||||||
|
"import": "./dist/components/index.mjs",
|
||||||
|
"require": "./dist/components/index.js",
|
||||||
|
"default": "./dist/components/index.js"
|
||||||
|
},
|
||||||
|
"./hooks": {
|
||||||
|
"types": "./dist/hooks/index.d.ts",
|
||||||
|
"react-native": "./dist/hooks/index.js",
|
||||||
|
"import": "./dist/hooks/index.mjs",
|
||||||
|
"require": "./dist/hooks/index.js",
|
||||||
|
"default": "./dist/hooks/index.js"
|
||||||
|
},
|
||||||
|
"./utils": {
|
||||||
|
"types": "./dist/utils/index.d.ts",
|
||||||
|
"react-native": "./dist/utils/index.js",
|
||||||
|
"import": "./dist/utils/index.mjs",
|
||||||
|
"require": "./dist/utils/index.js",
|
||||||
|
"default": "./dist/utils/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@4.9.1"
|
||||||
|
}
|
||||||
139
packages/ui/src/components/composites/CodeEditor/CodeEditor.tsx
Normal file
139
packages/ui/src/components/composites/CodeEditor/CodeEditor.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import type { BasicSetupOptions } from '@uiw/react-codemirror'
|
||||||
|
import CodeMirror, { Annotation, EditorView } from '@uiw/react-codemirror'
|
||||||
|
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react'
|
||||||
|
import { memo } from 'react'
|
||||||
|
|
||||||
|
import { useBlurHandler, useHeightListener, useLanguageExtensions, useSaveKeymap } from './hooks'
|
||||||
|
import type { CodeEditorProps } from './types'
|
||||||
|
import { prepareCodeChanges } from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A code editor component based on CodeMirror.
|
||||||
|
* This is a wrapper of ReactCodeMirror.
|
||||||
|
*/
|
||||||
|
const CodeEditor = ({
|
||||||
|
ref,
|
||||||
|
value,
|
||||||
|
placeholder,
|
||||||
|
language,
|
||||||
|
languageConfig,
|
||||||
|
onSave,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
onHeightChange,
|
||||||
|
height,
|
||||||
|
maxHeight,
|
||||||
|
minHeight,
|
||||||
|
options,
|
||||||
|
extensions,
|
||||||
|
theme = 'light',
|
||||||
|
fontSize = 16,
|
||||||
|
style,
|
||||||
|
className,
|
||||||
|
editable = true,
|
||||||
|
readOnly = false,
|
||||||
|
expanded = true,
|
||||||
|
wrapped = true
|
||||||
|
}: CodeEditorProps) => {
|
||||||
|
const basicSetup = useMemo(() => {
|
||||||
|
return {
|
||||||
|
dropCursor: true,
|
||||||
|
allowMultipleSelections: true,
|
||||||
|
indentOnInput: true,
|
||||||
|
bracketMatching: true,
|
||||||
|
closeBrackets: true,
|
||||||
|
rectangularSelection: true,
|
||||||
|
crosshairCursor: true,
|
||||||
|
highlightActiveLineGutter: false,
|
||||||
|
highlightSelectionMatches: true,
|
||||||
|
closeBracketsKeymap: options?.keymap,
|
||||||
|
searchKeymap: options?.keymap,
|
||||||
|
foldKeymap: options?.keymap,
|
||||||
|
completionKeymap: options?.keymap,
|
||||||
|
lintKeymap: options?.keymap,
|
||||||
|
...(options as BasicSetupOptions)
|
||||||
|
}
|
||||||
|
}, [options])
|
||||||
|
|
||||||
|
const initialContent = useRef(options?.stream ? (value ?? '').trimEnd() : (value ?? ''))
|
||||||
|
const editorViewRef = useRef<EditorView | null>(null)
|
||||||
|
|
||||||
|
const langExtensions = useLanguageExtensions(language, options?.lint, languageConfig)
|
||||||
|
|
||||||
|
const handleSave = useCallback(() => {
|
||||||
|
const currentDoc = editorViewRef.current?.state.doc.toString() ?? ''
|
||||||
|
onSave?.(currentDoc)
|
||||||
|
}, [onSave])
|
||||||
|
|
||||||
|
// Calculate changes during streaming response to update EditorView
|
||||||
|
// Cannot handle user editing code during streaming response (and probably doesn't need to)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editorViewRef.current) return
|
||||||
|
|
||||||
|
const newContent = options?.stream ? (value ?? '').trimEnd() : (value ?? '')
|
||||||
|
const currentDoc = editorViewRef.current.state.doc.toString()
|
||||||
|
|
||||||
|
const changes = prepareCodeChanges(currentDoc, newContent)
|
||||||
|
|
||||||
|
if (changes && changes.length > 0) {
|
||||||
|
editorViewRef.current.dispatch({
|
||||||
|
changes,
|
||||||
|
annotations: [Annotation.define<boolean>().of(true)]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [options?.stream, value])
|
||||||
|
|
||||||
|
const saveKeymapExtension = useSaveKeymap({ onSave, enabled: options?.keymap })
|
||||||
|
const blurExtension = useBlurHandler({ onBlur })
|
||||||
|
const heightListenerExtension = useHeightListener({ onHeightChange })
|
||||||
|
|
||||||
|
const customExtensions = useMemo(() => {
|
||||||
|
return [
|
||||||
|
...(extensions ?? []),
|
||||||
|
...langExtensions,
|
||||||
|
...(wrapped ? [EditorView.lineWrapping] : []),
|
||||||
|
saveKeymapExtension,
|
||||||
|
blurExtension,
|
||||||
|
heightListenerExtension
|
||||||
|
].flat()
|
||||||
|
}, [extensions, langExtensions, wrapped, saveKeymapExtension, blurExtension, heightListenerExtension])
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
save: handleSave
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CodeMirror
|
||||||
|
// Set to a stable value to avoid triggering CodeMirror reset
|
||||||
|
value={initialContent.current}
|
||||||
|
placeholder={placeholder}
|
||||||
|
width="100%"
|
||||||
|
height={expanded ? undefined : height}
|
||||||
|
maxHeight={expanded ? undefined : maxHeight}
|
||||||
|
minHeight={minHeight}
|
||||||
|
editable={editable}
|
||||||
|
readOnly={readOnly}
|
||||||
|
theme={theme}
|
||||||
|
extensions={customExtensions}
|
||||||
|
onCreateEditor={(view: EditorView) => {
|
||||||
|
editorViewRef.current = view
|
||||||
|
onHeightChange?.(view.scrollDOM?.scrollHeight ?? 0)
|
||||||
|
}}
|
||||||
|
onChange={(value, viewUpdate) => {
|
||||||
|
if (onChange && viewUpdate.docChanged) onChange(value)
|
||||||
|
}}
|
||||||
|
basicSetup={basicSetup}
|
||||||
|
style={{
|
||||||
|
fontSize,
|
||||||
|
marginTop: 0,
|
||||||
|
borderRadius: 'inherit',
|
||||||
|
...style
|
||||||
|
}}
|
||||||
|
className={`code-editor ${className ?? ''}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeEditor.displayName = 'CodeEditor'
|
||||||
|
|
||||||
|
export default memo(CodeEditor)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { getNormalizedExtension } from '../utils'
|
||||||
|
|
||||||
|
const hoisted = vi.hoisted(() => ({
|
||||||
|
languages: {
|
||||||
|
svg: { extensions: ['.svg'] },
|
||||||
|
TypeScript: { extensions: ['.ts'] }
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@shared/config/languages', () => ({
|
||||||
|
languages: hoisted.languages
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('getNormalizedExtension', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return custom mapping for custom language', async () => {
|
||||||
|
await expect(getNormalizedExtension('svg')).resolves.toBe('xml')
|
||||||
|
await expect(getNormalizedExtension('SVG')).resolves.toBe('xml')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should prefer custom mapping when both custom and linguist exist', async () => {
|
||||||
|
await expect(getNormalizedExtension('svg')).resolves.toBe('xml')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return linguist mapping when available (strip leading dot)', async () => {
|
||||||
|
await expect(getNormalizedExtension('TypeScript')).resolves.toBe('ts')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return extension when input already looks like extension (leading dot)', async () => {
|
||||||
|
await expect(getNormalizedExtension('.json')).resolves.toBe('json')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return language as-is when no rules matched', async () => {
|
||||||
|
await expect(getNormalizedExtension('unknownLanguage')).resolves.toBe('unknownLanguage')
|
||||||
|
})
|
||||||
|
})
|
||||||
204
packages/ui/src/components/composites/CodeEditor/hooks.ts
Normal file
204
packages/ui/src/components/composites/CodeEditor/hooks.ts
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import { linter } from '@codemirror/lint' // statically imported by @uiw/codemirror-extensions-basic-setup
|
||||||
|
import { EditorView } from '@codemirror/view'
|
||||||
|
import type { Extension } from '@uiw/react-codemirror'
|
||||||
|
import { keymap } from '@uiw/react-codemirror'
|
||||||
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
import type { LanguageConfig } from './types'
|
||||||
|
import { getNormalizedExtension } from './utils'
|
||||||
|
|
||||||
|
/** 语言对应的 linter 加载器
|
||||||
|
* key: 语言文件扩展名(不包含 `.`)
|
||||||
|
*/
|
||||||
|
const linterLoaders: Record<string, () => Promise<any>> = {
|
||||||
|
json: async () => {
|
||||||
|
const jsonParseLinter = await import('@codemirror/lang-json').then((mod) => mod.jsonParseLinter)
|
||||||
|
return linter(jsonParseLinter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特殊语言加载器
|
||||||
|
* key: 语言文件扩展名(不包含 `.`)
|
||||||
|
*/
|
||||||
|
const specialLanguageLoaders: Record<string, () => Promise<Extension>> = {
|
||||||
|
dot: async () => {
|
||||||
|
const mod = await import('@viz-js/lang-dot')
|
||||||
|
return mod.dot()
|
||||||
|
},
|
||||||
|
// @uiw/codemirror-extensions-langs 4.25.1 移除了 mermaid 支持,这里加回来
|
||||||
|
mmd: async () => {
|
||||||
|
const mod = await import('codemirror-lang-mermaid')
|
||||||
|
return mod.mermaid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载语言扩展
|
||||||
|
*/
|
||||||
|
async function loadLanguageExtension(language: string, languageConfig?: LanguageConfig): Promise<Extension | null> {
|
||||||
|
const fileExt = await getNormalizedExtension(language, languageConfig)
|
||||||
|
|
||||||
|
// 尝试加载特殊语言
|
||||||
|
const specialLoader = specialLanguageLoaders[fileExt]
|
||||||
|
if (specialLoader) {
|
||||||
|
try {
|
||||||
|
return await specialLoader()
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(`Failed to load language ${language} (${fileExt})`, error as Error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退到 uiw/codemirror 包含的语言
|
||||||
|
try {
|
||||||
|
const { loadLanguage } = await import('@uiw/codemirror-extensions-langs')
|
||||||
|
const extension = loadLanguage(fileExt as any)
|
||||||
|
return extension || null
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(`Failed to load language ${language} (${fileExt})`, error as Error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载 linter 扩展
|
||||||
|
*/
|
||||||
|
async function loadLinterExtension(language: string, languageConfig?: LanguageConfig): Promise<Extension | null> {
|
||||||
|
const fileExt = await getNormalizedExtension(language, languageConfig)
|
||||||
|
|
||||||
|
const loader = linterLoaders[fileExt]
|
||||||
|
if (!loader) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await loader()
|
||||||
|
} catch (error) {
|
||||||
|
console.debug(`Failed to load linter for ${language} (${fileExt})`, error as Error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载语言相关扩展
|
||||||
|
*/
|
||||||
|
export const useLanguageExtensions = (language: string, lint?: boolean, languageConfig?: LanguageConfig) => {
|
||||||
|
const [extensions, setExtensions] = useState<Extension[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
|
||||||
|
const loadAllExtensions = async () => {
|
||||||
|
try {
|
||||||
|
// 加载所有扩展
|
||||||
|
const [languageResult, linterResult] = await Promise.allSettled([
|
||||||
|
loadLanguageExtension(language, languageConfig),
|
||||||
|
lint ? loadLinterExtension(language, languageConfig) : Promise.resolve(null)
|
||||||
|
])
|
||||||
|
|
||||||
|
if (cancelled) return
|
||||||
|
|
||||||
|
const results: Extension[] = []
|
||||||
|
|
||||||
|
// 语言扩展
|
||||||
|
if (languageResult.status === 'fulfilled' && languageResult.value) {
|
||||||
|
results.push(languageResult.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// linter 扩展
|
||||||
|
if (linterResult.status === 'fulfilled' && linterResult.value) {
|
||||||
|
results.push(linterResult.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
setExtensions(results)
|
||||||
|
} catch (error) {
|
||||||
|
if (!cancelled) {
|
||||||
|
console.debug('Failed to load language extensions:', error as Error)
|
||||||
|
setExtensions([])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAllExtensions()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [language, lint, languageConfig])
|
||||||
|
|
||||||
|
return extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseSaveKeymapProps {
|
||||||
|
onSave?: (content: string) => void
|
||||||
|
enabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeMirror 扩展,用于处理保存快捷键 (Cmd/Ctrl + S)
|
||||||
|
* @param onSave 保存时触发的回调函数
|
||||||
|
* @param enabled 是否启用此快捷键
|
||||||
|
* @returns 扩展或空数组
|
||||||
|
*/
|
||||||
|
export function useSaveKeymap({ onSave, enabled = true }: UseSaveKeymapProps) {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!enabled || !onSave) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return keymap.of([
|
||||||
|
{
|
||||||
|
key: 'Mod-s',
|
||||||
|
run: (view: EditorView) => {
|
||||||
|
onSave(view.state.doc.toString())
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
preventDefault: true
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}, [onSave, enabled])
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseBlurHandlerProps {
|
||||||
|
onBlur?: (content: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeMirror 扩展,用于处理编辑器的 blur 事件
|
||||||
|
* @param onBlur blur 事件触发时的回调函数
|
||||||
|
* @returns 扩展或空数组
|
||||||
|
*/
|
||||||
|
export function useBlurHandler({ onBlur }: UseBlurHandlerProps) {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!onBlur) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return EditorView.domEventHandlers({
|
||||||
|
blur: (_event, view) => {
|
||||||
|
onBlur(view.state.doc.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [onBlur])
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseHeightListenerProps {
|
||||||
|
onHeightChange?: (scrollHeight: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CodeMirror 扩展,用于监听编辑器高度变化
|
||||||
|
* @param onHeightChange 高度变化时触发的回调函数
|
||||||
|
* @returns 扩展或空数组
|
||||||
|
*/
|
||||||
|
export function useHeightListener({ onHeightChange }: UseHeightListenerProps) {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!onHeightChange) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return EditorView.updateListener.of((update) => {
|
||||||
|
if (update.docChanged || update.heightChanged) {
|
||||||
|
onHeightChange(update.view.scrollDOM?.scrollHeight ?? 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [onHeightChange])
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { default } from './CodeEditor'
|
||||||
|
export * from './types'
|
||||||
|
export { getCmThemeByName, getCmThemeNames } from './utils'
|
||||||
114
packages/ui/src/components/composites/CodeEditor/types.ts
Normal file
114
packages/ui/src/components/composites/CodeEditor/types.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import type { BasicSetupOptions, Extension } from '@uiw/react-codemirror'
|
||||||
|
|
||||||
|
export type CodeMirrorTheme = 'light' | 'dark' | 'none' | Extension
|
||||||
|
|
||||||
|
/** Language data structure for file extension mapping */
|
||||||
|
export interface LanguageData {
|
||||||
|
type: string
|
||||||
|
aliases?: string[]
|
||||||
|
extensions?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Language configuration mapping language names to their data */
|
||||||
|
export type LanguageConfig = Record<string, LanguageData>
|
||||||
|
|
||||||
|
export interface CodeEditorHandles {
|
||||||
|
save?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodeEditorProps {
|
||||||
|
ref?: React.RefObject<CodeEditorHandles | null>
|
||||||
|
/** Value used in controlled mode, e.g., code blocks. */
|
||||||
|
value: string
|
||||||
|
/** Placeholder when the editor content is empty. */
|
||||||
|
placeholder?: string | HTMLElement
|
||||||
|
/**
|
||||||
|
* Code language string.
|
||||||
|
* - Case-insensitive.
|
||||||
|
* - Supports common names: javascript, json, python, etc.
|
||||||
|
* - Supports aliases: c#/csharp, objective-c++/obj-c++/objc++, etc.
|
||||||
|
* - Supports file extensions: .cpp/cpp, .js/js, .py/py, etc.
|
||||||
|
*/
|
||||||
|
language: string
|
||||||
|
/**
|
||||||
|
* Language configuration for extension mapping.
|
||||||
|
* If not provided, will use a default minimal configuration.
|
||||||
|
* @optional
|
||||||
|
*/
|
||||||
|
languageConfig?: LanguageConfig
|
||||||
|
/** Fired when ref.save() is called or the save shortcut is triggered. */
|
||||||
|
onSave?: (newContent: string) => void
|
||||||
|
/** Fired when the editor content changes. */
|
||||||
|
onChange?: (newContent: string) => void
|
||||||
|
/** Fired when the editor loses focus. */
|
||||||
|
onBlur?: (newContent: string) => void
|
||||||
|
/** Fired when the editor height changes. */
|
||||||
|
onHeightChange?: (scrollHeight: number) => void
|
||||||
|
/**
|
||||||
|
* Fixed editor height, not exceeding maxHeight.
|
||||||
|
* Only works when expanded is false.
|
||||||
|
*/
|
||||||
|
height?: string
|
||||||
|
/**
|
||||||
|
* Maximum editor height.
|
||||||
|
* Only works when expanded is false.
|
||||||
|
*/
|
||||||
|
maxHeight?: string
|
||||||
|
/** Minimum editor height. */
|
||||||
|
minHeight?: string
|
||||||
|
/** Editor options that extend BasicSetupOptions. */
|
||||||
|
options?: {
|
||||||
|
/**
|
||||||
|
* Whether to enable special treatment for stream response.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
stream?: boolean
|
||||||
|
/**
|
||||||
|
* Whether to enable linting.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
lint?: boolean
|
||||||
|
/**
|
||||||
|
* Whether to enable keymap.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
keymap?: boolean
|
||||||
|
} & BasicSetupOptions
|
||||||
|
/** Additional extensions for CodeMirror. */
|
||||||
|
extensions?: Extension[]
|
||||||
|
/**
|
||||||
|
* CodeMirror theme name: 'light', 'dark', 'none', Extension.
|
||||||
|
* @default 'light'
|
||||||
|
*/
|
||||||
|
theme?: CodeMirrorTheme
|
||||||
|
/**
|
||||||
|
* Font size that overrides the app setting.
|
||||||
|
* @default 16
|
||||||
|
*/
|
||||||
|
fontSize?: number
|
||||||
|
/** Style overrides for the editor, passed directly to CodeMirror's style property. */
|
||||||
|
style?: React.CSSProperties
|
||||||
|
/** CSS class name appended to the default `code-editor` class. */
|
||||||
|
className?: string
|
||||||
|
/**
|
||||||
|
* Whether the editor view is editable.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
editable?: boolean
|
||||||
|
/**
|
||||||
|
* Set the editor state to read only but keep some user interactions, e.g., keymaps.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
readOnly?: boolean
|
||||||
|
/**
|
||||||
|
* Whether the editor is expanded.
|
||||||
|
* If true, the height and maxHeight props are ignored.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
expanded?: boolean
|
||||||
|
/**
|
||||||
|
* Whether the code lines are wrapped.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
wrapped?: boolean
|
||||||
|
}
|
||||||
268
packages/ui/src/components/composites/CodeEditor/utils.ts
Normal file
268
packages/ui/src/components/composites/CodeEditor/utils.ts
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
import * as cmThemes from '@uiw/codemirror-themes-all'
|
||||||
|
import type { Extension } from '@uiw/react-codemirror'
|
||||||
|
import diff from 'fast-diff'
|
||||||
|
|
||||||
|
import type { CodeMirrorTheme, LanguageConfig } from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes code changes using fast-diff and converts them to CodeMirror changes.
|
||||||
|
* Could handle all types of changes, though insertions are most common during streaming responses.
|
||||||
|
* @param oldCode The old code content
|
||||||
|
* @param newCode The new code content
|
||||||
|
* @returns An array of changes for EditorView.dispatch
|
||||||
|
*/
|
||||||
|
export function prepareCodeChanges(oldCode: string, newCode: string) {
|
||||||
|
const diffResult = diff(oldCode, newCode)
|
||||||
|
|
||||||
|
const changes: { from: number; to: number; insert: string }[] = []
|
||||||
|
let offset = 0
|
||||||
|
|
||||||
|
// operation: 1=insert, -1=delete, 0=equal
|
||||||
|
for (const [operation, text] of diffResult) {
|
||||||
|
if (operation === 1) {
|
||||||
|
changes.push({
|
||||||
|
from: offset,
|
||||||
|
to: offset,
|
||||||
|
insert: text
|
||||||
|
})
|
||||||
|
} else if (operation === -1) {
|
||||||
|
changes.push({
|
||||||
|
from: offset,
|
||||||
|
to: offset + text.length,
|
||||||
|
insert: ''
|
||||||
|
})
|
||||||
|
offset += text.length
|
||||||
|
} else {
|
||||||
|
offset += text.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom language file extension mapping
|
||||||
|
// key: language name in lowercase
|
||||||
|
// value: file extension
|
||||||
|
const _customLanguageExtensions: Record<string, string> = {
|
||||||
|
svg: 'xml',
|
||||||
|
vab: 'vb',
|
||||||
|
graphviz: 'dot'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default minimal language configuration for common languages
|
||||||
|
const _defaultLanguageConfig: LanguageConfig = {
|
||||||
|
JavaScript: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.js', '.mjs', '.cjs'],
|
||||||
|
aliases: ['js', 'node']
|
||||||
|
},
|
||||||
|
TypeScript: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.ts'],
|
||||||
|
aliases: ['ts']
|
||||||
|
},
|
||||||
|
Python: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.py'],
|
||||||
|
aliases: ['python3', 'py']
|
||||||
|
},
|
||||||
|
Java: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.java']
|
||||||
|
},
|
||||||
|
'C++': {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.cpp', '.cc', '.cxx'],
|
||||||
|
aliases: ['cpp']
|
||||||
|
},
|
||||||
|
C: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.c']
|
||||||
|
},
|
||||||
|
'C#': {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.cs'],
|
||||||
|
aliases: ['csharp']
|
||||||
|
},
|
||||||
|
HTML: {
|
||||||
|
type: 'markup',
|
||||||
|
extensions: ['.html', '.htm']
|
||||||
|
},
|
||||||
|
CSS: {
|
||||||
|
type: 'markup',
|
||||||
|
extensions: ['.css']
|
||||||
|
},
|
||||||
|
JSON: {
|
||||||
|
type: 'data',
|
||||||
|
extensions: ['.json']
|
||||||
|
},
|
||||||
|
XML: {
|
||||||
|
type: 'data',
|
||||||
|
extensions: ['.xml']
|
||||||
|
},
|
||||||
|
YAML: {
|
||||||
|
type: 'data',
|
||||||
|
extensions: ['.yml', '.yaml']
|
||||||
|
},
|
||||||
|
SQL: {
|
||||||
|
type: 'data',
|
||||||
|
extensions: ['.sql']
|
||||||
|
},
|
||||||
|
Shell: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.sh', '.bash'],
|
||||||
|
aliases: ['bash', 'sh']
|
||||||
|
},
|
||||||
|
Go: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.go'],
|
||||||
|
aliases: ['golang']
|
||||||
|
},
|
||||||
|
Rust: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.rs']
|
||||||
|
},
|
||||||
|
PHP: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.php']
|
||||||
|
},
|
||||||
|
Ruby: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.rb'],
|
||||||
|
aliases: ['rb']
|
||||||
|
},
|
||||||
|
Swift: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.swift']
|
||||||
|
},
|
||||||
|
Kotlin: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.kt']
|
||||||
|
},
|
||||||
|
Dart: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.dart']
|
||||||
|
},
|
||||||
|
R: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.r']
|
||||||
|
},
|
||||||
|
MATLAB: {
|
||||||
|
type: 'programming',
|
||||||
|
extensions: ['.m']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file extension of the language, by language name
|
||||||
|
* - First, exact match
|
||||||
|
* - Then, case-insensitive match
|
||||||
|
* - Finally, match aliases
|
||||||
|
* If there are multiple file extensions, only the first one will be returned
|
||||||
|
* @param language language name
|
||||||
|
* @param languageConfig optional language configuration, defaults to a minimal config
|
||||||
|
* @returns file extension
|
||||||
|
*/
|
||||||
|
export function getExtensionByLanguage(language: string, languageConfig?: LanguageConfig): string {
|
||||||
|
const languages = languageConfig || _defaultLanguageConfig
|
||||||
|
const lowerLanguage = language.toLowerCase()
|
||||||
|
|
||||||
|
// Exact match language name
|
||||||
|
const directMatch = languages[language]
|
||||||
|
if (directMatch?.extensions?.[0]) {
|
||||||
|
return directMatch.extensions[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case-insensitive match language name
|
||||||
|
for (const [langName, data] of Object.entries(languages)) {
|
||||||
|
if (langName.toLowerCase() === lowerLanguage && data.extensions?.[0]) {
|
||||||
|
return data.extensions[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match aliases
|
||||||
|
for (const [, data] of Object.entries(languages)) {
|
||||||
|
if (data.aliases?.some((alias) => alias.toLowerCase() === lowerLanguage)) {
|
||||||
|
return data.extensions?.[0] || `.${language}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to language name
|
||||||
|
return `.${language}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file extension of the language, for @uiw/codemirror-extensions-langs
|
||||||
|
* - First, search for custom extensions
|
||||||
|
* - Then, search for language configuration extensions
|
||||||
|
* - Finally, assume the name is already an extension
|
||||||
|
* @param language language name
|
||||||
|
* @param languageConfig optional language configuration
|
||||||
|
* @returns file extension (without `.` prefix)
|
||||||
|
*/
|
||||||
|
export async function getNormalizedExtension(language: string, languageConfig?: LanguageConfig) {
|
||||||
|
let lang = language
|
||||||
|
|
||||||
|
// If the language name looks like an extension, remove the dot
|
||||||
|
if (language.startsWith('.') && language.length > 1) {
|
||||||
|
lang = language.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerLanguage = lang.toLowerCase()
|
||||||
|
|
||||||
|
// 1. Search for custom extensions
|
||||||
|
const customExt = _customLanguageExtensions[lowerLanguage]
|
||||||
|
if (customExt) {
|
||||||
|
return customExt
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Search for language configuration extensions
|
||||||
|
const linguistExt = getExtensionByLanguage(lang, languageConfig)
|
||||||
|
if (linguistExt) {
|
||||||
|
return linguistExt.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to language name
|
||||||
|
return lang
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of CodeMirror theme names
|
||||||
|
* - Include auto, light, dark
|
||||||
|
* - Include all themes in @uiw/codemirror-themes-all
|
||||||
|
*
|
||||||
|
* A more robust approach might be to hardcode the theme list
|
||||||
|
* @returns theme name list
|
||||||
|
*/
|
||||||
|
export function getCmThemeNames(): string[] {
|
||||||
|
return ['auto', 'light', 'dark']
|
||||||
|
.concat(Object.keys(cmThemes))
|
||||||
|
.filter((item) => typeof (cmThemes as any)[item] !== 'function')
|
||||||
|
.filter((item) => !/^(defaultSettings)/.test(item as string) && !/(Style)$/.test(item as string))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the CodeMirror theme object by theme name
|
||||||
|
* @param name theme name
|
||||||
|
* @returns theme object
|
||||||
|
*/
|
||||||
|
export function getCmThemeByName(name: string): CodeMirrorTheme {
|
||||||
|
// 1. Search for the extension of the corresponding theme in @uiw/codemirror-themes-all
|
||||||
|
const candidate = (cmThemes as Record<string, unknown>)[name]
|
||||||
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(cmThemes, name) &&
|
||||||
|
typeof candidate !== 'function' &&
|
||||||
|
!/^defaultSettings/i.test(name) &&
|
||||||
|
!/(Style)$/.test(name)
|
||||||
|
) {
|
||||||
|
return candidate as Extension
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Basic string theme
|
||||||
|
if (name === 'light' || name === 'dark' || name === 'none') {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If not found, fallback to light
|
||||||
|
return 'light'
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// Original path: src/renderer/src/components/CollapsibleSearchBar.tsx
|
||||||
|
import type { InputRef } from 'antd'
|
||||||
|
import { Input } from 'antd'
|
||||||
|
import { Search } from 'lucide-react'
|
||||||
|
import { motion } from 'motion/react'
|
||||||
|
import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import { Tooltip } from '../../primitives/tooltip'
|
||||||
|
|
||||||
|
interface CollapsibleSearchBarProps {
|
||||||
|
onSearch: (text: string) => void
|
||||||
|
placeholder?: string
|
||||||
|
tooltip?: string
|
||||||
|
icon?: React.ReactNode
|
||||||
|
maxWidth?: string | number
|
||||||
|
style?: React.CSSProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collapsible search bar for list headers
|
||||||
|
* Renders as an icon initially, expands to full search input when clicked
|
||||||
|
*/
|
||||||
|
const CollapsibleSearchBar = ({
|
||||||
|
onSearch,
|
||||||
|
placeholder = 'Search',
|
||||||
|
tooltip = 'Search',
|
||||||
|
icon = <Search size={14} color="var(--color-icon)" />,
|
||||||
|
maxWidth = '100%',
|
||||||
|
style
|
||||||
|
}: CollapsibleSearchBarProps) => {
|
||||||
|
const [searchVisible, setSearchVisible] = useState(false)
|
||||||
|
const [searchText, setSearchText] = useState('')
|
||||||
|
const inputRef = useRef<InputRef>(null)
|
||||||
|
|
||||||
|
const handleTextChange = useCallback(
|
||||||
|
(text: string) => {
|
||||||
|
setSearchText(text)
|
||||||
|
onSearch(text)
|
||||||
|
},
|
||||||
|
[onSearch]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleClear = useCallback(() => {
|
||||||
|
setSearchText('')
|
||||||
|
setSearchVisible(false)
|
||||||
|
onSearch('')
|
||||||
|
}, [onSearch])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchVisible && inputRef.current) {
|
||||||
|
inputRef.current.focus()
|
||||||
|
}
|
||||||
|
}, [searchVisible])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', position: 'relative' }}>
|
||||||
|
<motion.div
|
||||||
|
initial="collapsed"
|
||||||
|
animate={searchVisible ? 'expanded' : 'collapsed'}
|
||||||
|
variants={{
|
||||||
|
expanded: { maxWidth: maxWidth, opacity: 1, transition: { duration: 0.3, ease: 'easeInOut' } },
|
||||||
|
collapsed: { maxWidth: 0, opacity: 0, transition: { duration: 0.3, ease: 'easeInOut' } }
|
||||||
|
}}
|
||||||
|
style={{ overflow: 'hidden', flex: 1 }}>
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
placeholder={placeholder}
|
||||||
|
size="small"
|
||||||
|
suffix={icon}
|
||||||
|
value={searchText}
|
||||||
|
autoFocus
|
||||||
|
allowClear
|
||||||
|
onChange={(e) => handleTextChange(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleTextChange('')
|
||||||
|
if (!searchText) setSearchVisible(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
if (!searchText) setSearchVisible(false)
|
||||||
|
}}
|
||||||
|
onClear={handleClear}
|
||||||
|
style={{ width: '100%', ...style }}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
<motion.div
|
||||||
|
initial="visible"
|
||||||
|
animate={searchVisible ? 'hidden' : 'visible'}
|
||||||
|
variants={{
|
||||||
|
visible: { opacity: 1, transition: { duration: 0.1, delay: 0.3, ease: 'easeInOut' } },
|
||||||
|
hidden: { opacity: 0, transition: { duration: 0.1, ease: 'easeInOut' } }
|
||||||
|
}}
|
||||||
|
style={{ cursor: 'pointer', display: 'flex' }}
|
||||||
|
onClick={() => setSearchVisible(true)}>
|
||||||
|
<Tooltip content={tooltip} delay={500} closeDelay={0}>
|
||||||
|
{icon}
|
||||||
|
</Tooltip>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(CollapsibleSearchBar)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Original path: src/renderer/src/components/DraggableList/index.tsx
|
||||||
|
export { default as DraggableList } from './list'
|
||||||
|
export { useDraggableReorder } from './useDraggableReorder'
|
||||||
|
export {
|
||||||
|
default as DraggableVirtualList,
|
||||||
|
type DraggableVirtualListProps,
|
||||||
|
type DraggableVirtualListRef
|
||||||
|
} from './virtual-list'
|
||||||
109
packages/ui/src/components/composites/DraggableList/list.tsx
Normal file
109
packages/ui/src/components/composites/DraggableList/list.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// Original path: src/renderer/src/components/DraggableList/list.tsx
|
||||||
|
import type {
|
||||||
|
DroppableProps,
|
||||||
|
DropResult,
|
||||||
|
OnDragEndResponder,
|
||||||
|
OnDragStartResponder,
|
||||||
|
ResponderProvided
|
||||||
|
} from '@hello-pangea/dnd'
|
||||||
|
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
|
||||||
|
import type { HTMLAttributes, Key } from 'react'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
// Inline utility function from @renderer/utils
|
||||||
|
function droppableReorder<T>(list: T[], sourceIndex: number, destIndex: number, len: number = 1): T[] {
|
||||||
|
const result = Array.from(list)
|
||||||
|
const removed = result.splice(sourceIndex, len)
|
||||||
|
|
||||||
|
if (sourceIndex < destIndex) {
|
||||||
|
result.splice(destIndex - len + 1, 0, ...removed)
|
||||||
|
} else {
|
||||||
|
result.splice(destIndex, 0, ...removed)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props<T> {
|
||||||
|
list: T[]
|
||||||
|
style?: React.CSSProperties
|
||||||
|
listStyle?: React.CSSProperties
|
||||||
|
listProps?: HTMLAttributes<HTMLDivElement>
|
||||||
|
children: (item: T, index: number) => React.ReactNode
|
||||||
|
itemKey?: keyof T | ((item: T) => Key)
|
||||||
|
onUpdate: (list: T[]) => void
|
||||||
|
onDragStart?: OnDragStartResponder
|
||||||
|
onDragEnd?: OnDragEndResponder
|
||||||
|
droppableProps?: Partial<DroppableProps>
|
||||||
|
}
|
||||||
|
|
||||||
|
function DraggableList<T>({
|
||||||
|
children,
|
||||||
|
list,
|
||||||
|
style,
|
||||||
|
listStyle,
|
||||||
|
listProps,
|
||||||
|
itemKey,
|
||||||
|
droppableProps,
|
||||||
|
onDragStart,
|
||||||
|
onUpdate,
|
||||||
|
onDragEnd
|
||||||
|
}: Props<T>) {
|
||||||
|
const _onDragEnd = (result: DropResult, provided: ResponderProvided) => {
|
||||||
|
onDragEnd?.(result, provided)
|
||||||
|
if (result.destination) {
|
||||||
|
const sourceIndex = result.source.index
|
||||||
|
const destIndex = result.destination.index
|
||||||
|
if (sourceIndex !== destIndex) {
|
||||||
|
const reorderAgents = droppableReorder(list, sourceIndex, destIndex)
|
||||||
|
onUpdate(reorderAgents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getId = useCallback(
|
||||||
|
(item: T) => {
|
||||||
|
if (typeof itemKey === 'function') return itemKey(item)
|
||||||
|
if (itemKey) return item[itemKey] as Key
|
||||||
|
if (typeof item === 'string') return item as Key
|
||||||
|
if (item && typeof item === 'object' && 'id' in item) return item.id as Key
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
[itemKey]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragDropContext onDragStart={onDragStart} onDragEnd={_onDragEnd}>
|
||||||
|
<Droppable droppableId="droppable" {...droppableProps}>
|
||||||
|
{(provided) => (
|
||||||
|
<div {...provided.droppableProps} ref={provided.innerRef} style={style}>
|
||||||
|
<div {...listProps} className="draggable-list-container">
|
||||||
|
{list.map((item, index) => {
|
||||||
|
const draggableId = String(getId(item) ?? index)
|
||||||
|
return (
|
||||||
|
<Draggable key={`draggable_${draggableId}`} draggableId={draggableId} index={index}>
|
||||||
|
{(provided) => (
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
style={{
|
||||||
|
...listStyle,
|
||||||
|
...provided.draggableProps.style,
|
||||||
|
marginBottom: 8
|
||||||
|
}}>
|
||||||
|
{children(item, index)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DraggableList
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user