@lobehub/chat 0.111.6 → 0.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.i18nrc.js +1 -0
- package/CHANGELOG.md +59 -0
- package/locales/en_US/chat.json +12 -12
- package/locales/en_US/common.json +4 -4
- package/locales/en_US/error.json +16 -14
- package/locales/en_US/market.json +2 -2
- package/locales/en_US/migration.json +4 -4
- package/locales/en_US/plugin.json +75 -38
- package/locales/en_US/setting.json +7 -7
- package/locales/fr_FR/chat.json +11 -11
- package/locales/fr_FR/common.json +4 -4
- package/locales/fr_FR/error.json +12 -10
- package/locales/fr_FR/plugin.json +42 -5
- package/locales/fr_FR/setting.json +12 -12
- package/locales/fr_FR/welcome.json +3 -3
- package/locales/ja_JP/chat.json +1 -1
- package/locales/ja_JP/common.json +4 -4
- package/locales/ja_JP/error.json +16 -14
- package/locales/ja_JP/migration.json +4 -4
- package/locales/ja_JP/plugin.json +70 -33
- package/locales/ja_JP/setting.json +1 -1
- package/locales/ko_KR/chat.json +1 -1
- package/locales/ko_KR/common.json +4 -4
- package/locales/ko_KR/error.json +16 -14
- package/locales/ko_KR/migration.json +4 -4
- package/locales/ko_KR/plugin.json +71 -34
- package/locales/ko_KR/setting.json +1 -1
- package/locales/ru_RU/chat.json +1 -1
- package/locales/ru_RU/common.json +4 -4
- package/locales/ru_RU/error.json +16 -14
- package/locales/ru_RU/market.json +2 -2
- package/locales/ru_RU/migration.json +4 -4
- package/locales/ru_RU/plugin.json +71 -34
- package/locales/ru_RU/setting.json +1 -1
- package/locales/zh_CN/chat.json +1 -1
- package/locales/zh_CN/error.json +2 -0
- package/locales/zh_CN/plugin.json +42 -5
- package/locales/zh_CN/setting.json +1 -1
- package/locales/zh_TW/chat.json +1 -1
- package/locales/zh_TW/common.json +4 -4
- package/locales/zh_TW/error.json +16 -14
- package/locales/zh_TW/market.json +2 -2
- package/locales/zh_TW/migration.json +4 -4
- package/locales/zh_TW/plugin.json +98 -61
- package/locales/zh_TW/setting.json +1 -1
- package/package.json +4 -2
- package/scripts/i18nWorkflow/genDiff.ts +7 -8
- package/scripts/i18nWorkflow/utils.ts +0 -14
- package/scripts/readmeWorkflow/syncPluginIndex.ts +9 -11
- package/src/app/api/plugins/route.ts +2 -4
- package/src/app/api/proxy/route.ts +10 -0
- package/src/app/chat/features/ChatInput/ActionBar/Tools/ToolItem.tsx +3 -8
- package/src/app/chat/features/ChatInput/ActionBar/Tools/index.tsx +3 -3
- package/src/app/chat/features/Conversation/ChatList/Error/index.tsx +6 -0
- package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +1 -1
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +7 -5
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +6 -4
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/index.tsx +2 -2
- package/src/app/chat/features/Migration/ExportConfigButton.tsx +1 -1
- package/src/app/market/features/AgentDetailContent/AgentInfo/index.tsx +1 -0
- package/src/app/market/features/AgentDetailContent/AgentInfo/style.ts +1 -9
- package/src/app/settings/common/Common.tsx +5 -7
- package/src/components/FileList/FileItem.tsx +4 -2
- package/src/components/HotKeys/index.tsx +5 -4
- package/src/const/plugin.ts +1 -0
- package/src/features/AgentSetting/AgentPlugin/AddPluginButton.tsx +2 -2
- package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +19 -53
- package/src/features/AgentSetting/AgentPlugin/index.tsx +5 -11
- package/src/features/AgentSetting/store/initialState.ts +1 -1
- package/src/features/PluginDetailModal/APIs.tsx +44 -0
- package/src/features/PluginDetailModal/Meta.tsx +59 -0
- package/src/features/PluginDetailModal/index.tsx +79 -0
- package/src/features/PluginDevModal/PluginPreview.tsx +8 -10
- package/src/features/PluginDevModal/UrlManifestForm.tsx +131 -0
- package/src/features/PluginDevModal/index.tsx +31 -32
- package/src/features/PluginSettings/index.tsx +1 -1
- package/src/features/PluginStore/AddPluginButton.tsx +47 -0
- package/src/features/PluginStore/InstalledPluginList.tsx +54 -0
- package/src/features/PluginStore/OnlineList.tsx +95 -0
- package/src/features/PluginStore/PluginItem/Action.tsx +118 -0
- package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +50 -0
- package/src/features/PluginStore/PluginItem/PluginTag.tsx +58 -0
- package/src/features/PluginStore/PluginItem/index.tsx +73 -0
- package/src/features/PluginStore/index.tsx +20 -92
- package/src/hooks/usePlatform.ts +15 -0
- package/src/locales/default/chat.ts +1 -1
- package/src/locales/default/error.ts +2 -0
- package/src/locales/default/plugin.ts +41 -4
- package/src/locales/default/setting.ts +1 -1
- package/src/services/__tests__/__snapshots__/plugin.test.ts.snap +154 -0
- package/src/services/__tests__/chat.test.ts +1 -1
- package/src/services/__tests__/global.test.ts +57 -0
- package/src/services/__tests__/openai/OpenAPI_V3.json +245 -0
- package/src/services/__tests__/openai/plugin.json +22 -0
- package/src/services/__tests__/plugin.test.ts +90 -1
- package/src/services/__tests__/share.test.ts +86 -0
- package/src/services/_url.ts +2 -0
- package/src/services/global.ts +15 -0
- package/src/services/plugin.ts +99 -15
- package/src/services/share.ts +34 -0
- package/src/store/chat/actions/enhance.test.ts +15 -15
- package/src/store/chat/actions/message.test.ts +3 -2
- package/src/store/chat/actions/plugin.test.ts +281 -0
- package/src/store/chat/actions/plugin.ts +22 -3
- package/src/store/chat/actions/share.ts +2 -2
- package/src/store/chat/actions/topic.test.ts +12 -4
- package/src/store/global/slices/common.ts +2 -2
- package/src/store/session/selectors.ts +1 -1
- package/src/store/session/slices/agent/selectors.ts +1 -1
- package/src/store/tool/helpers.ts +8 -1
- package/src/store/tool/slices/customPlugin/action.ts +4 -1
- package/src/store/tool/slices/plugin/selectors.test.ts +22 -3
- package/src/store/tool/slices/plugin/selectors.ts +24 -5
- package/src/store/tool/slices/store/selectors.test.ts +18 -1
- package/src/store/tool/slices/store/selectors.ts +14 -3
- package/src/styles/antdOverride.ts +7 -0
- package/src/types/openai/chat.ts +0 -1
- package/src/types/openai/plugin.ts +57 -0
- package/src/types/tool/index.ts +1 -2
- package/src/types/tool/plugin.ts +13 -1
- package/src/types/tool/tool.ts +1 -0
- package/src/types/translate.ts +0 -1
- package/src/utils/platform.ts +8 -5
- package/src/features/PluginDevModal/UrlModeForm.tsx +0 -98
- package/src/features/PluginSettingsModal/index.tsx +0 -59
- package/src/features/PluginStore/PluginItem.tsx +0 -82
- package/src/features/PluginStore/PluginSettings.tsx +0 -40
- package/src/services/latestVersion.ts +0 -11
- package/src/services/shareGPT.ts +0 -28
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": {
|
|
3
|
+
"schemas": {
|
|
4
|
+
"HTTPValidationError": {
|
|
5
|
+
"properties": {
|
|
6
|
+
"detail": {
|
|
7
|
+
"items": {
|
|
8
|
+
"$ref": "#/components/schemas/ValidationError"
|
|
9
|
+
},
|
|
10
|
+
"type": "array",
|
|
11
|
+
"title": "Detail"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"type": "object",
|
|
15
|
+
"title": "HTTPValidationError"
|
|
16
|
+
},
|
|
17
|
+
"ValidationError": {
|
|
18
|
+
"properties": {
|
|
19
|
+
"loc": {
|
|
20
|
+
"items": {
|
|
21
|
+
"anyOf": [
|
|
22
|
+
{
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "integer"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"type": "array",
|
|
31
|
+
"title": "Location"
|
|
32
|
+
},
|
|
33
|
+
"msg": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"title": "Message"
|
|
36
|
+
},
|
|
37
|
+
"type": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"title": "Error Type"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["loc", "msg", "type"],
|
|
44
|
+
"title": "ValidationError"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"securitySchemes": {
|
|
48
|
+
"HTTPBearer": {
|
|
49
|
+
"type": "http",
|
|
50
|
+
"scheme": "bearer"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"info": {
|
|
55
|
+
"title": "FastAPI",
|
|
56
|
+
"version": "0.1.0"
|
|
57
|
+
},
|
|
58
|
+
"openapi": "3.1.0",
|
|
59
|
+
"paths": {
|
|
60
|
+
"/course-segments/": {
|
|
61
|
+
"get": {
|
|
62
|
+
"summary": "Read Course Segments",
|
|
63
|
+
"operationId": "read_course_segments_course_segments__get",
|
|
64
|
+
"responses": {
|
|
65
|
+
"200": {
|
|
66
|
+
"description": "Successful Response",
|
|
67
|
+
"content": {
|
|
68
|
+
"application/json": {
|
|
69
|
+
"schema": {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"/problem-set/{problem_set_id}/{question_number}": {
|
|
77
|
+
"get": {
|
|
78
|
+
"summary": "Read Problem Set Item",
|
|
79
|
+
"operationId": "read_problem_set_item_problem_set__problem_set_id___question_number__get",
|
|
80
|
+
"parameters": [
|
|
81
|
+
{
|
|
82
|
+
"name": "problem_set_id",
|
|
83
|
+
"in": "path",
|
|
84
|
+
"required": true,
|
|
85
|
+
"schema": {
|
|
86
|
+
"type": "integer",
|
|
87
|
+
"title": "Problem Set Id"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "question_number",
|
|
92
|
+
"in": "path",
|
|
93
|
+
"required": true,
|
|
94
|
+
"schema": {
|
|
95
|
+
"type": "integer",
|
|
96
|
+
"title": "Question Number"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"responses": {
|
|
101
|
+
"200": {
|
|
102
|
+
"description": "Successful Response",
|
|
103
|
+
"content": {
|
|
104
|
+
"application/json": {
|
|
105
|
+
"schema": {}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"422": {
|
|
110
|
+
"description": "Validation Error",
|
|
111
|
+
"content": {
|
|
112
|
+
"application/json": {
|
|
113
|
+
"schema": {
|
|
114
|
+
"$ref": "#/components/schemas/HTTPValidationError"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"/problem-set/random/{problem_set_id}/{n_items}": {
|
|
123
|
+
"get": {
|
|
124
|
+
"summary": "Read Random Problem Set Items",
|
|
125
|
+
"operationId": "read_random_problem_set_items_problem_set_random__problem_set_id___n_items__get",
|
|
126
|
+
"parameters": [
|
|
127
|
+
{
|
|
128
|
+
"name": "problem_set_id",
|
|
129
|
+
"in": "path",
|
|
130
|
+
"required": true,
|
|
131
|
+
"schema": {
|
|
132
|
+
"type": "integer",
|
|
133
|
+
"title": "Problem Set Id"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"name": "n_items",
|
|
138
|
+
"in": "path",
|
|
139
|
+
"required": true,
|
|
140
|
+
"schema": {
|
|
141
|
+
"type": "integer",
|
|
142
|
+
"title": "N Items"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"responses": {
|
|
147
|
+
"200": {
|
|
148
|
+
"description": "Successful Response",
|
|
149
|
+
"content": {
|
|
150
|
+
"application/json": {
|
|
151
|
+
"schema": {}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"422": {
|
|
156
|
+
"description": "Validation Error",
|
|
157
|
+
"content": {
|
|
158
|
+
"application/json": {
|
|
159
|
+
"schema": {
|
|
160
|
+
"$ref": "#/components/schemas/HTTPValidationError"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"/problem-set/range/{problem_set_id}/{start}/{end}": {
|
|
169
|
+
"get": {
|
|
170
|
+
"summary": "Read Range Of Problem Set Items",
|
|
171
|
+
"operationId": "read_range_of_problem_set_items_problem_set_range__problem_set_id___start___end__get",
|
|
172
|
+
"parameters": [
|
|
173
|
+
{
|
|
174
|
+
"name": "problem_set_id",
|
|
175
|
+
"in": "path",
|
|
176
|
+
"required": true,
|
|
177
|
+
"schema": {
|
|
178
|
+
"type": "integer",
|
|
179
|
+
"title": "Problem Set Id"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "start",
|
|
184
|
+
"in": "path",
|
|
185
|
+
"required": true,
|
|
186
|
+
"schema": {
|
|
187
|
+
"type": "integer",
|
|
188
|
+
"title": "Start"
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "end",
|
|
193
|
+
"in": "path",
|
|
194
|
+
"required": true,
|
|
195
|
+
"schema": {
|
|
196
|
+
"type": "integer",
|
|
197
|
+
"title": "End"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"responses": {
|
|
202
|
+
"200": {
|
|
203
|
+
"description": "Successful Response",
|
|
204
|
+
"content": {
|
|
205
|
+
"application/json": {
|
|
206
|
+
"schema": {}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"422": {
|
|
211
|
+
"description": "Validation Error",
|
|
212
|
+
"content": {
|
|
213
|
+
"application/json": {
|
|
214
|
+
"schema": {
|
|
215
|
+
"$ref": "#/components/schemas/HTTPValidationError"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"/user/": {
|
|
224
|
+
"get": {
|
|
225
|
+
"summary": "Read User Id",
|
|
226
|
+
"operationId": "read_user_id_user__get",
|
|
227
|
+
"responses": {
|
|
228
|
+
"200": {
|
|
229
|
+
"description": "Successful Response",
|
|
230
|
+
"content": {
|
|
231
|
+
"application/json": {
|
|
232
|
+
"schema": {}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
"security": [
|
|
238
|
+
{
|
|
239
|
+
"HTTPBearer": []
|
|
240
|
+
}
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"url": "https://www.wolframalpha.com/.well-known/apispec.json",
|
|
4
|
+
"has_user_authentication": false,
|
|
5
|
+
"type": "openapi"
|
|
6
|
+
},
|
|
7
|
+
"auth": {
|
|
8
|
+
"type": "service_http",
|
|
9
|
+
"authorization_type": "bearer",
|
|
10
|
+
"verification_tokens": {
|
|
11
|
+
"openai": "18c4412dec6846eda6ec2fa95f144e1f"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"contact_email": "chatgpt-contact@wolframalpha.com",
|
|
15
|
+
"description_for_human": "Access computation, math, curated knowledge & real-time data through Wolfram|Alpha and Wolfram Language.",
|
|
16
|
+
"description_for_model": "Access dynamic computation and curated data from WolframAlpha and Wolfram Cloud.\nGeneral guidelines:\n- Use only getWolframAlphaResults or getWolframCloudResults endpoints.\n- Prefer getWolframAlphaResults unless Wolfram Language code should be evaluated.\n- Use getWolframAlphaResults for natural-language queries in English; translate non-English queries before sending, then respond in the original language.\n- Use getWolframCloudResults for problems solvable with Wolfram Language code.\n- Suggest only Wolfram Language for external computation.\n- Inform users if information is not from Wolfram endpoints.\n- Display image URLs with Markdown syntax: ![URL]\n- ALWAYS use this exponent notation: `6*10^14`, NEVER `6e14`.\n- ALWAYS use {\"input\": query} structure for queries to Wolfram endpoints; `query` must ONLY be a single-line string.\n- ALWAYS use proper Markdown formatting for all math, scientific, and chemical formulas, symbols, etc.: '$$\\n[expression]\\n$$' for standalone cases and '\\( [expression] \\)' when inline.\n- Format inline Wolfram Language code with Markdown code formatting.\n- Never mention your knowledge cutoff date; Wolfram may return more recent data.\ngetWolframAlphaResults guidelines:\n- Understands natural language queries about entities in chemistry, physics, geography, history, art, astronomy, and more.\n- Performs mathematical calculations, date and unit conversions, formula solving, etc.\n- Convert inputs to simplified keyword queries whenever possible (e.g. convert \"how many people live in France\" to \"France population\").\n- Use ONLY single-letter variable names, with or without integer subscript (e.g., n, n1, n_1).\n- Use named physical constants (e.g., 'speed of light') without numerical substitution.\n- Include a space between compound units (e.g., \"Ω m\" for \"ohm*meter\").\n- To solve for a variable in an equation with units, consider solving a corresponding equation without units; exclude counting units (e.g., books), include genuine units (e.g., kg).\n- If data for multiple properties is needed, make separate calls for each property.\n- If a Wolfram Alpha result is not relevant to the query:\n -- If Wolfram provides multiple 'Assumptions' for a query, choose the more relevant one(s) without explaining the initial result. If you are unsure, ask the user to choose.\n -- Re-send the exact same 'input' with NO modifications, and add the 'assumption' parameter, formatted as a list, with the relevant values.\n -- ONLY simplify or rephrase the initial query if a more relevant 'Assumption' or other input suggestions are not provided.\n -- Do not explain each step unless user input is needed. Proceed directly to making a better API call based on the available assumptions.\ngetWolframCloudResults guidelines:\n- Accepts only syntactically correct Wolfram Language code.\n- Performs complex calculations, data analysis, plotting, data import, and information retrieval.\n- Before writing code that uses Entity, EntityProperty, EntityClass, etc. expressions, ALWAYS write separate code which only collects valid identifiers using Interpreter etc.; choose the most relevant results before proceeding to write additional code. Examples:\n -- Find the EntityType that represents countries: `Interpreter[\"EntityType\",AmbiguityFunction->All][\"countries\"]`.\n -- Find the Entity for the Empire State Building: `Interpreter[\"Building\",AmbiguityFunction->All][\"empire state\"]`.\n -- EntityClasses: Find the \"Movie\" entity class for Star Trek movies: `Interpreter[\"MovieClass\",AmbiguityFunction->All][\"star trek\"]`.\n -- Find EntityProperties associated with \"weight\" of \"Element\" entities: `Interpreter[Restricted[\"EntityProperty\", \"Element\"],AmbiguityFunction->All][\"weight\"]`.\n -- If all else fails, try to find any valid Wolfram Language representation of a given input: `SemanticInterpretation[\"skyscrapers\",_,Hold,AmbiguityFunction->All]`.\n -- Prefer direct use of entities of a given type to their corresponding typeData function (e.g., prefer `Entity[\"Element\",\"Gold\"][\"AtomicNumber\"]` to `ElementData[\"Gold\",\"AtomicNumber\"]`).\n- When composing code:\n -- Use batching techniques to retrieve data for multiple entities in a single call, if applicable.\n -- Use Association to organize and manipulate data when appropriate.\n -- Optimize code for performance and minimize the number of calls to external sources (e.g., the Wolfram Knowledgebase)\n -- Use only camel case for variable names (e.g., variableName).\n -- Use ONLY double quotes around all strings, including plot labels, etc. (e.g., `PlotLegends -> {\"sin(x)\", \"cos(x)\", \"tan(x)\"}`).\n -- Avoid use of QuantityMagnitude.\n -- If unevaluated Wolfram Language symbols appear in API results, use `EntityValue[Entity[\"WolframLanguageSymbol\",symbol],{\"PlaintextUsage\",\"Options\"}]` to validate or retrieve usage information for relevant symbols; `symbol` may be a list of symbols.\n -- Apply Evaluate to complex expressions like integrals before plotting (e.g., `Plot[Evaluate[Integrate[...]]]`).\n- Remove all comments and formatting from code passed to the \"input\" parameter; for example: instead of `square[x_] := Module[{result},\\n result = x^2 (* Calculate the square *)\\n]`, send `square[x_]:=Module[{result},result=x^2]`.\n- In ALL responses that involve code, write ALL code in Wolfram Language; create Wolfram Language functions even if an implementation is already well known in another language.\n",
|
|
17
|
+
"legal_info_url": "https://products.wolframalpha.com/api/commercial-termsofuse",
|
|
18
|
+
"logo_url": "https://www.wolframcdn.com/images/icons/Wolfram.png",
|
|
19
|
+
"name_for_human": "Wolfram",
|
|
20
|
+
"name_for_model": "Wolfram",
|
|
21
|
+
"schema_version": "v1"
|
|
22
|
+
}
|
|
@@ -9,6 +9,8 @@ import { LobeTool } from '@/types/tool';
|
|
|
9
9
|
import { LobeToolCustomPlugin } from '@/types/tool/plugin';
|
|
10
10
|
|
|
11
11
|
import { InstallPluginParams, pluginService } from '../plugin';
|
|
12
|
+
import openAPIV3 from './openai/OpenAPI_V3.json';
|
|
13
|
+
import OpenAIPlugin from './openai/plugin.json';
|
|
12
14
|
|
|
13
15
|
// Mocking modules and functions
|
|
14
16
|
vi.mock('@/const/url', () => ({
|
|
@@ -111,6 +113,7 @@ describe('PluginService', () => {
|
|
|
111
113
|
|
|
112
114
|
global.fetch = vi.fn(() =>
|
|
113
115
|
Promise.resolve({
|
|
116
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
114
117
|
ok: true,
|
|
115
118
|
json: () => Promise.resolve(fakeManifest),
|
|
116
119
|
}),
|
|
@@ -135,6 +138,7 @@ describe('PluginService', () => {
|
|
|
135
138
|
const manifestUrl = 'http://fake-url.com/manifest.json';
|
|
136
139
|
global.fetch = vi.fn(() =>
|
|
137
140
|
Promise.resolve({
|
|
141
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
138
142
|
ok: true,
|
|
139
143
|
json: () => Promise.resolve(fakeManifest),
|
|
140
144
|
}),
|
|
@@ -164,6 +168,7 @@ describe('PluginService', () => {
|
|
|
164
168
|
const manifestUrl = 'http://fake-url.com/manifest.json';
|
|
165
169
|
global.fetch = vi.fn(() =>
|
|
166
170
|
Promise.resolve({
|
|
171
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
167
172
|
ok: true,
|
|
168
173
|
json: () => {
|
|
169
174
|
throw new Error('abc');
|
|
@@ -184,6 +189,7 @@ describe('PluginService', () => {
|
|
|
184
189
|
global.fetch = vi.fn(() =>
|
|
185
190
|
Promise.resolve({
|
|
186
191
|
ok: false,
|
|
192
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
187
193
|
json: () => Promise.resolve(fakeManifest),
|
|
188
194
|
}),
|
|
189
195
|
) as any;
|
|
@@ -194,6 +200,85 @@ describe('PluginService', () => {
|
|
|
194
200
|
expect(e).toEqual(new TypeError('fetchError'));
|
|
195
201
|
}
|
|
196
202
|
});
|
|
203
|
+
|
|
204
|
+
describe('support OpenAPI manifest', () => {
|
|
205
|
+
it('should get plugin manifest', async () => {
|
|
206
|
+
const manifestUrl = 'http://fake-url.com/manifest.json';
|
|
207
|
+
const openapiUrl = 'http://fake-url.com/openapiUrl.json';
|
|
208
|
+
|
|
209
|
+
const fakeManifest = {
|
|
210
|
+
$schema: '../node_modules/@lobehub/chat-plugin-sdk/schema.json',
|
|
211
|
+
api: [],
|
|
212
|
+
openapi: openapiUrl,
|
|
213
|
+
author: 'LobeHub',
|
|
214
|
+
createAt: '2023-08-12',
|
|
215
|
+
homepage: 'https://github.com/lobehub/chat-plugin-realtime-weather',
|
|
216
|
+
identifier: 'realtime-weather',
|
|
217
|
+
meta: {
|
|
218
|
+
avatar: '🌈',
|
|
219
|
+
tags: ['weather', 'realtime'],
|
|
220
|
+
title: 'Realtime Weather',
|
|
221
|
+
description: 'Get realtime weather information',
|
|
222
|
+
},
|
|
223
|
+
ui: {
|
|
224
|
+
url: 'https://realtime-weather.chat-plugin.lobehub.com/iframe',
|
|
225
|
+
height: 310,
|
|
226
|
+
},
|
|
227
|
+
version: '1',
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
global.fetch = vi.fn((url) =>
|
|
231
|
+
Promise.resolve({
|
|
232
|
+
ok: true,
|
|
233
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
234
|
+
json: () => Promise.resolve(url === openapiUrl ? openAPIV3 : fakeManifest),
|
|
235
|
+
}),
|
|
236
|
+
) as any;
|
|
237
|
+
|
|
238
|
+
const manifest = await pluginService.getPluginManifest(manifestUrl);
|
|
239
|
+
|
|
240
|
+
expect(manifest).toMatchSnapshot();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should return error on openAPIInvalid', async () => {
|
|
244
|
+
const openapiUrl = 'http://fake-url.com/openapiUrl.json';
|
|
245
|
+
const manifestUrl = 'http://fake-url.com/manifest.json';
|
|
246
|
+
const fakeManifest = {
|
|
247
|
+
$schema: '../node_modules/@lobehub/chat-plugin-sdk/schema.json',
|
|
248
|
+
api: [],
|
|
249
|
+
openapi: openapiUrl,
|
|
250
|
+
author: 'LobeHub',
|
|
251
|
+
createAt: '2023-08-12',
|
|
252
|
+
homepage: 'https://github.com/lobehub/chat-plugin-realtime-weather',
|
|
253
|
+
identifier: 'realtime-weather',
|
|
254
|
+
meta: {
|
|
255
|
+
avatar: '🌈',
|
|
256
|
+
tags: ['weather', 'realtime'],
|
|
257
|
+
title: 'Realtime Weather',
|
|
258
|
+
description: 'Get realtime weather information',
|
|
259
|
+
},
|
|
260
|
+
ui: {
|
|
261
|
+
url: 'https://realtime-weather.chat-plugin.lobehub.com/iframe',
|
|
262
|
+
height: 310,
|
|
263
|
+
},
|
|
264
|
+
version: '1',
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
global.fetch = vi.fn((url) =>
|
|
268
|
+
Promise.resolve({
|
|
269
|
+
ok: true,
|
|
270
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
271
|
+
json: () => Promise.resolve(url === openapiUrl ? [] : fakeManifest),
|
|
272
|
+
}),
|
|
273
|
+
) as any;
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
await pluginService.getPluginManifest(manifestUrl);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
expect(e).toEqual(new TypeError('openAPIInvalid'));
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
});
|
|
197
282
|
});
|
|
198
283
|
|
|
199
284
|
describe('installPlugin', () => {
|
|
@@ -329,5 +414,9 @@ describe('PluginService', () => {
|
|
|
329
414
|
});
|
|
330
415
|
});
|
|
331
416
|
|
|
332
|
-
|
|
417
|
+
it('can parse the OpenAI plugin', async () => {
|
|
418
|
+
const manifest = pluginService['convertOpenAIManifestToLobeManifest'](OpenAIPlugin as any);
|
|
419
|
+
|
|
420
|
+
expect(manifest).toMatchSnapshot();
|
|
421
|
+
});
|
|
333
422
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { ShareGPTConversation } from '@/types/share';
|
|
4
|
+
import { parseMarkdown } from '@/utils/parseMarkdown';
|
|
5
|
+
|
|
6
|
+
import { SHARE_GPT_URL, shareGPTService } from '../share';
|
|
7
|
+
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
vi.mock('@/utils/parseMarkdown', () => ({
|
|
10
|
+
parseMarkdown: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
global.fetch = vi.fn();
|
|
14
|
+
|
|
15
|
+
describe('ShareGPTService', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create and return a ShareGPT URL when fetch is successful', async () => {
|
|
21
|
+
// Arrange
|
|
22
|
+
const mockId = '123abc';
|
|
23
|
+
const conversation: ShareGPTConversation = {
|
|
24
|
+
items: [
|
|
25
|
+
{ from: 'human', value: 'Hello' },
|
|
26
|
+
{ from: 'gpt', value: 'Hi there!' },
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
(parseMarkdown as Mock).mockResolvedValue('Parsed markdown');
|
|
30
|
+
(fetch as Mock).mockResolvedValue({
|
|
31
|
+
json: () => Promise.resolve({ id: mockId }),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Act
|
|
35
|
+
const url = await shareGPTService.createShareGPTUrl(conversation);
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
expect(parseMarkdown).toHaveBeenCalledWith('Hi there!');
|
|
39
|
+
expect(fetch).toHaveBeenCalledWith(SHARE_GPT_URL, expect.anything());
|
|
40
|
+
expect(url).toBe(`https://shareg.pt/${mockId}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should throw an error when the fetch call fails', async () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
const conversation: ShareGPTConversation = {
|
|
46
|
+
items: [{ from: 'human', value: 'Hello' }],
|
|
47
|
+
};
|
|
48
|
+
(fetch as Mock).mockRejectedValue(new Error('Network error'));
|
|
49
|
+
|
|
50
|
+
// Act & Assert
|
|
51
|
+
await expect(shareGPTService.createShareGPTUrl(conversation)).rejects.toThrow('Network error');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should not parse markdown for items not from gpt', async () => {
|
|
55
|
+
// Arrange
|
|
56
|
+
const mockId = '123abc';
|
|
57
|
+
const conversation: ShareGPTConversation = {
|
|
58
|
+
items: [
|
|
59
|
+
{ from: 'human', value: 'Hello' },
|
|
60
|
+
{ from: 'human', value: 'How are you?' },
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
(fetch as Mock).mockResolvedValue({
|
|
64
|
+
json: () => Promise.resolve({ id: mockId }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Act
|
|
68
|
+
await shareGPTService.createShareGPTUrl(conversation);
|
|
69
|
+
|
|
70
|
+
// Assert
|
|
71
|
+
expect(parseMarkdown).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should throw an error if the response does not contain an id', async () => {
|
|
75
|
+
// Arrange
|
|
76
|
+
const conversation: ShareGPTConversation = {
|
|
77
|
+
items: [{ from: 'human', value: 'Hello' }],
|
|
78
|
+
};
|
|
79
|
+
(fetch as Mock).mockResolvedValue({
|
|
80
|
+
json: () => Promise.resolve({}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Act & Assert
|
|
84
|
+
await expect(shareGPTService.createShareGPTUrl(conversation)).rejects.toThrow();
|
|
85
|
+
});
|
|
86
|
+
});
|
package/src/services/_url.ts
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const VERSION_URL = 'https://registry.npmmirror.com/@lobehub/chat';
|
|
2
|
+
|
|
3
|
+
class GlobalService {
|
|
4
|
+
/**
|
|
5
|
+
* get latest version from npm
|
|
6
|
+
*/
|
|
7
|
+
getLatestVersion = async (): Promise<string> => {
|
|
8
|
+
const res = await fetch(VERSION_URL);
|
|
9
|
+
const data = await res.json();
|
|
10
|
+
|
|
11
|
+
return data['dist-tags']?.latest;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const globalService = new GlobalService();
|
package/src/services/plugin.ts
CHANGED
|
@@ -3,10 +3,14 @@ import {
|
|
|
3
3
|
LobeChatPluginsMarketIndex,
|
|
4
4
|
pluginManifestSchema,
|
|
5
5
|
} from '@lobehub/chat-plugin-sdk';
|
|
6
|
+
import { OpenAPIConvertor } from '@lobehub/chat-plugin-sdk/openapi';
|
|
7
|
+
import YAML from 'yaml';
|
|
6
8
|
|
|
7
9
|
import { getPluginIndexJSON } from '@/const/url';
|
|
8
10
|
import { PluginModel } from '@/database/models/plugin';
|
|
11
|
+
import { PROXY_URL } from '@/services/_url';
|
|
9
12
|
import { globalHelpers } from '@/store/global/helpers';
|
|
13
|
+
import { OpenAIPluginManifest } from '@/types/openai/plugin';
|
|
10
14
|
import { LobeTool } from '@/types/tool';
|
|
11
15
|
import { LobeToolCustomPlugin } from '@/types/tool/plugin';
|
|
12
16
|
|
|
@@ -16,6 +20,35 @@ export interface InstallPluginParams {
|
|
|
16
20
|
type: 'plugin' | 'customPlugin';
|
|
17
21
|
}
|
|
18
22
|
class PluginService {
|
|
23
|
+
private _fetchJSON = async <T = any>(url: string, proxy = false): Promise<T> => {
|
|
24
|
+
// 2. 发送请求
|
|
25
|
+
let res: Response;
|
|
26
|
+
try {
|
|
27
|
+
res = await (proxy ? fetch(PROXY_URL, { body: url, method: 'POST' }) : fetch(url));
|
|
28
|
+
} catch {
|
|
29
|
+
throw new TypeError('fetchError');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
throw new TypeError('fetchError');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let data;
|
|
37
|
+
const contentType = res.headers.get('Content-Type');
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
if (contentType === 'application/json') {
|
|
41
|
+
data = await res.json();
|
|
42
|
+
} else {
|
|
43
|
+
const yaml = await res.text();
|
|
44
|
+
data = YAML.parse(yaml);
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
throw new TypeError('urlError');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return data;
|
|
51
|
+
};
|
|
19
52
|
/**
|
|
20
53
|
* get plugin list from store
|
|
21
54
|
*/
|
|
@@ -29,29 +62,24 @@ class PluginService {
|
|
|
29
62
|
return data;
|
|
30
63
|
};
|
|
31
64
|
|
|
32
|
-
getPluginManifest = async (
|
|
65
|
+
getPluginManifest = async (
|
|
66
|
+
url?: string,
|
|
67
|
+
useProxy: boolean = false,
|
|
68
|
+
): Promise<LobeChatPluginManifest> => {
|
|
33
69
|
// 1. valid plugin
|
|
34
70
|
if (!url) {
|
|
35
71
|
throw new TypeError('noManifest');
|
|
36
72
|
}
|
|
37
73
|
|
|
38
74
|
// 2. 发送请求
|
|
39
|
-
let res: Response;
|
|
40
|
-
try {
|
|
41
|
-
res = await fetch(url);
|
|
42
|
-
} catch {
|
|
43
|
-
throw new TypeError('fetchError');
|
|
44
|
-
}
|
|
45
75
|
|
|
46
|
-
|
|
47
|
-
throw new TypeError('fetchError');
|
|
48
|
-
}
|
|
76
|
+
let data = await this._fetchJSON<LobeChatPluginManifest>(url, useProxy);
|
|
49
77
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
// if there is a description_for_model, it is an OpenAI plugin
|
|
80
|
+
// we need convert to lobe plugin
|
|
81
|
+
if (data['description_for_model']) {
|
|
82
|
+
data = this.convertOpenAIManifestToLobeManifest(data as any);
|
|
55
83
|
}
|
|
56
84
|
|
|
57
85
|
// 3. 校验插件文件格式规范
|
|
@@ -61,6 +89,21 @@ class PluginService {
|
|
|
61
89
|
throw new TypeError('manifestInvalid', { cause: parser.error });
|
|
62
90
|
}
|
|
63
91
|
|
|
92
|
+
// 4. if exist OpenAPI api, merge the OpenAPIs to api
|
|
93
|
+
if (parser.data.openapi) {
|
|
94
|
+
const openapiJson = await this._fetchJSON(parser.data.openapi, useProxy);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const convertor = new OpenAPIConvertor(openapiJson);
|
|
98
|
+
const openAPIs = await convertor.convertOpenAPIToPluginSchema();
|
|
99
|
+
data.api = [...data.api, ...openAPIs];
|
|
100
|
+
|
|
101
|
+
data.settings = await convertor.convertAuthToSettingsSchema(data.settings);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw new TypeError('openAPIInvalid', { cause: error });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
64
107
|
return data;
|
|
65
108
|
};
|
|
66
109
|
|
|
@@ -94,6 +137,47 @@ class PluginService {
|
|
|
94
137
|
async updatePluginSettings(id: string, settings: any) {
|
|
95
138
|
return PluginModel.update(id, { settings });
|
|
96
139
|
}
|
|
140
|
+
|
|
141
|
+
private convertOpenAIManifestToLobeManifest = (
|
|
142
|
+
data: OpenAIPluginManifest,
|
|
143
|
+
): LobeChatPluginManifest => {
|
|
144
|
+
const manifest: LobeChatPluginManifest = {
|
|
145
|
+
api: [],
|
|
146
|
+
homepage: data.legal_info_url,
|
|
147
|
+
identifier: data.name_for_model,
|
|
148
|
+
meta: {
|
|
149
|
+
avatar: data.logo_url,
|
|
150
|
+
description: data.description_for_human,
|
|
151
|
+
title: data.name_for_human,
|
|
152
|
+
},
|
|
153
|
+
openapi: data.api.url,
|
|
154
|
+
|
|
155
|
+
type: 'default',
|
|
156
|
+
version: '1',
|
|
157
|
+
};
|
|
158
|
+
switch (data.auth.type) {
|
|
159
|
+
case 'none': {
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case 'service_http': {
|
|
163
|
+
manifest.settings = {
|
|
164
|
+
properties: {
|
|
165
|
+
apiAuthKey: {
|
|
166
|
+
default: data.auth.verification_tokens['openai'],
|
|
167
|
+
description: 'API Key',
|
|
168
|
+
format: 'password',
|
|
169
|
+
title: 'API Key',
|
|
170
|
+
type: 'string',
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
type: 'object',
|
|
174
|
+
};
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return manifest;
|
|
180
|
+
};
|
|
97
181
|
}
|
|
98
182
|
|
|
99
183
|
export const pluginService = new PluginService();
|