@electerm/electerm-react 2.8.8 → 2.10.6
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/client/common/constants.js +3 -3
- package/client/common/pre.js +1 -120
- package/client/components/bookmark-form/ai-bookmark-form.jsx +324 -0
- package/client/components/bookmark-form/bookmark-form.styl +1 -1
- package/client/components/bookmark-form/bookmark-schema.js +179 -0
- package/client/components/bookmark-form/common/ai-category-select.jsx +32 -0
- package/client/components/bookmark-form/common/category-select.jsx +2 -4
- package/client/components/bookmark-form/common/fields.jsx +0 -10
- package/client/components/bookmark-form/config/rdp.js +0 -1
- package/client/components/bookmark-form/config/session-config.js +3 -1
- package/client/components/bookmark-form/config/spice.js +44 -0
- package/client/components/bookmark-form/config/vnc.js +1 -2
- package/client/components/bookmark-form/fix-bookmark-default.js +134 -0
- package/client/components/bookmark-form/index.jsx +74 -13
- package/client/components/session/session.jsx +13 -3
- package/client/components/setting-panel/keywords-transport.jsx +0 -1
- package/client/components/shortcuts/shortcut-handler.js +11 -5
- package/client/components/sidebar/index.jsx +11 -1
- package/client/components/spice/spice-session.jsx +276 -0
- package/client/components/tabs/add-btn-menu.jsx +9 -2
- package/client/components/terminal/attach-addon-custom.js +20 -76
- package/client/components/terminal/terminal.jsx +34 -28
- package/client/components/terminal/transfer-client-base.js +232 -0
- package/client/components/terminal/trzsz-client.js +306 -0
- package/client/components/terminal/xterm-loader.js +109 -0
- package/client/components/terminal/zmodem-client.js +13 -166
- package/client/components/text-editor/simple-editor.jsx +1 -2
- package/client/entry/electerm.jsx +0 -2
- package/client/store/system-menu.js +10 -0
- package/package.json +1 -1
- package/client/common/trzsz.js +0 -46
- package/client/components/bookmark-form/common/wiki-alert.jsx +0 -9
- package/client/components/terminal/fs.js +0 -59
|
@@ -67,7 +67,8 @@ export const connectionMap = buildConst([
|
|
|
67
67
|
'web',
|
|
68
68
|
'rdp',
|
|
69
69
|
'vnc',
|
|
70
|
-
'ftp'
|
|
70
|
+
'ftp',
|
|
71
|
+
'spice'
|
|
71
72
|
])
|
|
72
73
|
|
|
73
74
|
export const authTypeMap = buildConst([
|
|
@@ -143,6 +144,7 @@ export const terminalSerialType = 'serial'
|
|
|
143
144
|
export const terminalTelnetType = 'telnet'
|
|
144
145
|
export const terminalLocalType = 'local'
|
|
145
146
|
export const terminalFtpType = 'ftp'
|
|
147
|
+
export const terminalSpiceType = 'spice'
|
|
146
148
|
export const openedSidebarKey = 'opened-sidebar'
|
|
147
149
|
export const sidebarPinnedKey = 'sidebar-pinned'
|
|
148
150
|
export const pinnedQuickCommandBarKey = 'pinned-quick-command-bar'
|
|
@@ -242,8 +244,6 @@ export const proxyHelpLink = 'https://github.com/electerm/electerm/wiki/proxy-fo
|
|
|
242
244
|
export const regexHelpLink = 'https://github.com/electerm/electerm/wiki/Terminal-keywords-highlight-regular-expression-exmaples'
|
|
243
245
|
export const connectionHoppingWikiLink = 'https://github.com/electerm/electerm/wiki/Connection-Hopping-Behavior-Change-in-electerm-since-v1.50.65'
|
|
244
246
|
export const aiConfigWikiLink = 'https://github.com/electerm/electerm/wiki/AI-model-config-guide'
|
|
245
|
-
export const rdpWikiLink = 'https://github.com/electerm/electerm/wiki/RDP-limitation'
|
|
246
|
-
export const vncWikiLink = 'https://github.com/electerm/electerm/wiki/VNC-session-known-issues'
|
|
247
247
|
export const modals = {
|
|
248
248
|
hide: 0,
|
|
249
249
|
setting: 1,
|
package/client/common/pre.js
CHANGED
|
@@ -12,32 +12,6 @@ const props = runSync('getConstants')
|
|
|
12
12
|
props.env = JSON.parse(props.env)
|
|
13
13
|
props.versions = JSON.parse(props.versions)
|
|
14
14
|
|
|
15
|
-
// Encoding function
|
|
16
|
-
function encodeUint8Array (uint8Array) {
|
|
17
|
-
let str = ''
|
|
18
|
-
const len = uint8Array.byteLength
|
|
19
|
-
|
|
20
|
-
for (let i = 0; i < len; i++) {
|
|
21
|
-
str += String.fromCharCode(uint8Array[i])
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return btoa(str)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Decoding function
|
|
28
|
-
function decodeBase64String (base64String) {
|
|
29
|
-
const str = atob(base64String)
|
|
30
|
-
const len = str.length
|
|
31
|
-
|
|
32
|
-
const uint8Array = new Uint8Array(len)
|
|
33
|
-
|
|
34
|
-
for (let i = 0; i < len; i++) {
|
|
35
|
-
uint8Array[i] = str.charCodeAt(i)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return uint8Array
|
|
39
|
-
}
|
|
40
|
-
|
|
41
15
|
window.pre = {
|
|
42
16
|
requireAuth: runSync('shouldAuth'),
|
|
43
17
|
readClipboard: () => {
|
|
@@ -71,101 +45,8 @@ const path = {
|
|
|
71
45
|
basename: window.pre.basename
|
|
72
46
|
}
|
|
73
47
|
|
|
74
|
-
const fs = {
|
|
75
|
-
stat: (path, cb) => {
|
|
76
|
-
window.fs.statCustom(path)
|
|
77
|
-
.catch(err => cb(err))
|
|
78
|
-
.then(obj => {
|
|
79
|
-
obj.isDirectory = () => obj.isD
|
|
80
|
-
obj.isFile = () => obj.isF
|
|
81
|
-
cb(undefined, obj)
|
|
82
|
-
})
|
|
83
|
-
},
|
|
84
|
-
access: (...args) => {
|
|
85
|
-
const cb = args.pop()
|
|
86
|
-
window.fs.access(...args)
|
|
87
|
-
.then((data) => cb(undefined, data))
|
|
88
|
-
.catch((err) => cb(err))
|
|
89
|
-
},
|
|
90
|
-
open: (...args) => {
|
|
91
|
-
const cb = args.pop()
|
|
92
|
-
if (window.et.isWebApp) {
|
|
93
|
-
window.fs.openCustom(...args)
|
|
94
|
-
.then((data) => cb(undefined, data))
|
|
95
|
-
.catch((err) => cb(err))
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
runGlobalAsync('fsOpen', ...args)
|
|
99
|
-
.then((data) => cb(undefined, data))
|
|
100
|
-
.catch((err) => cb(err))
|
|
101
|
-
},
|
|
102
|
-
read: (p1, arr, ...args) => {
|
|
103
|
-
const cb = args.pop()
|
|
104
|
-
if (window.et.isWebApp) {
|
|
105
|
-
window.fs.readCustom(
|
|
106
|
-
p1,
|
|
107
|
-
arr.length,
|
|
108
|
-
...args
|
|
109
|
-
)
|
|
110
|
-
.then((data) => {
|
|
111
|
-
const { n, newArr } = data
|
|
112
|
-
const newArr1 = decodeBase64String(newArr)
|
|
113
|
-
cb(undefined, n, newArr1)
|
|
114
|
-
})
|
|
115
|
-
.catch(err => cb(err))
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
runGlobalAsync('fsRead', p1, arr.length, ...args)
|
|
119
|
-
.then((data) => {
|
|
120
|
-
const { n, buffer } = data
|
|
121
|
-
cb(undefined, n, buffer)
|
|
122
|
-
})
|
|
123
|
-
.catch(err => cb(err))
|
|
124
|
-
},
|
|
125
|
-
close: (fd, cb) => {
|
|
126
|
-
if (window.et.isWebApp) {
|
|
127
|
-
window.fs.closeCustom(fd)
|
|
128
|
-
.then((data) => cb(undefined, data))
|
|
129
|
-
.catch((err) => cb(err))
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
runGlobalAsync('fsClose', fd)
|
|
133
|
-
.then((data) => cb(undefined, data))
|
|
134
|
-
.catch((err) => cb(err))
|
|
135
|
-
},
|
|
136
|
-
readdir: (p, cb) => {
|
|
137
|
-
window.fs.readdir(p)
|
|
138
|
-
.then((data) => cb(undefined, data))
|
|
139
|
-
.catch((err) => cb(err))
|
|
140
|
-
},
|
|
141
|
-
mkdir: (...args) => {
|
|
142
|
-
const cb = args.pop()
|
|
143
|
-
window.fs.mkdir(...args)
|
|
144
|
-
.then((data) => cb(undefined, data))
|
|
145
|
-
.catch((err) => cb(err))
|
|
146
|
-
},
|
|
147
|
-
write: (p1, buf, cb) => {
|
|
148
|
-
if (window.et.isWebApp) {
|
|
149
|
-
window.fs.writeCustom(p1, encodeUint8Array(buf))
|
|
150
|
-
.then((data) => cb(undefined, data))
|
|
151
|
-
.catch((err) => cb(err))
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
runGlobalAsync('fsWrite', p1, buf)
|
|
155
|
-
.then((data) => cb(undefined, data))
|
|
156
|
-
.catch((err) => cb(err))
|
|
157
|
-
},
|
|
158
|
-
realpath: (p, cb) => {
|
|
159
|
-
window.fs.realpath(p)
|
|
160
|
-
.then((data) => cb(undefined, data))
|
|
161
|
-
.catch((err) => cb(err))
|
|
162
|
-
},
|
|
163
|
-
constants: runSync('getFsContants')
|
|
164
|
-
}
|
|
165
|
-
|
|
166
48
|
window.reqs = {
|
|
167
|
-
path
|
|
168
|
-
fs
|
|
49
|
+
path
|
|
169
50
|
}
|
|
170
51
|
|
|
171
52
|
function require (name) {
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI-powered bookmark generation form
|
|
3
|
+
*/
|
|
4
|
+
import { useState } from 'react'
|
|
5
|
+
import { Button, Input, message, Space, Alert } from 'antd'
|
|
6
|
+
import {
|
|
7
|
+
RobotOutlined,
|
|
8
|
+
LoadingOutlined,
|
|
9
|
+
CheckOutlined,
|
|
10
|
+
CloseOutlined,
|
|
11
|
+
EditOutlined,
|
|
12
|
+
CopyOutlined,
|
|
13
|
+
DownloadOutlined,
|
|
14
|
+
EyeOutlined
|
|
15
|
+
} from '@ant-design/icons'
|
|
16
|
+
import SimpleEditor from '../text-editor/simple-editor'
|
|
17
|
+
import { copy } from '../../common/clipboard'
|
|
18
|
+
import download from '../../common/download'
|
|
19
|
+
import AICategorySelect from './common/ai-category-select.jsx'
|
|
20
|
+
import HelpIcon from '../common/help-icon'
|
|
21
|
+
import Modal from '../common/modal.jsx'
|
|
22
|
+
import { buildPrompt } from './bookmark-schema.js'
|
|
23
|
+
import { fixBookmarkData } from './fix-bookmark-default.js'
|
|
24
|
+
import generate from '../../common/id-with-stamp'
|
|
25
|
+
|
|
26
|
+
const { TextArea } = Input
|
|
27
|
+
const e = window.translate
|
|
28
|
+
|
|
29
|
+
export default function AIBookmarkForm (props) {
|
|
30
|
+
const { onCancel } = props
|
|
31
|
+
const [description, setDescription] = useState('')
|
|
32
|
+
const [loading, setLoading] = useState(false)
|
|
33
|
+
const [showConfirm, setShowConfirm] = useState(false)
|
|
34
|
+
const [editMode, setEditMode] = useState(false)
|
|
35
|
+
const [editorText, setEditorText] = useState('')
|
|
36
|
+
const [selectedCategory, setSelectedCategory] = useState('default')
|
|
37
|
+
|
|
38
|
+
const handleGenerate = async () => {
|
|
39
|
+
if (window.store.aiConfigMissing()) {
|
|
40
|
+
window.store.toggleAIConfig()
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!description.trim()) {
|
|
45
|
+
return message.warning(e('description') + ' required')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setLoading(true)
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const config = window.store.config
|
|
52
|
+
const prompt = buildPrompt(description)
|
|
53
|
+
|
|
54
|
+
const aiResponse = await window.pre.runGlobalAsync(
|
|
55
|
+
'AIchat',
|
|
56
|
+
prompt,
|
|
57
|
+
config.modelAI,
|
|
58
|
+
'You are a helpful assistant that generates bookmark configurations in JSON format.',
|
|
59
|
+
config.baseURLAI,
|
|
60
|
+
config.apiPathAI,
|
|
61
|
+
config.apiKeyAI,
|
|
62
|
+
config.proxyAI,
|
|
63
|
+
false // Disable streaming for structured response
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if (aiResponse && aiResponse.error) {
|
|
67
|
+
throw new Error(aiResponse.error)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let bookmarkData
|
|
71
|
+
if (aiResponse && aiResponse.response) {
|
|
72
|
+
// Parse the JSON response
|
|
73
|
+
let jsonStr = aiResponse.response.trim()
|
|
74
|
+
// Remove markdown code blocks if present
|
|
75
|
+
if (jsonStr.startsWith('```json')) {
|
|
76
|
+
jsonStr = jsonStr.slice(7)
|
|
77
|
+
} else if (jsonStr.startsWith('```')) {
|
|
78
|
+
jsonStr = jsonStr.slice(3)
|
|
79
|
+
}
|
|
80
|
+
if (jsonStr.endsWith('```')) {
|
|
81
|
+
jsonStr = jsonStr.slice(0, -3)
|
|
82
|
+
}
|
|
83
|
+
jsonStr = jsonStr.trim()
|
|
84
|
+
|
|
85
|
+
bookmarkData = JSON.parse(jsonStr)
|
|
86
|
+
const pretty = JSON.stringify(bookmarkData, null, 2)
|
|
87
|
+
setEditorText(pretty)
|
|
88
|
+
// set default category when preview opens
|
|
89
|
+
setSelectedCategory('default')
|
|
90
|
+
setShowConfirm(true)
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('AI bookmark generation error:', error)
|
|
94
|
+
message.error(e('aiGenerateError') || 'AI generation failed: ' + error.message)
|
|
95
|
+
} finally {
|
|
96
|
+
setLoading(false)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getGeneratedData () {
|
|
101
|
+
if (!editorText) return message.warning(e('noData') || 'No data')
|
|
102
|
+
let parsed = null
|
|
103
|
+
try {
|
|
104
|
+
parsed = fixBookmarkData(JSON.parse(editorText))
|
|
105
|
+
} catch (err) {
|
|
106
|
+
return message.error(e('invalidJson') || 'Invalid JSON')
|
|
107
|
+
}
|
|
108
|
+
if (!parsed) return []
|
|
109
|
+
return Array.isArray(parsed) ? parsed : [parsed]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const createBookmark = async (bm) => {
|
|
113
|
+
const { store } = window
|
|
114
|
+
const { addItem } = store
|
|
115
|
+
const fixedBm = fixBookmarkData(bm)
|
|
116
|
+
if (!fixedBm.id) {
|
|
117
|
+
fixedBm.id = generate()
|
|
118
|
+
}
|
|
119
|
+
if (fixedBm.connectionHoppings?.length) {
|
|
120
|
+
fixedBm.hasHopping = true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Add bookmark
|
|
124
|
+
addItem(fixedBm, 'bookmarks')
|
|
125
|
+
|
|
126
|
+
// Ensure the bookmark id is registered in its group
|
|
127
|
+
try {
|
|
128
|
+
const groupId = fixedBm.category || selectedCategory || 'default'
|
|
129
|
+
const group = window.store.bookmarkGroups.find(g => g.id === groupId)
|
|
130
|
+
if (group) {
|
|
131
|
+
group.bookmarkIds = [
|
|
132
|
+
...new Set([...(group.bookmarkIds || []), fixedBm.id])
|
|
133
|
+
]
|
|
134
|
+
fixedBm.color = group.color
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error('Failed to update bookmark group:', err)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const handleConfirm = async () => {
|
|
142
|
+
const parsed = getGeneratedData()
|
|
143
|
+
if (!parsed.length) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
for (const item of parsed) {
|
|
147
|
+
// set defaults like mcpAddBookmark would
|
|
148
|
+
await createBookmark(item)
|
|
149
|
+
}
|
|
150
|
+
setShowConfirm(false)
|
|
151
|
+
setDescription('')
|
|
152
|
+
message.success(e('Done'))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const handleCancelConfirm = () => {
|
|
156
|
+
setShowConfirm(false)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const handleCancel = () => {
|
|
160
|
+
setDescription('')
|
|
161
|
+
if (onCancel) {
|
|
162
|
+
onCancel()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const handleToggleEdit = () => {
|
|
167
|
+
setEditMode(!editMode)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const handleEditorChange = (e) => {
|
|
171
|
+
// SimpleEditor passes event-like or value via onChange
|
|
172
|
+
const val = e && e.target ? e.target.value : e
|
|
173
|
+
setEditorText(val)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const handleCopy = () => {
|
|
177
|
+
copy(editorText)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const handleSaveToFile = async () => {
|
|
181
|
+
const parsed = getGeneratedData()
|
|
182
|
+
if (!parsed.length) {
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
const date = new Date().toISOString().slice(0, 10)
|
|
186
|
+
const fileName = `bookmarks-${date}.json`
|
|
187
|
+
await download(fileName, editorText)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const renderEditor = () => {
|
|
191
|
+
const editorProps = {
|
|
192
|
+
value: editorText,
|
|
193
|
+
onChange: handleEditorChange
|
|
194
|
+
}
|
|
195
|
+
return (
|
|
196
|
+
<SimpleEditor
|
|
197
|
+
{...editorProps}
|
|
198
|
+
/>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const renderPreview = () => {
|
|
203
|
+
if (editMode) {
|
|
204
|
+
return renderEditor()
|
|
205
|
+
}
|
|
206
|
+
return (
|
|
207
|
+
<pre className='ai-bookmark-json-preview'>
|
|
208
|
+
{editorText}
|
|
209
|
+
</pre>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const renderCategorySelect = () => {
|
|
214
|
+
return (
|
|
215
|
+
<AICategorySelect
|
|
216
|
+
bookmarkGroups={window.store.bookmarkGroups}
|
|
217
|
+
value={selectedCategory}
|
|
218
|
+
onChange={setSelectedCategory}
|
|
219
|
+
/>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const textAreaProps = {
|
|
224
|
+
value: description,
|
|
225
|
+
onChange: e => setDescription(e.target.value),
|
|
226
|
+
placeholder: e('createBookmarkByAI'),
|
|
227
|
+
autoSize: { minRows: 4, maxRows: 8 },
|
|
228
|
+
disabled: loading
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const generateBtnProps = {
|
|
232
|
+
type: 'primary',
|
|
233
|
+
onClick: handleGenerate,
|
|
234
|
+
disabled: !description.trim(),
|
|
235
|
+
icon: loading ? <LoadingOutlined /> : <RobotOutlined />,
|
|
236
|
+
loading
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const modalProps = {
|
|
240
|
+
title: e('confirmBookmarkData') || 'Confirm Bookmark Data',
|
|
241
|
+
open: showConfirm,
|
|
242
|
+
onCancel: handleCancelConfirm,
|
|
243
|
+
footer: (
|
|
244
|
+
<div className='custom-modal-footer-buttons'>
|
|
245
|
+
<Button onClick={handleCancelConfirm}>
|
|
246
|
+
<CloseOutlined /> {e('cancel')}
|
|
247
|
+
</Button>
|
|
248
|
+
<Button type='primary' onClick={handleConfirm}>
|
|
249
|
+
<CheckOutlined /> {e('confirm')}
|
|
250
|
+
</Button>
|
|
251
|
+
</div>
|
|
252
|
+
),
|
|
253
|
+
width: '80%'
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const editBtnProps = {
|
|
257
|
+
icon: editMode ? <EyeOutlined /> : <EditOutlined />,
|
|
258
|
+
title: editMode ? e('preview') : e('edit'),
|
|
259
|
+
onClick: handleToggleEdit
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const copyBtnProps = {
|
|
263
|
+
icon: <CopyOutlined />,
|
|
264
|
+
title: e('copy'),
|
|
265
|
+
onClick: handleCopy
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const downloadBtnProps = {
|
|
269
|
+
icon: <DownloadOutlined />,
|
|
270
|
+
title: e('download'),
|
|
271
|
+
onClick: handleSaveToFile
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const cancelProps = {
|
|
275
|
+
onClick: handleCancel,
|
|
276
|
+
title: e('cancel'),
|
|
277
|
+
icon: <CloseOutlined />,
|
|
278
|
+
className: 'mg1l'
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<div className='ai-bookmark-form pd2'>
|
|
283
|
+
<div className='pd1b ai-bookmark-header'>
|
|
284
|
+
<span className='ai-title'>
|
|
285
|
+
<RobotOutlined className='mg1r' />
|
|
286
|
+
{e('createBookmarkByAI')}
|
|
287
|
+
</span>
|
|
288
|
+
<HelpIcon link='https://github.com/electerm/electerm/wiki/Create-bookmark-by-AI' />
|
|
289
|
+
</div>
|
|
290
|
+
<div className='pd1b'>
|
|
291
|
+
<Alert
|
|
292
|
+
type='info'
|
|
293
|
+
showIcon
|
|
294
|
+
title={e('aiSecurityNotice')}
|
|
295
|
+
/>
|
|
296
|
+
</div>
|
|
297
|
+
<div className='pd1b'>
|
|
298
|
+
<TextArea {...textAreaProps} />
|
|
299
|
+
</div>
|
|
300
|
+
<div className='pd1t'>
|
|
301
|
+
<Button {...generateBtnProps}>
|
|
302
|
+
{e('submit')}
|
|
303
|
+
</Button>
|
|
304
|
+
<Button {...cancelProps}>
|
|
305
|
+
{e('cancel')}
|
|
306
|
+
</Button>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<Modal {...modalProps}>
|
|
310
|
+
<div className='pd1y'>
|
|
311
|
+
<Space.Compact className='ai-action-buttons'>
|
|
312
|
+
<Button {...editBtnProps} />
|
|
313
|
+
<Button {...copyBtnProps} />
|
|
314
|
+
<Button {...downloadBtnProps} />
|
|
315
|
+
</Space.Compact>
|
|
316
|
+
</div>
|
|
317
|
+
<div className='pd1y'>
|
|
318
|
+
{renderCategorySelect()}
|
|
319
|
+
</div>
|
|
320
|
+
{renderPreview()}
|
|
321
|
+
</Modal>
|
|
322
|
+
</div>
|
|
323
|
+
)
|
|
324
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const bookmarkSchema = {
|
|
2
|
+
ssh: {
|
|
3
|
+
type: 'ssh',
|
|
4
|
+
host: 'string (required) - hostname or IP address',
|
|
5
|
+
port: 'number (default: 22) - SSH port',
|
|
6
|
+
username: 'string (required) - SSH username',
|
|
7
|
+
password: 'string - password for authentication',
|
|
8
|
+
privateKey: 'string - private key content or path for key-based auth',
|
|
9
|
+
passphrase: 'string - passphrase for private key/cetificate',
|
|
10
|
+
certificate: 'string - certificate content',
|
|
11
|
+
authType: 'string - auth type (password|privateKey|profiles)',
|
|
12
|
+
profile: 'string - profile id to reuse saved auth',
|
|
13
|
+
title: 'string - bookmark title',
|
|
14
|
+
description: 'string - bookmark description',
|
|
15
|
+
startDirectoryRemote: 'string - remote starting directory',
|
|
16
|
+
startDirectoryLocal: 'string - local starting directory',
|
|
17
|
+
enableSsh: 'boolean - enable ssh, default is true',
|
|
18
|
+
enableSftp: 'boolean - enable sftp, default is true',
|
|
19
|
+
sshTunnels: 'array - ssh tunnel definitions (see sshTunnels items)',
|
|
20
|
+
connectionHoppings: 'array - connection hopping definitions',
|
|
21
|
+
useSshAgent: 'boolean - use SSH agent, default is true',
|
|
22
|
+
sshAgent: 'string - ssh agent path',
|
|
23
|
+
serverHostKey: 'array - server host key algorithms',
|
|
24
|
+
cipher: 'array - cipher list',
|
|
25
|
+
runScripts: 'array - run scripts after connected ({delay,script})',
|
|
26
|
+
quickCommands: 'array - quick commands ({name,command})',
|
|
27
|
+
proxy: 'string - proxy address (socks5://...)',
|
|
28
|
+
x11: 'boolean - enable x11 forwarding, default is false',
|
|
29
|
+
term: 'string - terminal type, default is xterm-256color, required',
|
|
30
|
+
displayRaw: 'boolean - display raw output, default is false',
|
|
31
|
+
encode: 'string - charset, default is utf8',
|
|
32
|
+
envLang: 'string - ENV LANG, default is en_US.UTF-8',
|
|
33
|
+
setEnv: 'string - environment variables, format: `KEY1=VALUE1 KEY2=VALUE2`',
|
|
34
|
+
color: 'string - tag color, like #000000',
|
|
35
|
+
interactiveValues: 'strings separated by newline'
|
|
36
|
+
},
|
|
37
|
+
sshTunnelsItem: {
|
|
38
|
+
sshTunnel: 'string - forwardRemoteToLocal|forwardLocalToRemote|dynamicForward',
|
|
39
|
+
sshTunnelLocalHost: 'string',
|
|
40
|
+
sshTunnelLocalPort: 'number',
|
|
41
|
+
sshTunnelRemoteHost: 'string',
|
|
42
|
+
sshTunnelRemotePort: 'number',
|
|
43
|
+
name: 'string - optional tunnel name'
|
|
44
|
+
},
|
|
45
|
+
connectionHoppingsItem: {
|
|
46
|
+
host: 'string',
|
|
47
|
+
port: 'number',
|
|
48
|
+
username: 'string',
|
|
49
|
+
password: 'string',
|
|
50
|
+
privateKey: 'string',
|
|
51
|
+
passphrase: 'string - passphrase',
|
|
52
|
+
certificate: 'string',
|
|
53
|
+
authType: 'string',
|
|
54
|
+
profile: 'string - profile id'
|
|
55
|
+
},
|
|
56
|
+
telnet: {
|
|
57
|
+
type: 'telnet',
|
|
58
|
+
host: 'string (required) - hostname or IP address',
|
|
59
|
+
port: 'number (default: 23) - Telnet port',
|
|
60
|
+
username: 'string - username',
|
|
61
|
+
password: 'string - password',
|
|
62
|
+
title: 'string - bookmark title',
|
|
63
|
+
description: 'string - bookmark description',
|
|
64
|
+
loginPrompt: 'string - login prompt regex',
|
|
65
|
+
passwordPrompt: 'string - password prompt regex',
|
|
66
|
+
runScripts: 'array - run scripts after connected ({delay,script})',
|
|
67
|
+
startDirectoryRemote: 'string - remote starting directory',
|
|
68
|
+
startDirectoryLocal: 'string - local starting directory',
|
|
69
|
+
profile: 'string - profile id',
|
|
70
|
+
proxy: 'string - proxy address (socks5://...)'
|
|
71
|
+
},
|
|
72
|
+
serial: {
|
|
73
|
+
type: 'serial',
|
|
74
|
+
path: 'string (required) - serial port path, e.g., /dev/ttyUSB0 or COM1',
|
|
75
|
+
baudRate: 'number (default: 9600) - baud rate',
|
|
76
|
+
dataBits: 'number (default: 8) - data bits',
|
|
77
|
+
stopBits: 'number (default: 1) - stop bits',
|
|
78
|
+
parity: 'string - "none", "even", "odd", "mark", "space"',
|
|
79
|
+
title: 'string - bookmark title',
|
|
80
|
+
rtscts: 'boolean - enable RTS/CTS flow control, default is false',
|
|
81
|
+
xon: 'boolean - enable XON flow control, default is false',
|
|
82
|
+
xoff: 'boolean - enable XOFF flow control, default is false',
|
|
83
|
+
xany: 'boolean - enable XANY flow control, default is false',
|
|
84
|
+
runScripts: 'array - run scripts after connected ({delay,script})',
|
|
85
|
+
description: 'string - bookmark description'
|
|
86
|
+
},
|
|
87
|
+
vnc: {
|
|
88
|
+
type: 'vnc',
|
|
89
|
+
host: 'string (required) - hostname or IP address',
|
|
90
|
+
port: 'number (default: 5900) - VNC port',
|
|
91
|
+
username: 'string - VNC username',
|
|
92
|
+
password: 'string - VNC password',
|
|
93
|
+
viewOnly: 'boolean - view only mode, default is false',
|
|
94
|
+
clipViewport: 'boolean - clip viewport to window, default is false',
|
|
95
|
+
scaleViewport: 'boolean - scale viewport to window, default is true',
|
|
96
|
+
qualityLevel: 'number (0-9) - VNC quality level, lower is faster, default is 3',
|
|
97
|
+
compressionLevel: 'number (0-9) - VNC compression level, lower is faster, default is 1',
|
|
98
|
+
shared: 'boolean - shared session, default is true',
|
|
99
|
+
proxy: 'string - proxy address (socks5://...)',
|
|
100
|
+
title: 'string - bookmark title',
|
|
101
|
+
description: 'string - bookmark description',
|
|
102
|
+
profile: 'string - profile id'
|
|
103
|
+
},
|
|
104
|
+
rdp: {
|
|
105
|
+
type: 'rdp',
|
|
106
|
+
host: 'string (required) - hostname or IP address',
|
|
107
|
+
port: 'number (default: 3389) - RDP port',
|
|
108
|
+
username: 'string - username',
|
|
109
|
+
password: 'string - password',
|
|
110
|
+
title: 'string - bookmark title',
|
|
111
|
+
description: 'string - bookmark description',
|
|
112
|
+
profile: 'string - profile id',
|
|
113
|
+
proxy: 'string - proxy address (socks5://...)',
|
|
114
|
+
domain: 'string - login domain'
|
|
115
|
+
},
|
|
116
|
+
ftp: {
|
|
117
|
+
type: 'ftp',
|
|
118
|
+
host: 'string (required) - hostname or IP address',
|
|
119
|
+
port: 'number (default: 21) - FTP port',
|
|
120
|
+
user: 'string - username',
|
|
121
|
+
secure: 'boolean - use secure FTP (FTPS), default is false',
|
|
122
|
+
password: 'string - password',
|
|
123
|
+
title: 'string - bookmark title',
|
|
124
|
+
profile: 'string - profile id',
|
|
125
|
+
description: 'string - bookmark description'
|
|
126
|
+
},
|
|
127
|
+
web: {
|
|
128
|
+
type: 'web',
|
|
129
|
+
url: 'string (required) - website URL',
|
|
130
|
+
title: 'string - bookmark title',
|
|
131
|
+
description: 'string - bookmark description',
|
|
132
|
+
useragent: 'string - custom user agent'
|
|
133
|
+
},
|
|
134
|
+
local: {
|
|
135
|
+
type: 'local',
|
|
136
|
+
title: 'string - bookmark title',
|
|
137
|
+
description: 'string - bookmark description',
|
|
138
|
+
startDirectoryLocal: 'string - local starting directory',
|
|
139
|
+
runScripts: 'array - run scripts after connected ({delay,script})'
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function buildPrompt (description) {
|
|
144
|
+
const lang = window.store.config.languageAI || window.store.getLangName()
|
|
145
|
+
const schemaDescription = Object.entries(bookmarkSchema)
|
|
146
|
+
.map(([type, fields]) => {
|
|
147
|
+
const fieldList = Object.entries(fields)
|
|
148
|
+
.map(([key, desc]) => ` ${key}: ${desc}`)
|
|
149
|
+
.join('\n')
|
|
150
|
+
return ` ${type}:\n${fieldList}`
|
|
151
|
+
})
|
|
152
|
+
.join('\n\n')
|
|
153
|
+
|
|
154
|
+
return `You are a bookmark configuration generator. Based on the user's natural language description, generate bookmark configurations in JSON format.
|
|
155
|
+
|
|
156
|
+
Available bookmark types and their fields:
|
|
157
|
+
${schemaDescription}
|
|
158
|
+
|
|
159
|
+
Important rules:
|
|
160
|
+
1. Analyze the user's description to determine the most appropriate connection type
|
|
161
|
+
2. For SSH connections, use type "ssh" and default port 22 unless specified
|
|
162
|
+
3. For Telnet connections, use type "telnet" and default port 23 unless specified
|
|
163
|
+
4. For VNC connections, use type "vnc" and default port 5900 unless specified
|
|
164
|
+
5. For RDP connections, use type "rdp" and default port 3389 unless specified
|
|
165
|
+
6. For FTP connections, use type "ftp" and default port 21 unless specified
|
|
166
|
+
7. For Serial connections, use type "serial"
|
|
167
|
+
8. For Web/Browser connections, use type "web" with a URL field
|
|
168
|
+
9. For Local terminal, use type "local"
|
|
169
|
+
10. Only include fields that are relevant to the connection type
|
|
170
|
+
11. Always include a meaningful title if not specified
|
|
171
|
+
12. Respond ONLY with valid JSON, no markdown formatting or explanations
|
|
172
|
+
13. Reply in ${lang} language
|
|
173
|
+
|
|
174
|
+
User description: ${description}
|
|
175
|
+
|
|
176
|
+
Generate the bookmark JSON:`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default bookmarkSchema
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TreeSelect } from 'antd'
|
|
2
|
+
import formatBookmarkGroups from './bookmark-group-tree-format'
|
|
3
|
+
|
|
4
|
+
const e = window.translate
|
|
5
|
+
|
|
6
|
+
export default function AICategorySelect ({
|
|
7
|
+
bookmarkGroups = [],
|
|
8
|
+
value,
|
|
9
|
+
onChange
|
|
10
|
+
}) {
|
|
11
|
+
const tree = formatBookmarkGroups(bookmarkGroups)
|
|
12
|
+
|
|
13
|
+
const handleChange = (categoryId) => {
|
|
14
|
+
if (onChange) {
|
|
15
|
+
onChange(categoryId)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className='pd1b'>
|
|
21
|
+
<label className='iblock mg1r'>{e('bookmarkCategory')}</label>
|
|
22
|
+
<TreeSelect
|
|
23
|
+
value={value}
|
|
24
|
+
treeData={tree}
|
|
25
|
+
treeDefaultExpandAll
|
|
26
|
+
showSearch
|
|
27
|
+
onChange={handleChange}
|
|
28
|
+
style={{ minWidth: 200 }}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|