@electerm/electerm-react 3.15.159 → 3.15.160
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/components/ai/agent.js +3 -2
- package/client/components/ai/ai-chat-history-item.jsx +2 -1
- package/client/components/ai/ai-chat.jsx +3 -0
- package/client/components/ai/ai-config.jsx +10 -2
- package/client/components/ai/ai-guardrails.js +10 -0
- package/client/components/ai/ai-output.jsx +51 -5
- package/client/components/ai/ai.styl +13 -0
- package/client/components/bookmark-form/ai-bookmark-form.jsx +2 -1
- package/client/components/terminal/terminal-command-dropdown.jsx +2 -1
- package/client/store/common.js +4 -3
- package/client/store/init-state.js +2 -2
- package/client/store/store.js +18 -1
- package/package.json +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { agentTools, executeToolCall } from './agent-tools'
|
|
2
|
+
import { appendMandatoryGuardrails } from './ai-guardrails'
|
|
2
3
|
|
|
3
4
|
const MAX_ITERATIONS = 150
|
|
4
5
|
|
|
5
6
|
function buildAgentSystemPrompt (config) {
|
|
6
7
|
const lang = config.languageAI || window.store.getLangName()
|
|
7
8
|
const baseRole = config.roleAI || 'You are a helpful assistant.'
|
|
8
|
-
return `${baseRole}
|
|
9
|
+
return appendMandatoryGuardrails(`${baseRole}
|
|
9
10
|
|
|
10
11
|
You are operating inside electerm, a terminal/SSH client. You have access to tools that let you:
|
|
11
12
|
- Run commands in terminal tabs and read their output
|
|
@@ -21,7 +22,7 @@ Prefer using the active terminal unless the user specifies otherwise.
|
|
|
21
22
|
For SSH connections, prefer using open_tab to connect directly, or create a bookmark with add_bookmark and open it with open_bookmark if the user wants to save the connection.
|
|
22
23
|
For file transfers, use the sftp_upload and sftp_download tools. The tab must be an SSH/FTP connection with SFTP initialized.
|
|
23
24
|
|
|
24
|
-
Reply in ${lang} language.`
|
|
25
|
+
Reply in ${lang} language.`)
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
function updateChatEntry (chatEntry, updates) {
|
|
@@ -3,6 +3,7 @@ import AIOutput from './ai-output'
|
|
|
3
3
|
import AIStopIcon from './ai-stop-icon'
|
|
4
4
|
import AgentToolCallCard from './agent-tool-call-card'
|
|
5
5
|
import { runAgentLoop } from './agent'
|
|
6
|
+
import { appendMandatoryGuardrails } from './ai-guardrails'
|
|
6
7
|
import {
|
|
7
8
|
Alert,
|
|
8
9
|
Tooltip
|
|
@@ -42,7 +43,7 @@ export default function AIChatHistoryItem ({ item }) {
|
|
|
42
43
|
|
|
43
44
|
function buildRole () {
|
|
44
45
|
const lang = languageAI || window.store.getLangName()
|
|
45
|
-
return roleAI + `;用[${lang}]回复`
|
|
46
|
+
return appendMandatoryGuardrails(roleAI + `;用[${lang}]回复`)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
const conversationMessages = useMemo(() => {
|
|
@@ -18,6 +18,7 @@ import Password from '../common/password'
|
|
|
18
18
|
import AiHistory, { addHistoryItem } from './ai-history'
|
|
19
19
|
import message from '../common/message'
|
|
20
20
|
import { getAIPresets } from './ai-config-props'
|
|
21
|
+
import { appendMandatoryGuardrails } from './ai-guardrails'
|
|
21
22
|
|
|
22
23
|
const STORAGE_KEY_CONFIG = 'ai_config_history'
|
|
23
24
|
const EVENT_NAME_CONFIG = 'ai-config-history-update'
|
|
@@ -74,7 +75,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
74
75
|
'AIchat',
|
|
75
76
|
'Hi',
|
|
76
77
|
values.modelAI,
|
|
77
|
-
values.roleAI,
|
|
78
|
+
appendMandatoryGuardrails(values.roleAI),
|
|
78
79
|
values.baseURLAI,
|
|
79
80
|
values.apiPathAI,
|
|
80
81
|
values.apiKeyAI,
|
|
@@ -164,7 +165,14 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
164
165
|
<Link to={aiConfigWikiLink}>WIKI: {aiConfigWikiLink}</Link>
|
|
165
166
|
}
|
|
166
167
|
type='info'
|
|
167
|
-
className='
|
|
168
|
+
className='mg2t mg1b'
|
|
169
|
+
/>
|
|
170
|
+
<Alert
|
|
171
|
+
title={
|
|
172
|
+
window.translate('aiWarn')
|
|
173
|
+
}
|
|
174
|
+
type='warning'
|
|
175
|
+
className='mg2b'
|
|
168
176
|
/>
|
|
169
177
|
<div className='mg1b alignright'>
|
|
170
178
|
{renderPresetMenu()}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Append mandatory guardrails (if configured via window.et.mandatoryGuardrails)
|
|
2
|
+
// to the AI system role as an extra layer of protection. This is typically set
|
|
3
|
+
// by enterprise deployments to enforce policies on every AI request.
|
|
4
|
+
export function appendMandatoryGuardrails (systemContent) {
|
|
5
|
+
const guardrails = window.et && window.et.mandatoryGuardrails
|
|
6
|
+
if (!guardrails) {
|
|
7
|
+
return systemContent
|
|
8
|
+
}
|
|
9
|
+
return `${systemContent}\n\n${guardrails}`
|
|
10
|
+
}
|
|
@@ -2,12 +2,19 @@ import { useRef, useEffect } from 'react'
|
|
|
2
2
|
import ReactMarkdown from 'react-markdown'
|
|
3
3
|
import { copy } from '../../common/clipboard'
|
|
4
4
|
import Link from '../common/external-link'
|
|
5
|
-
import { Tag } from 'antd'
|
|
6
|
-
import {
|
|
5
|
+
import { Tag, Popconfirm, Button, Alert } from 'antd'
|
|
6
|
+
import {
|
|
7
|
+
CopyOutlined,
|
|
8
|
+
PlayCircleOutlined,
|
|
9
|
+
FlagOutlined,
|
|
10
|
+
FlagFilled
|
|
11
|
+
} from '@ant-design/icons'
|
|
7
12
|
import getBrand from './get-brand'
|
|
8
13
|
|
|
9
14
|
const e = window.translate
|
|
10
15
|
|
|
16
|
+
const enableAIFlag = !!(window.et && window.et.enableAIFlag)
|
|
17
|
+
|
|
11
18
|
export default function AIOutput ({ item }) {
|
|
12
19
|
const outputRef = useRef(null)
|
|
13
20
|
const {
|
|
@@ -23,7 +30,7 @@ export default function AIOutput ({ item }) {
|
|
|
23
30
|
}
|
|
24
31
|
}, [response])
|
|
25
32
|
|
|
26
|
-
if (!response) {
|
|
33
|
+
if (!response && !(item.flagged && enableAIFlag)) {
|
|
27
34
|
return null
|
|
28
35
|
}
|
|
29
36
|
|
|
@@ -89,6 +96,38 @@ export default function AIOutput ({ item }) {
|
|
|
89
96
|
)
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
function handleToggleFlag () {
|
|
100
|
+
const index = window.store.aiChatHistory.findIndex(i => i.id === item.id)
|
|
101
|
+
if (index === -1) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
window.store.aiChatHistory[index].flagged = !window.store.aiChatHistory[index].flagged
|
|
105
|
+
window.store.aiChatHistory = [...window.store.aiChatHistory]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function renderFlag () {
|
|
109
|
+
if (!enableAIFlag) {
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
return (
|
|
113
|
+
<div className={'ai-stream-output-flag' + (item.flagged ? ' is-flagged' : '')}>
|
|
114
|
+
<Popconfirm
|
|
115
|
+
title={item.flagged ? 'Remove the harmful-info flag?' : 'Flag this as harmful info?'}
|
|
116
|
+
okText='Confirm'
|
|
117
|
+
cancelText='Cancel'
|
|
118
|
+
onConfirm={handleToggleFlag}
|
|
119
|
+
>
|
|
120
|
+
<Button
|
|
121
|
+
size='small'
|
|
122
|
+
type='text'
|
|
123
|
+
icon={item.flagged ? <FlagFilled /> : <FlagOutlined />}
|
|
124
|
+
onClick={(evt) => evt.stopPropagation()}
|
|
125
|
+
/>
|
|
126
|
+
</Popconfirm>
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
92
131
|
function renderBrand () {
|
|
93
132
|
if (!brand) {
|
|
94
133
|
return null
|
|
@@ -113,9 +152,16 @@ export default function AIOutput ({ item }) {
|
|
|
113
152
|
|
|
114
153
|
return (
|
|
115
154
|
<div className='ai-stream-output' ref={outputRef}>
|
|
155
|
+
{renderFlag()}
|
|
116
156
|
<div className='pd1'>
|
|
117
|
-
{
|
|
118
|
-
|
|
157
|
+
{item.flagged && enableAIFlag
|
|
158
|
+
? <Alert type='warning' message='user flagged as harmful info' />
|
|
159
|
+
: (
|
|
160
|
+
<>
|
|
161
|
+
{renderBrand()}
|
|
162
|
+
<ReactMarkdown {...mdProps} />
|
|
163
|
+
</>
|
|
164
|
+
)}
|
|
119
165
|
</div>
|
|
120
166
|
</div>
|
|
121
167
|
)
|
|
@@ -33,8 +33,17 @@
|
|
|
33
33
|
// display block
|
|
34
34
|
|
|
35
35
|
.ai-stream-output
|
|
36
|
+
position relative
|
|
36
37
|
max-height 400px
|
|
37
38
|
overflow-y auto
|
|
39
|
+
&::-webkit-scrollbar-thumb
|
|
40
|
+
background var(--main-dark)
|
|
41
|
+
.ai-stream-output-flag
|
|
42
|
+
position absolute
|
|
43
|
+
top 13px
|
|
44
|
+
right 10px
|
|
45
|
+
z-index 10
|
|
46
|
+
|
|
38
47
|
|
|
39
48
|
.ai-chat-input
|
|
40
49
|
position relative
|
|
@@ -52,6 +61,9 @@
|
|
|
52
61
|
z-index 1
|
|
53
62
|
font-size 16px
|
|
54
63
|
|
|
64
|
+
.ai-disclamer
|
|
65
|
+
font-size 12px
|
|
66
|
+
|
|
55
67
|
.code-block
|
|
56
68
|
width 100%
|
|
57
69
|
pre
|
|
@@ -69,6 +81,7 @@
|
|
|
69
81
|
|
|
70
82
|
.ai-chat-sessions-wrap
|
|
71
83
|
width 100%
|
|
84
|
+
height 100%
|
|
72
85
|
overflow-y auto
|
|
73
86
|
overflow-x hidden
|
|
74
87
|
padding 4px 0
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import SimpleEditor from '../text-editor/simple-editor'
|
|
19
19
|
import { copy } from '../../common/clipboard'
|
|
20
20
|
import download from '../../common/download'
|
|
21
|
+
import { appendMandatoryGuardrails } from '../ai/ai-guardrails'
|
|
21
22
|
import AICategorySelect from './common/ai-category-select.jsx'
|
|
22
23
|
import HelpIcon from '../common/help-icon'
|
|
23
24
|
import Modal from '../common/modal.jsx'
|
|
@@ -77,7 +78,7 @@ export default function AIBookmarkForm (props) {
|
|
|
77
78
|
'AIchat',
|
|
78
79
|
prompt,
|
|
79
80
|
config.modelAI,
|
|
80
|
-
'You are a helpful assistant that generates bookmark configurations in JSON format.',
|
|
81
|
+
appendMandatoryGuardrails('You are a helpful assistant that generates bookmark configurations in JSON format.'),
|
|
81
82
|
config.baseURLAI,
|
|
82
83
|
config.apiPathAI,
|
|
83
84
|
config.apiKeyAI,
|
|
@@ -2,6 +2,7 @@ import { Component } from 'manate/react/class-components'
|
|
|
2
2
|
import { refsStatic, refs } from '../common/ref'
|
|
3
3
|
import SuggestionItem from './cmd-item'
|
|
4
4
|
import { aiSuggestionsCache } from '../../common/cache'
|
|
5
|
+
import { appendMandatoryGuardrails } from '../ai/ai-guardrails'
|
|
5
6
|
import classnames from 'classnames'
|
|
6
7
|
import {
|
|
7
8
|
LoadingOutlined
|
|
@@ -67,7 +68,7 @@ export default class TerminalCmdSuggestions extends Component {
|
|
|
67
68
|
'AIchat',
|
|
68
69
|
prompt,
|
|
69
70
|
config.modelAI,
|
|
70
|
-
config.roleAI,
|
|
71
|
+
appendMandatoryGuardrails(config.roleAI),
|
|
71
72
|
config.baseURLAI,
|
|
72
73
|
config.apiPathAI,
|
|
73
74
|
config.apiKeyAI,
|
package/client/store/common.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import handleError from '../common/error-handler'
|
|
6
6
|
import Modal from '../components/common/modal'
|
|
7
|
+
import { appendMandatoryGuardrails } from '../components/ai/ai-guardrails'
|
|
7
8
|
import { debounce, some, get, pickBy } from 'lodash-es'
|
|
8
9
|
import {
|
|
9
10
|
leftSidebarWidthKey,
|
|
@@ -106,7 +107,7 @@ export default Store => {
|
|
|
106
107
|
|
|
107
108
|
Store.prototype.setLeftSidePanelWidth = function (v) {
|
|
108
109
|
ls.setItem(leftSidebarWidthKey, v)
|
|
109
|
-
window.store.
|
|
110
|
+
window.store._leftSidebarWidth = v
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
Store.prototype.setAddPanelWidth = function (v) {
|
|
@@ -116,7 +117,7 @@ export default Store => {
|
|
|
116
117
|
|
|
117
118
|
Store.prototype.setRightSidePanelWidth = function (v) {
|
|
118
119
|
ls.setItem(rightSidebarWidthKey, v)
|
|
119
|
-
window.store.
|
|
120
|
+
window.store._rightPanelWidth = v
|
|
120
121
|
}
|
|
121
122
|
Store.prototype.dismissDelKeyTip = function (v) {
|
|
122
123
|
ls.setItem(dismissDelKeyTipLsKey, 'y')
|
|
@@ -322,7 +323,7 @@ export default Store => {
|
|
|
322
323
|
const firstEntry = sessionEntries[0]
|
|
323
324
|
const lang = firstEntry.languageAI || store.getLangName()
|
|
324
325
|
const messages = [
|
|
325
|
-
{ role: 'system', content: firstEntry.roleAI + `;用[${lang}]回复` }
|
|
326
|
+
{ role: 'system', content: appendMandatoryGuardrails(firstEntry.roleAI + `;用[${lang}]回复`) }
|
|
326
327
|
]
|
|
327
328
|
|
|
328
329
|
// Start from the last compress entry to include its summary as context
|
|
@@ -123,7 +123,7 @@ export default () => {
|
|
|
123
123
|
rightPanelVisible: false,
|
|
124
124
|
rightPanelTab: 'info',
|
|
125
125
|
rightPanelPinned: false,
|
|
126
|
-
|
|
126
|
+
_rightPanelWidth: parseInt(ls.getItem(rightSidebarWidthKey), 10) || 500,
|
|
127
127
|
showAIConfigModal: false,
|
|
128
128
|
|
|
129
129
|
// for settings related
|
|
@@ -164,7 +164,7 @@ export default () => {
|
|
|
164
164
|
|
|
165
165
|
// sidebar
|
|
166
166
|
openedSideBar: ls.getItem(openedSidebarKey) || '',
|
|
167
|
-
|
|
167
|
+
_leftSidebarWidth: parseInt(ls.getItem(leftSidebarWidthKey), 10) || 300,
|
|
168
168
|
addPanelWidth: parseInt(ls.getItem(addPanelWidthLsKey), 10) || 300,
|
|
169
169
|
menuOpened: false,
|
|
170
170
|
pinned: ls.getItem(sidebarPinnedKey) === 'true',
|
package/client/store/store.js
CHANGED
|
@@ -37,7 +37,8 @@ import {
|
|
|
37
37
|
terminalSshConfigType,
|
|
38
38
|
paneMap,
|
|
39
39
|
staticNewItemTabs,
|
|
40
|
-
isWin
|
|
40
|
+
isWin,
|
|
41
|
+
sidebarWidth
|
|
41
42
|
} from '../common/constants'
|
|
42
43
|
import getInitItem from '../common/init-setting-item'
|
|
43
44
|
import createTitle from '../common/create-title'
|
|
@@ -58,6 +59,22 @@ class Store {
|
|
|
58
59
|
return window.store.innerWidth
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
get leftSidebarWidth () {
|
|
63
|
+
const { store } = window
|
|
64
|
+
if (store.isMobile) {
|
|
65
|
+
return Math.min(store.innerWidth - sidebarWidth, 500)
|
|
66
|
+
}
|
|
67
|
+
return store._leftSidebarWidth
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get rightPanelWidth () {
|
|
71
|
+
const { store } = window
|
|
72
|
+
if (store.isMobile) {
|
|
73
|
+
return Math.min(store.innerWidth, 500)
|
|
74
|
+
}
|
|
75
|
+
return store._rightPanelWidth
|
|
76
|
+
}
|
|
77
|
+
|
|
61
78
|
get config () {
|
|
62
79
|
return deepCopy(window.store._config)
|
|
63
80
|
}
|