@electerm/electerm-react 3.8.6 → 3.8.15
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/parse-quick-connect.js +9 -1
- package/client/components/ai/ai-config-modal.jsx +52 -0
- package/client/components/ai/ai-config.jsx +5 -38
- package/client/components/ai/get-brand.js +0 -11
- package/client/components/bg/custom-css.jsx +2 -1
- package/client/components/main/main.jsx +2 -0
- package/client/components/setting-panel/setting-modal.jsx +1 -0
- package/client/components/sftp/file-info-modal.jsx +50 -0
- package/client/components/tree-list/bookmark-toolbar.jsx +4 -66
- package/client/components/tree-list/bookmark-upload.js +106 -0
- package/client/store/common.js +2 -10
- package/client/store/init-state.js +1 -0
- package/package.json +1 -1
- package/client/components/ai/providers.js +0 -14
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
|
|
22
22
|
const SUPPORTED_PROTOCOLS = ['ssh', 'telnet', 'vnc', 'rdp', 'spice', 'serial', 'ftp', 'http', 'https', 'electerm']
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Deny list for opts keys - these are parsed from the URL itself
|
|
26
|
+
* and should not be overridable via the opts JSON parameter for safety
|
|
27
|
+
*/
|
|
28
|
+
const OPTS_DENY_LIST = ['type', 'host']
|
|
29
|
+
|
|
24
30
|
/**
|
|
25
31
|
* Default ports for each protocol
|
|
26
32
|
*/
|
|
@@ -393,6 +399,7 @@ function parseQuickConnect (str) {
|
|
|
393
399
|
if (optsStr) {
|
|
394
400
|
try {
|
|
395
401
|
const extraOpts = JSON.parse(optsStr)
|
|
402
|
+
OPTS_DENY_LIST.forEach(key => delete extraOpts[key])
|
|
396
403
|
Object.assign(opts, extraOpts)
|
|
397
404
|
} catch (err) {
|
|
398
405
|
console.error('Failed to parse opts:', err)
|
|
@@ -439,5 +446,6 @@ export {
|
|
|
439
446
|
getDefaultPort,
|
|
440
447
|
getSupportedProtocols,
|
|
441
448
|
SUPPORTED_PROTOCOLS,
|
|
442
|
-
DEFAULT_PORTS
|
|
449
|
+
DEFAULT_PORTS,
|
|
450
|
+
OPTS_DENY_LIST
|
|
443
451
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import Modal from '../common/modal'
|
|
2
|
+
import { auto } from 'manate/react'
|
|
3
|
+
import AIConfigForm from './ai-config'
|
|
4
|
+
import message from '../common/message'
|
|
5
|
+
import { aiConfigsArr } from './ai-config-props'
|
|
6
|
+
import { pick } from 'lodash-es'
|
|
7
|
+
|
|
8
|
+
const e = window.translate
|
|
9
|
+
|
|
10
|
+
export default auto(function AIConfigModal ({ store }) {
|
|
11
|
+
const { showAIConfigModal } = store
|
|
12
|
+
|
|
13
|
+
if (!showAIConfigModal) {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getInitialValues () {
|
|
18
|
+
const res = pick(store.config, aiConfigsArr)
|
|
19
|
+
if (!res.languageAI) {
|
|
20
|
+
res.languageAI = window.store.getLangName()
|
|
21
|
+
}
|
|
22
|
+
return res
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function handleSubmit (values) {
|
|
26
|
+
window.store.updateConfig(values)
|
|
27
|
+
message.success(e('saved') || 'Saved')
|
|
28
|
+
window.store.showAIConfigModal = false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function handleClose () {
|
|
32
|
+
window.store.showAIConfigModal = false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Modal
|
|
37
|
+
open={showAIConfigModal}
|
|
38
|
+
onCancel={handleClose}
|
|
39
|
+
footer={null}
|
|
40
|
+
title='AI Config'
|
|
41
|
+
width='80%'
|
|
42
|
+
destroyOnClose
|
|
43
|
+
className='ai-config-modal'
|
|
44
|
+
>
|
|
45
|
+
<AIConfigForm
|
|
46
|
+
initialValues={getInitialValues()}
|
|
47
|
+
onSubmit={handleSubmit}
|
|
48
|
+
showAIConfig
|
|
49
|
+
/>
|
|
50
|
+
</Modal>
|
|
51
|
+
)
|
|
52
|
+
})
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
Alert,
|
|
7
7
|
Space
|
|
8
8
|
} from 'antd'
|
|
9
|
-
import { useEffect
|
|
9
|
+
import { useEffect } from 'react'
|
|
10
10
|
import Link from '../common/external-link'
|
|
11
11
|
import AiCache from './ai-cache'
|
|
12
12
|
import {
|
|
@@ -15,9 +15,6 @@ import {
|
|
|
15
15
|
import Password from '../common/password'
|
|
16
16
|
import AiHistory, { addHistoryItem } from './ai-history'
|
|
17
17
|
|
|
18
|
-
// Comprehensive API provider configurations
|
|
19
|
-
import providers from './providers'
|
|
20
|
-
|
|
21
18
|
const STORAGE_KEY_CONFIG = 'ai_config_history'
|
|
22
19
|
const EVENT_NAME_CONFIG = 'ai-config-history-update'
|
|
23
20
|
|
|
@@ -39,7 +36,6 @@ const proxyOptions = [
|
|
|
39
36
|
|
|
40
37
|
export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig }) {
|
|
41
38
|
const [form] = Form.useForm()
|
|
42
|
-
const [modelOptions, setModelOptions] = useState([])
|
|
43
39
|
|
|
44
40
|
useEffect(() => {
|
|
45
41
|
if (initialValues) {
|
|
@@ -51,23 +47,6 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
51
47
|
return true
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
const getBaseURLOptions = () => {
|
|
55
|
-
return providers.map(provider => ({
|
|
56
|
-
value: provider.baseURL,
|
|
57
|
-
label: provider.label
|
|
58
|
-
}))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const getModelOptions = (baseURL) => {
|
|
62
|
-
const provider = providers.find(p => p.baseURL === baseURL)
|
|
63
|
-
if (!provider) return []
|
|
64
|
-
|
|
65
|
-
return provider.models.map(model => ({
|
|
66
|
-
value: model,
|
|
67
|
-
label: model
|
|
68
|
-
}))
|
|
69
|
-
}
|
|
70
|
-
|
|
71
50
|
const handleSubmit = async (values) => {
|
|
72
51
|
onSubmit(values)
|
|
73
52
|
addHistoryItem(STORAGE_KEY_CONFIG, values, EVENT_NAME_CONFIG)
|
|
@@ -88,11 +67,6 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
88
67
|
return { label, title }
|
|
89
68
|
}
|
|
90
69
|
|
|
91
|
-
function handleChange (v) {
|
|
92
|
-
const options = getModelOptions(v)
|
|
93
|
-
setModelOptions(options)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
70
|
if (!showAIConfig) {
|
|
97
71
|
return null
|
|
98
72
|
}
|
|
@@ -127,12 +101,8 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
127
101
|
{ type: 'url', message: 'Please enter a valid URL!' }
|
|
128
102
|
]}
|
|
129
103
|
>
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
placeholder='Enter or select API provider URL'
|
|
133
|
-
filterOption={filter}
|
|
134
|
-
onChange={handleChange}
|
|
135
|
-
allowClear
|
|
104
|
+
<Input
|
|
105
|
+
placeholder='Enter API provider URL'
|
|
136
106
|
style={{ width: '75%' }}
|
|
137
107
|
/>
|
|
138
108
|
</Form.Item>
|
|
@@ -156,10 +126,8 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
156
126
|
name='modelAI'
|
|
157
127
|
rules={[{ required: true, message: 'Please input or select a model!' }]}
|
|
158
128
|
>
|
|
159
|
-
<
|
|
160
|
-
options={modelOptions}
|
|
129
|
+
<Input
|
|
161
130
|
placeholder='Enter or select AI model'
|
|
162
|
-
filterOption={filter}
|
|
163
131
|
/>
|
|
164
132
|
</Form.Item>
|
|
165
133
|
|
|
@@ -202,11 +170,10 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
202
170
|
>
|
|
203
171
|
<AutoComplete
|
|
204
172
|
options={proxyOptions}
|
|
205
|
-
placeholder='Enter proxy URL (optional)'
|
|
206
173
|
filterOption={filter}
|
|
207
174
|
allowClear
|
|
208
175
|
>
|
|
209
|
-
<Input />
|
|
176
|
+
<Input placeholder='Enter proxy URL (optional)' />
|
|
210
177
|
</AutoComplete>
|
|
211
178
|
</Form.Item>
|
|
212
179
|
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
import providers from './providers'
|
|
2
|
-
|
|
3
1
|
export default function getBrand (baseURLAI) {
|
|
4
|
-
// First, try to match with providers
|
|
5
|
-
const provider = providers.find(p => p.baseURL === baseURLAI)
|
|
6
|
-
if (provider) {
|
|
7
|
-
return {
|
|
8
|
-
brand: provider.label,
|
|
9
|
-
brandUrl: provider.homepage
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
2
|
// If no match, extract brand from URL
|
|
14
3
|
try {
|
|
15
4
|
const url = new URL(baseURLAI)
|
|
@@ -13,7 +13,8 @@ export default function CustomCss (props) {
|
|
|
13
13
|
if (configLoaded) {
|
|
14
14
|
const style = document.getElementById(themeDomId)
|
|
15
15
|
if (style) {
|
|
16
|
-
|
|
16
|
+
const safeCss = (customCss || '').replace(/@import/gi, '#')
|
|
17
|
+
style.innerHTML = safeCss
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
}, [customCss, configLoaded])
|
|
@@ -28,6 +28,7 @@ import ConnectionHoppingWarning from './connection-hopping-warnning'
|
|
|
28
28
|
import SshConfigLoadNotify from '../ssh-config/ssh-config-load-notify'
|
|
29
29
|
import LoadSshConfigs from '../ssh-config/load-ssh-configs'
|
|
30
30
|
import AIChat from '../ai/ai-chat'
|
|
31
|
+
import AIConfigModal from '../ai/ai-config-modal'
|
|
31
32
|
import Opacity from '../common/opacity'
|
|
32
33
|
import MoveItemModal from '../tree-list/move-item-modal'
|
|
33
34
|
import InputContextMenu from '../common/input-context-menu'
|
|
@@ -295,6 +296,7 @@ export default auto(function Index (props) {
|
|
|
295
296
|
<BookmarkFromHistoryModal />
|
|
296
297
|
<NotificationContainer />
|
|
297
298
|
<BatchOpRunner />
|
|
299
|
+
<AIConfigModal store={store} />
|
|
298
300
|
</div>
|
|
299
301
|
</ConfigProvider>
|
|
300
302
|
)
|
|
@@ -46,6 +46,11 @@ export default class FileMode extends React.PureComponent {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
showFileInfoModal (data) {
|
|
49
|
+
if (data.pid !== this.remoteExecPid) {
|
|
50
|
+
this.remoteExecPid = data.pid
|
|
51
|
+
this.remoteExecPlatform = null
|
|
52
|
+
this.remoteExecPlatformPromise = null
|
|
53
|
+
}
|
|
49
54
|
this.setStateProxy({
|
|
50
55
|
...data,
|
|
51
56
|
size: 0,
|
|
@@ -115,6 +120,47 @@ export default class FileMode extends React.PureComponent {
|
|
|
115
120
|
}
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
escapePowerShellPath = (value) => {
|
|
124
|
+
return String(value).replace(/'/g, "''")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
normalizeRemoteWindowsPath = (value) => {
|
|
128
|
+
return String(value).replace(/^\/([a-zA-Z]:)/, '$1')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getRemoteExecPlatform = async () => {
|
|
132
|
+
if (this.remoteExecPid === this.state.pid && this.remoteExecPlatform) {
|
|
133
|
+
return this.remoteExecPlatform
|
|
134
|
+
}
|
|
135
|
+
if (this.remoteExecPid !== this.state.pid) {
|
|
136
|
+
this.remoteExecPid = this.state.pid
|
|
137
|
+
this.remoteExecPlatform = null
|
|
138
|
+
this.remoteExecPlatformPromise = null
|
|
139
|
+
}
|
|
140
|
+
if (!this.remoteExecPlatformPromise) {
|
|
141
|
+
this.remoteExecPlatformPromise = runCmd(this.state.pid, 'cmd.exe /d /s /c ver')
|
|
142
|
+
.then(output => {
|
|
143
|
+
return String(output).toLowerCase().includes('windows')
|
|
144
|
+
? 'windows'
|
|
145
|
+
: 'posix'
|
|
146
|
+
})
|
|
147
|
+
.catch(() => 'posix')
|
|
148
|
+
.then(platform => {
|
|
149
|
+
this.remoteExecPlatform = platform
|
|
150
|
+
return platform
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
return this.remoteExecPlatformPromise
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
calcRemoteWin = async (folder) => {
|
|
157
|
+
const winFolder = this.normalizeRemoteWindowsPath(folder)
|
|
158
|
+
const escapedFolder = this.escapePowerShellPath(winFolder)
|
|
159
|
+
const cmd = `powershell.exe -NoLogo -NonInteractive -NoProfile -Command "$result = Get-ChildItem -LiteralPath '${escapedFolder}' -Recurse -File | Measure-Object -Property Length -Sum; if ($null -eq $result.Sum) { 0 } else { [int64]$result.Sum }"`
|
|
160
|
+
const result = await runCmd(this.state.pid, cmd).catch(window.store.onError)
|
|
161
|
+
return filesize(parseInt(String(result || '').trim(), 10) || 0)
|
|
162
|
+
}
|
|
163
|
+
|
|
118
164
|
calcLocal = async (folder) => {
|
|
119
165
|
const cmd = isWin
|
|
120
166
|
? `Get-ChildItem -Recurse '${folder}' | Measure-Object -Property Length -Sum`
|
|
@@ -125,6 +171,10 @@ export default class FileMode extends React.PureComponent {
|
|
|
125
171
|
}
|
|
126
172
|
|
|
127
173
|
calcRemote = async (folder) => {
|
|
174
|
+
const platform = await this.getRemoteExecPlatform()
|
|
175
|
+
if (platform === 'windows') {
|
|
176
|
+
return this.calcRemoteWin(folder)
|
|
177
|
+
}
|
|
128
178
|
const cmd = `du -sh '${folder}'`
|
|
129
179
|
const r = await runCmd(
|
|
130
180
|
this.state.pid,
|
|
@@ -10,12 +10,9 @@ import {
|
|
|
10
10
|
import { Button, Space, Dropdown } from 'antd'
|
|
11
11
|
import copy from 'json-deep-copy'
|
|
12
12
|
import time from '../../common/time'
|
|
13
|
-
import { uniq } from 'lodash-es'
|
|
14
|
-
import { fixBookmarks } from '../../common/db-fix'
|
|
15
13
|
import download from '../../common/download'
|
|
16
|
-
import delay from '../../common/wait'
|
|
17
|
-
import { action } from 'manate'
|
|
18
14
|
import Upload from '../common/upload'
|
|
15
|
+
import { beforeBookmarkUpload } from './bookmark-upload'
|
|
19
16
|
|
|
20
17
|
const e = window.translate
|
|
21
18
|
|
|
@@ -28,71 +25,12 @@ export default function BookmarkToolbar (props) {
|
|
|
28
25
|
bookmarkGroups,
|
|
29
26
|
bookmarks
|
|
30
27
|
} = props
|
|
31
|
-
const
|
|
32
|
-
const { store } = window
|
|
33
|
-
const filePath = file.filePath
|
|
34
|
-
const txt = await window.fs.readFile(filePath)
|
|
35
|
-
|
|
36
|
-
const content = JSON.parse(txt)
|
|
37
|
-
const {
|
|
38
|
-
bookmarkGroups: bookmarkGroups1,
|
|
39
|
-
bookmarks: bookmarks1
|
|
40
|
-
} = content
|
|
41
|
-
const bookmarkGroups0 = copy(bookmarkGroups)
|
|
42
|
-
const bookmarks0 = copy(bookmarks)
|
|
43
|
-
|
|
44
|
-
// Using Map instead of reduce
|
|
45
|
-
const bmTree = new Map(
|
|
46
|
-
bookmarks0.map(bookmark => [bookmark.id, bookmark])
|
|
47
|
-
)
|
|
48
|
-
const bmgTree = new Map(
|
|
49
|
-
bookmarkGroups0.map(group => [group.id, group])
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
const fixed = fixBookmarks(bookmarks1)
|
|
53
|
-
|
|
54
|
-
fixed.forEach(bg => {
|
|
55
|
-
if (!bmTree.has(bg.id)) {
|
|
56
|
-
store.bookmarks.push(bg)
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
bookmarkGroups1.forEach(bg => {
|
|
61
|
-
if (!bmgTree.has(bg.id)) {
|
|
62
|
-
store.bookmarkGroups.push(bg)
|
|
63
|
-
} else {
|
|
64
|
-
const bg1 = store.bookmarkGroups.find(
|
|
65
|
-
b => b.id === bg.id
|
|
66
|
-
)
|
|
67
|
-
bg1.bookmarkIds = uniq(
|
|
68
|
-
[
|
|
69
|
-
...bg1.bookmarkIds,
|
|
70
|
-
...bg.bookmarkIds
|
|
71
|
-
]
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
return false
|
|
76
|
-
})
|
|
77
|
-
const beforeUpload = async (file) => {
|
|
78
|
-
const names = [
|
|
79
|
-
'bookmarks',
|
|
80
|
-
'bookmarkGroups'
|
|
81
|
-
]
|
|
82
|
-
for (const name of names) {
|
|
83
|
-
window[`watch${name}`].stop()
|
|
84
|
-
}
|
|
85
|
-
upload(file)
|
|
86
|
-
await delay(1000)
|
|
87
|
-
for (const name of names) {
|
|
88
|
-
window[`watch${name}`].start()
|
|
89
|
-
}
|
|
90
|
-
}
|
|
28
|
+
const beforeUpload = beforeBookmarkUpload
|
|
91
29
|
|
|
92
30
|
const handleDownload = () => {
|
|
93
31
|
const txt = JSON.stringify({
|
|
94
|
-
bookmarkGroups: copy(bookmarkGroups),
|
|
95
|
-
bookmarks: copy(bookmarks)
|
|
32
|
+
bookmarkGroups: copy(bookmarkGroups || []),
|
|
33
|
+
bookmarks: copy(bookmarks || [])
|
|
96
34
|
}, null, 2)
|
|
97
35
|
const stamp = time(undefined, 'YYYY-MM-DD-HH-mm-ss')
|
|
98
36
|
download('bookmarks-' + stamp + '.json', txt)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bookmark import/upload logic
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import copy from 'json-deep-copy'
|
|
6
|
+
import { uniq, isPlainObject } from 'lodash-es'
|
|
7
|
+
import { action } from 'manate'
|
|
8
|
+
import uid from '../../common/uid'
|
|
9
|
+
import time from '../../common/time'
|
|
10
|
+
import { fixBookmarks } from '../../common/db-fix'
|
|
11
|
+
import delay from '../../common/wait'
|
|
12
|
+
|
|
13
|
+
function fixBookmarksId (bookmarks) {
|
|
14
|
+
return bookmarks.map(item => {
|
|
15
|
+
if (!isPlainObject(item)) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
if (!item.id) {
|
|
19
|
+
item.id = uid()
|
|
20
|
+
}
|
|
21
|
+
return item
|
|
22
|
+
}).filter(Boolean)
|
|
23
|
+
}
|
|
24
|
+
export const bookmarkUpload = action(async (file) => {
|
|
25
|
+
const { store } = window
|
|
26
|
+
const { bookmarks, bookmarkGroups } = store
|
|
27
|
+
|
|
28
|
+
const filePath = file.filePath
|
|
29
|
+
const txt = await window.fs.readFile(filePath)
|
|
30
|
+
|
|
31
|
+
const content = JSON.parse(txt)
|
|
32
|
+
let bookmarkGroups1 = []
|
|
33
|
+
let bookmarks1 = []
|
|
34
|
+
if (Array.isArray(content)) {
|
|
35
|
+
bookmarks1 = fixBookmarksId(content)
|
|
36
|
+
bookmarkGroups1 = [{
|
|
37
|
+
id: uid(),
|
|
38
|
+
title: 'imported_' + time(),
|
|
39
|
+
color: '#0088cc',
|
|
40
|
+
bookmarkGroupIds: [],
|
|
41
|
+
bookmarkIds: bookmarks1.map(b => b.id)
|
|
42
|
+
}]
|
|
43
|
+
} else {
|
|
44
|
+
bookmarkGroups1 = content.bookmarkGroups || []
|
|
45
|
+
bookmarks1 = fixBookmarksId(content.bookmarks || [])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const bookmarkGroups0 = copy(bookmarkGroups)
|
|
49
|
+
const bookmarks0 = copy(bookmarks)
|
|
50
|
+
|
|
51
|
+
const bmTree = new Map(
|
|
52
|
+
bookmarks0.map(bookmark => [bookmark.id, bookmark])
|
|
53
|
+
)
|
|
54
|
+
const bmgTree = new Map(
|
|
55
|
+
bookmarkGroups0.map(group => [group.id, group])
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const fixed = fixBookmarks(bookmarks1)
|
|
59
|
+
|
|
60
|
+
fixed.forEach(bg => {
|
|
61
|
+
if (!bmTree.has(bg.id)) {
|
|
62
|
+
store.bookmarks.push(bg)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
bookmarkGroups1.forEach(bg => {
|
|
67
|
+
if (!bmgTree.has(bg.id)) {
|
|
68
|
+
store.bookmarkGroups.push(bg)
|
|
69
|
+
} else {
|
|
70
|
+
const bg1 = store.bookmarkGroups.find(
|
|
71
|
+
b => b.id === bg.id
|
|
72
|
+
)
|
|
73
|
+
bg1.bookmarkIds = uniq(
|
|
74
|
+
[
|
|
75
|
+
...(bg1.bookmarkIds || []),
|
|
76
|
+
...(bg.bookmarkIds || [])
|
|
77
|
+
]
|
|
78
|
+
)
|
|
79
|
+
bg1.bookmarkGroupIds = uniq(
|
|
80
|
+
[
|
|
81
|
+
...(bg1.bookmarkGroupIds || []),
|
|
82
|
+
...(bg.bookmarkGroupIds || [])
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
store.fixBookmarkGroups()
|
|
89
|
+
|
|
90
|
+
return false
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
export async function beforeBookmarkUpload (file) {
|
|
94
|
+
const names = [
|
|
95
|
+
'bookmarks',
|
|
96
|
+
'bookmarkGroups'
|
|
97
|
+
]
|
|
98
|
+
for (const name of names) {
|
|
99
|
+
window[`watch${name}`].stop()
|
|
100
|
+
}
|
|
101
|
+
bookmarkUpload(file)
|
|
102
|
+
await delay(1000)
|
|
103
|
+
for (const name of names) {
|
|
104
|
+
window[`watch${name}`].start()
|
|
105
|
+
}
|
|
106
|
+
}
|
package/client/store/common.js
CHANGED
|
@@ -10,9 +10,7 @@ import {
|
|
|
10
10
|
rightSidebarWidthKey,
|
|
11
11
|
addPanelWidthLsKey,
|
|
12
12
|
dismissDelKeyTipLsKey,
|
|
13
|
-
connectionMap
|
|
14
|
-
settingMap,
|
|
15
|
-
settingAiId
|
|
13
|
+
connectionMap
|
|
16
14
|
} from '../common/constants'
|
|
17
15
|
import * as ls from '../common/safe-local-storage'
|
|
18
16
|
import { refs, refsStatic } from '../components/common/ref'
|
|
@@ -20,7 +18,6 @@ import { action } from 'manate'
|
|
|
20
18
|
import uid from '../common/uid'
|
|
21
19
|
import deepCopy from 'json-deep-copy'
|
|
22
20
|
import { aiConfigsArr } from '../components/ai/ai-config-props'
|
|
23
|
-
import settingList from '../common/setting-list'
|
|
24
21
|
|
|
25
22
|
const e = window.translate
|
|
26
23
|
const { assign } = Object
|
|
@@ -54,12 +51,7 @@ export default Store => {
|
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
Store.prototype.toggleAIConfig = function () {
|
|
57
|
-
|
|
58
|
-
store.storeAssign({
|
|
59
|
-
settingTab: settingMap.setting
|
|
60
|
-
})
|
|
61
|
-
store.setSettingItem(settingList().find(d => d.id === settingAiId))
|
|
62
|
-
store.openSettingModal()
|
|
54
|
+
window.store.showAIConfigModal = true
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
Store.prototype.onResize = debounce(async function () {
|
|
@@ -118,6 +118,7 @@ export default () => {
|
|
|
118
118
|
rightPanelTab: 'info',
|
|
119
119
|
rightPanelPinned: false,
|
|
120
120
|
rightPanelWidth: parseInt(ls.getItem(rightSidebarWidthKey), 10) || 500,
|
|
121
|
+
showAIConfigModal: false,
|
|
121
122
|
|
|
122
123
|
// for settings related
|
|
123
124
|
settingItem: initSettingItem([], settingMap.bookmarks),
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export default [
|
|
2
|
-
{
|
|
3
|
-
label: 'OpenAI',
|
|
4
|
-
baseURL: 'https://api.openai.com/v1',
|
|
5
|
-
homepage: 'https://openai.com',
|
|
6
|
-
models: ['gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-4.5']
|
|
7
|
-
},
|
|
8
|
-
{
|
|
9
|
-
label: 'DeepSeek',
|
|
10
|
-
baseURL: 'https://api.deepseek.com/v1',
|
|
11
|
-
homepage: 'https://deepseek.com',
|
|
12
|
-
models: ['deepseek-chat', 'deepseek-coder', 'deepseek-reasoner']
|
|
13
|
-
}
|
|
14
|
-
]
|