@electerm/electerm-react 3.10.0 → 3.11.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/client/common/bookmark-schemas.js +164 -0
- package/client/common/ws.js +25 -6
- package/client/common/zod.js +180 -0
- package/client/components/ai/agent-tool-call-card.jsx +90 -0
- package/client/components/ai/agent-tools.js +193 -0
- package/client/components/ai/agent.js +159 -0
- package/client/components/ai/ai-chat-entry.jsx +11 -0
- package/client/components/ai/ai-chat-history-item.jsx +48 -2
- package/client/components/ai/ai-chat.jsx +25 -6
- package/client/components/ai/ai-config.jsx +45 -4
- package/client/components/ai/ai.styl +73 -0
- package/client/components/main/main.jsx +3 -3
- package/client/components/rdp/file-transfer.js +3 -0
- package/client/components/terminal/terminal-error-handle.jsx +1 -1
- package/client/components/terminal/terminal-interactive-ui.jsx +157 -0
- package/client/components/terminal/terminal-interactive.jsx +64 -163
- package/client/components/terminal-info/terminal-info-entry.jsx +11 -0
- package/client/components/text-editor/text-editor-entry.jsx +11 -0
- package/client/components/widgets/widget-form.jsx +27 -2
- package/client/entry/worker.js +9 -5
- package/package.json +1 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terminal interactive UI - renders a single interactive event modal
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Form, Button } from 'antd'
|
|
6
|
+
import Modal from '../common/modal'
|
|
7
|
+
import InputAutoFocus from '../common/input-auto-focus'
|
|
8
|
+
|
|
9
|
+
const e = window.translate
|
|
10
|
+
const FormItem = Form.Item
|
|
11
|
+
|
|
12
|
+
export default function TermInteractiveUI ({
|
|
13
|
+
opts,
|
|
14
|
+
onSend,
|
|
15
|
+
onClose
|
|
16
|
+
}) {
|
|
17
|
+
const [form] = Form.useForm()
|
|
18
|
+
|
|
19
|
+
function onCancel () {
|
|
20
|
+
onSend({
|
|
21
|
+
id: opts.id,
|
|
22
|
+
results: []
|
|
23
|
+
})
|
|
24
|
+
onClose()
|
|
25
|
+
}
|
|
26
|
+
function onOk () {
|
|
27
|
+
form.submit()
|
|
28
|
+
}
|
|
29
|
+
function onConfirm () {
|
|
30
|
+
onSend({
|
|
31
|
+
id: opts.id,
|
|
32
|
+
results: [opts.options.confirmResult || 'yes']
|
|
33
|
+
})
|
|
34
|
+
onClose()
|
|
35
|
+
}
|
|
36
|
+
function onIgnore () {
|
|
37
|
+
onSend({
|
|
38
|
+
id: opts.id,
|
|
39
|
+
results: Object.keys(opts.options.prompts).map(() => '')
|
|
40
|
+
})
|
|
41
|
+
onClose()
|
|
42
|
+
}
|
|
43
|
+
function onFinish (res) {
|
|
44
|
+
onSend({
|
|
45
|
+
id: opts.id,
|
|
46
|
+
results: Object.values(res)
|
|
47
|
+
})
|
|
48
|
+
onClose()
|
|
49
|
+
}
|
|
50
|
+
function renderFormItem (pro, i) {
|
|
51
|
+
const {
|
|
52
|
+
prompt,
|
|
53
|
+
echo
|
|
54
|
+
} = pro
|
|
55
|
+
const note = (opts.options.instructions || [])[i]
|
|
56
|
+
const type = echo
|
|
57
|
+
? 'input'
|
|
58
|
+
: 'password'
|
|
59
|
+
return (
|
|
60
|
+
<FormItem
|
|
61
|
+
key={prompt + i}
|
|
62
|
+
label={prompt}
|
|
63
|
+
rules={[{
|
|
64
|
+
required: true, message: 'required'
|
|
65
|
+
}]}
|
|
66
|
+
>
|
|
67
|
+
<div>
|
|
68
|
+
<pre>{note}</pre>
|
|
69
|
+
</div>
|
|
70
|
+
<FormItem noStyle name={'item' + i}>
|
|
71
|
+
<InputAutoFocus
|
|
72
|
+
type={type}
|
|
73
|
+
placeholder={note}
|
|
74
|
+
/>
|
|
75
|
+
</FormItem>
|
|
76
|
+
</FormItem>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
function renderConfirmBody () {
|
|
80
|
+
const instructions = opts.options.instructions || []
|
|
81
|
+
return (
|
|
82
|
+
<div>
|
|
83
|
+
{
|
|
84
|
+
instructions.map((note, index) => {
|
|
85
|
+
return <pre key={note + index}>{note}</pre>
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
<FormItem>
|
|
89
|
+
<Button
|
|
90
|
+
type='primary'
|
|
91
|
+
onClick={onConfirm}
|
|
92
|
+
>
|
|
93
|
+
{opts.options.submitText || e('submit')}
|
|
94
|
+
</Button>
|
|
95
|
+
<Button
|
|
96
|
+
className='mg1l'
|
|
97
|
+
onClick={onCancel}
|
|
98
|
+
>
|
|
99
|
+
{opts.options.cancelText || e('cancel')}
|
|
100
|
+
</Button>
|
|
101
|
+
</FormItem>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
const props = {
|
|
106
|
+
maskClosable: false,
|
|
107
|
+
okText: e('submit'),
|
|
108
|
+
onCancel,
|
|
109
|
+
onOk,
|
|
110
|
+
closable: false,
|
|
111
|
+
open: true,
|
|
112
|
+
title: opts.options?.name || '?',
|
|
113
|
+
footer: null
|
|
114
|
+
}
|
|
115
|
+
return (
|
|
116
|
+
<Modal
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
{
|
|
120
|
+
opts.options?.mode === 'confirm'
|
|
121
|
+
? renderConfirmBody()
|
|
122
|
+
: (
|
|
123
|
+
<Form
|
|
124
|
+
form={form}
|
|
125
|
+
layout='vertical'
|
|
126
|
+
onFinish={onFinish}
|
|
127
|
+
>
|
|
128
|
+
{
|
|
129
|
+
opts.options.prompts.map(renderFormItem)
|
|
130
|
+
}
|
|
131
|
+
<FormItem>
|
|
132
|
+
<Button
|
|
133
|
+
type='primary'
|
|
134
|
+
htmlType='submit'
|
|
135
|
+
>
|
|
136
|
+
{e('submit')}
|
|
137
|
+
</Button>
|
|
138
|
+
<Button
|
|
139
|
+
type='dashed'
|
|
140
|
+
className='mg1l'
|
|
141
|
+
onClick={onIgnore}
|
|
142
|
+
>
|
|
143
|
+
{e('ignore')}
|
|
144
|
+
</Button>
|
|
145
|
+
<Button
|
|
146
|
+
className='mg1l'
|
|
147
|
+
onClick={onCancel}
|
|
148
|
+
>
|
|
149
|
+
{e('cancel')}
|
|
150
|
+
</Button>
|
|
151
|
+
</FormItem>
|
|
152
|
+
</Form>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
</Modal>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
@@ -1,30 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* handle terminal interactive operation
|
|
2
|
+
* handle terminal interactive operation - queue based
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { useEffect, useState } from 'react'
|
|
6
|
-
import { Form, Button } from 'antd'
|
|
7
|
-
import Modal from '../common/modal'
|
|
8
|
-
import InputAutoFocus from '../common/input-auto-focus'
|
|
5
|
+
import { useEffect, useState, useRef, useCallback } from 'react'
|
|
9
6
|
import wait from '../../common/wait'
|
|
10
|
-
|
|
11
|
-
const e = window.translate
|
|
12
|
-
const FormItem = Form.Item
|
|
7
|
+
import TermInteractiveUI from './terminal-interactive-ui'
|
|
13
8
|
|
|
14
9
|
export default function TermInteractive () {
|
|
15
|
-
const [
|
|
16
|
-
const
|
|
10
|
+
const [current, setCurrent] = useState(null)
|
|
11
|
+
const queueRef = useRef([])
|
|
12
|
+
const hasCurrentRef = useRef(false)
|
|
13
|
+
|
|
17
14
|
function updateTab (data) {
|
|
18
15
|
window.store.updateTab(data.tabId, data.update)
|
|
19
16
|
}
|
|
20
|
-
|
|
17
|
+
|
|
18
|
+
function processNext () {
|
|
19
|
+
const next = queueRef.current.shift()
|
|
20
|
+
if (next) {
|
|
21
|
+
setCurrent(next)
|
|
22
|
+
} else {
|
|
23
|
+
hasCurrentRef.current = false
|
|
24
|
+
setCurrent(null)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const onMsgRef = useRef(null)
|
|
29
|
+
onMsgRef.current = function onMsg (e) {
|
|
21
30
|
if (
|
|
22
31
|
e &&
|
|
23
32
|
e.data &&
|
|
24
33
|
typeof e.data === 'string' &&
|
|
25
34
|
e.data.includes('session-interactive')
|
|
26
35
|
) {
|
|
27
|
-
|
|
36
|
+
const parsed = JSON.parse(e.data)
|
|
37
|
+
if (hasCurrentRef.current) {
|
|
38
|
+
queueRef.current.push(parsed)
|
|
39
|
+
} else {
|
|
40
|
+
hasCurrentRef.current = true
|
|
41
|
+
setCurrent(parsed)
|
|
42
|
+
}
|
|
28
43
|
} else if (
|
|
29
44
|
e &&
|
|
30
45
|
e.data &&
|
|
@@ -34,166 +49,52 @@ export default function TermInteractive () {
|
|
|
34
49
|
updateTab(JSON.parse(e.data))
|
|
35
50
|
}
|
|
36
51
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
function onCancel () {
|
|
42
|
-
window.et.commonWs.s({
|
|
43
|
-
id: opts.id,
|
|
44
|
-
results: []
|
|
45
|
-
})
|
|
46
|
-
clear()
|
|
47
|
-
}
|
|
48
|
-
function onOk () {
|
|
49
|
-
form.submit()
|
|
50
|
-
}
|
|
51
|
-
function onConfirm () {
|
|
52
|
-
window.et.commonWs.s({
|
|
53
|
-
id: opts.id,
|
|
54
|
-
results: [opts.options.confirmResult || 'yes']
|
|
55
|
-
})
|
|
56
|
-
clear()
|
|
57
|
-
}
|
|
58
|
-
function onIgnore () {
|
|
59
|
-
window.et.commonWs.s({
|
|
60
|
-
id: opts.id,
|
|
61
|
-
results: Object.keys(opts.options.prompts).map(() => '')
|
|
62
|
-
})
|
|
63
|
-
clear()
|
|
64
|
-
}
|
|
65
|
-
function onFinish (res) {
|
|
66
|
-
window.et.commonWs.s({
|
|
67
|
-
id: opts.id,
|
|
68
|
-
results: Object.values(res)
|
|
69
|
-
})
|
|
70
|
-
clear()
|
|
71
|
-
}
|
|
72
|
-
function renderFormItem (pro, i) {
|
|
73
|
-
const {
|
|
74
|
-
prompt,
|
|
75
|
-
echo
|
|
76
|
-
} = pro
|
|
77
|
-
const note = (opts.options.instructions || [])[i]
|
|
78
|
-
const type = echo
|
|
79
|
-
? 'input'
|
|
80
|
-
: 'password'
|
|
81
|
-
return (
|
|
82
|
-
<FormItem
|
|
83
|
-
key={prompt + i}
|
|
84
|
-
label={prompt}
|
|
85
|
-
rules={[{
|
|
86
|
-
required: true, message: 'required'
|
|
87
|
-
}]}
|
|
88
|
-
>
|
|
89
|
-
<div>
|
|
90
|
-
<pre>{note}</pre>
|
|
91
|
-
</div>
|
|
92
|
-
<FormItem noStyle name={'item' + i}>
|
|
93
|
-
<InputAutoFocus
|
|
94
|
-
type={type}
|
|
95
|
-
placeholder={note}
|
|
96
|
-
/>
|
|
97
|
-
</FormItem>
|
|
98
|
-
</FormItem>
|
|
99
|
-
)
|
|
52
|
+
|
|
53
|
+
function onSend (data) {
|
|
54
|
+
window.et.commonWs.s(data)
|
|
100
55
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
56
|
+
|
|
57
|
+
const onClose = useCallback(() => {
|
|
58
|
+
processNext()
|
|
59
|
+
}, [])
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
let cancelled = false
|
|
63
|
+
function handler (e) {
|
|
64
|
+
if (!cancelled) {
|
|
65
|
+
onMsgRef.current(e)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function initWatch () {
|
|
69
|
+
for (;;) {
|
|
70
|
+
if (cancelled) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
if (window.et.commonWs) {
|
|
74
|
+
window.et.commonWs.addEventListener('message', handler)
|
|
75
|
+
return
|
|
109
76
|
}
|
|
110
|
-
<FormItem>
|
|
111
|
-
<Button
|
|
112
|
-
type='primary'
|
|
113
|
-
onClick={onConfirm}
|
|
114
|
-
>
|
|
115
|
-
{opts.options.submitText || e('submit')}
|
|
116
|
-
</Button>
|
|
117
|
-
<Button
|
|
118
|
-
className='mg1l'
|
|
119
|
-
onClick={onCancel}
|
|
120
|
-
>
|
|
121
|
-
{opts.options.cancelText || e('cancel')}
|
|
122
|
-
</Button>
|
|
123
|
-
</FormItem>
|
|
124
|
-
</div>
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
async function initWatch () {
|
|
128
|
-
let done = false
|
|
129
|
-
while (!done) {
|
|
130
|
-
if (window.et.commonWs) {
|
|
131
|
-
window.et.commonWs.addEventListener('message', onMsg)
|
|
132
|
-
done = true
|
|
133
|
-
} else {
|
|
134
77
|
await wait(400)
|
|
135
78
|
}
|
|
136
79
|
}
|
|
137
|
-
}
|
|
138
|
-
function init () {
|
|
139
80
|
initWatch()
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
81
|
+
return () => {
|
|
82
|
+
cancelled = true
|
|
83
|
+
if (window.et.commonWs) {
|
|
84
|
+
window.et.commonWs.removeEventListener('message', handler)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
143
87
|
}, [])
|
|
144
|
-
|
|
88
|
+
|
|
89
|
+
if (!current) {
|
|
145
90
|
return null
|
|
146
91
|
}
|
|
147
|
-
|
|
148
|
-
maskClosable: false,
|
|
149
|
-
okText: e('submit'),
|
|
150
|
-
onCancel,
|
|
151
|
-
onOk,
|
|
152
|
-
closable: false,
|
|
153
|
-
open: true,
|
|
154
|
-
title: opts.options?.name || '?',
|
|
155
|
-
footer: null
|
|
156
|
-
}
|
|
92
|
+
|
|
157
93
|
return (
|
|
158
|
-
<
|
|
159
|
-
{
|
|
160
|
-
|
|
161
|
-
{
|
|
162
|
-
|
|
163
|
-
? renderConfirmBody()
|
|
164
|
-
: (
|
|
165
|
-
<Form
|
|
166
|
-
form={form}
|
|
167
|
-
layout='vertical'
|
|
168
|
-
onFinish={onFinish}
|
|
169
|
-
>
|
|
170
|
-
{
|
|
171
|
-
opts.options.prompts.map(renderFormItem)
|
|
172
|
-
}
|
|
173
|
-
<FormItem>
|
|
174
|
-
<Button
|
|
175
|
-
type='primary'
|
|
176
|
-
htmlType='submit'
|
|
177
|
-
>
|
|
178
|
-
{e('submit')}
|
|
179
|
-
</Button>
|
|
180
|
-
<Button
|
|
181
|
-
type='dashed'
|
|
182
|
-
className='mg1l'
|
|
183
|
-
onClick={onIgnore}
|
|
184
|
-
>
|
|
185
|
-
{e('ignore')}
|
|
186
|
-
</Button>
|
|
187
|
-
<Button
|
|
188
|
-
className='mg1l'
|
|
189
|
-
onClick={onCancel}
|
|
190
|
-
>
|
|
191
|
-
{e('cancel')}
|
|
192
|
-
</Button>
|
|
193
|
-
</FormItem>
|
|
194
|
-
</Form>
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
</Modal>
|
|
94
|
+
<TermInteractiveUI
|
|
95
|
+
opts={current}
|
|
96
|
+
onSend={onSend}
|
|
97
|
+
onClose={onClose}
|
|
98
|
+
/>
|
|
198
99
|
)
|
|
199
100
|
}
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Widget form component
|
|
3
3
|
*/
|
|
4
4
|
import React, { useState, useEffect } from 'react'
|
|
5
|
-
import { Form, Input, InputNumber, Switch, Select, Button, Tooltip, Alert } from 'antd'
|
|
5
|
+
import { Form, Input, InputNumber, Switch, Select, Button, Tooltip, Alert, Space } from 'antd'
|
|
6
6
|
import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
|
|
7
7
|
import HelpIcon from '../common/help-icon'
|
|
8
|
+
import { nanoid } from 'nanoid'
|
|
8
9
|
import BatchOpEditor from '../batch-op/batch-op-editor'
|
|
9
10
|
|
|
10
11
|
export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInstance }) {
|
|
@@ -43,12 +44,36 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
const renderFormItem = (config) => {
|
|
46
|
-
const { name, type, description, choices } = config
|
|
47
|
+
const { name, type, description, choices, showGenerator } = config
|
|
47
48
|
let control = null
|
|
48
49
|
|
|
49
50
|
switch (type) {
|
|
50
51
|
case 'string':
|
|
51
52
|
control = <Input placeholder={description} />
|
|
53
|
+
if (showGenerator) {
|
|
54
|
+
return (
|
|
55
|
+
<Form.Item
|
|
56
|
+
key={name}
|
|
57
|
+
{...formItemLayout}
|
|
58
|
+
label={name}
|
|
59
|
+
tooltip={description}
|
|
60
|
+
>
|
|
61
|
+
<Space.Compact style={{ width: '100%' }}>
|
|
62
|
+
<Form.Item
|
|
63
|
+
noStyle
|
|
64
|
+
name={name}
|
|
65
|
+
>
|
|
66
|
+
<Input placeholder={description} />
|
|
67
|
+
</Form.Item>
|
|
68
|
+
<Button
|
|
69
|
+
onClick={() => form.setFieldValue(name, 'ett_' + nanoid())}
|
|
70
|
+
>
|
|
71
|
+
Generate
|
|
72
|
+
</Button>
|
|
73
|
+
</Space.Compact>
|
|
74
|
+
</Form.Item>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
52
77
|
break
|
|
53
78
|
case 'textarea':
|
|
54
79
|
control = <Input.TextArea autoSize={{ minRows: 3 }} placeholder={description} />
|
package/client/entry/worker.js
CHANGED
|
@@ -114,7 +114,10 @@ async function onMsg (e) {
|
|
|
114
114
|
} else if (action === 'addEventListener') {
|
|
115
115
|
const ws = self.insts[wsId]
|
|
116
116
|
if (ws) {
|
|
117
|
-
ws.
|
|
117
|
+
if (!ws.cbs) {
|
|
118
|
+
ws.cbs = {}
|
|
119
|
+
}
|
|
120
|
+
const cb = (e) => {
|
|
118
121
|
send({
|
|
119
122
|
wsId,
|
|
120
123
|
id,
|
|
@@ -123,13 +126,14 @@ async function onMsg (e) {
|
|
|
123
126
|
}
|
|
124
127
|
})
|
|
125
128
|
}
|
|
126
|
-
ws.
|
|
129
|
+
ws.cbs[id] = cb
|
|
130
|
+
ws.addEventListener(type, cb)
|
|
127
131
|
}
|
|
128
132
|
} else if (action === 'removeEventListener') {
|
|
129
133
|
const ws = self.insts[wsId]
|
|
130
|
-
if (ws) {
|
|
131
|
-
ws.removeEventListener(type, ws.
|
|
132
|
-
delete ws.
|
|
134
|
+
if (ws && ws.cbs && ws.cbs[id]) {
|
|
135
|
+
ws.removeEventListener(type, ws.cbs[id])
|
|
136
|
+
delete ws.cbs[id]
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
}
|