@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.
Files changed (33) hide show
  1. package/client/common/constants.js +3 -3
  2. package/client/common/pre.js +1 -120
  3. package/client/components/bookmark-form/ai-bookmark-form.jsx +324 -0
  4. package/client/components/bookmark-form/bookmark-form.styl +1 -1
  5. package/client/components/bookmark-form/bookmark-schema.js +179 -0
  6. package/client/components/bookmark-form/common/ai-category-select.jsx +32 -0
  7. package/client/components/bookmark-form/common/category-select.jsx +2 -4
  8. package/client/components/bookmark-form/common/fields.jsx +0 -10
  9. package/client/components/bookmark-form/config/rdp.js +0 -1
  10. package/client/components/bookmark-form/config/session-config.js +3 -1
  11. package/client/components/bookmark-form/config/spice.js +44 -0
  12. package/client/components/bookmark-form/config/vnc.js +1 -2
  13. package/client/components/bookmark-form/fix-bookmark-default.js +134 -0
  14. package/client/components/bookmark-form/index.jsx +74 -13
  15. package/client/components/session/session.jsx +13 -3
  16. package/client/components/setting-panel/keywords-transport.jsx +0 -1
  17. package/client/components/shortcuts/shortcut-handler.js +11 -5
  18. package/client/components/sidebar/index.jsx +11 -1
  19. package/client/components/spice/spice-session.jsx +276 -0
  20. package/client/components/tabs/add-btn-menu.jsx +9 -2
  21. package/client/components/terminal/attach-addon-custom.js +20 -76
  22. package/client/components/terminal/terminal.jsx +34 -28
  23. package/client/components/terminal/transfer-client-base.js +232 -0
  24. package/client/components/terminal/trzsz-client.js +306 -0
  25. package/client/components/terminal/xterm-loader.js +109 -0
  26. package/client/components/terminal/zmodem-client.js +13 -166
  27. package/client/components/text-editor/simple-editor.jsx +1 -2
  28. package/client/entry/electerm.jsx +0 -2
  29. package/client/store/system-menu.js +10 -0
  30. package/package.json +1 -1
  31. package/client/common/trzsz.js +0 -46
  32. package/client/components/bookmark-form/common/wiki-alert.jsx +0 -9
  33. 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,
@@ -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
+ }
@@ -14,4 +14,4 @@
14
14
  .item-item-use
15
15
  display inline-block
16
16
  .number-input
17
- min-width 210px
17
+ min-width 210px
@@ -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
+ }