Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
60e14b22
authored
Oct 01, 2025
by
HynoR
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add visual editing mode for chat configurations
parent
7b96f7c8
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
309 additions
and
8 deletions
+309
-8
web/src/pages/Setting/Chat/SettingsChats.jsx
+309
-8
No files found.
web/src/pages/Setting/Chat/SettingsChats.jsx
View file @
60e14b22
...
@@ -18,7 +18,25 @@ For commercial licensing, please contact support@quantumnous.com
...
@@ -18,7 +18,25 @@ For commercial licensing, please contact support@quantumnous.com
*/
*/
import
React
,
{
useEffect
,
useState
,
useRef
}
from
'react'
;
import
React
,
{
useEffect
,
useState
,
useRef
}
from
'react'
;
import
{
Banner
,
Button
,
Form
,
Space
,
Spin
}
from
'@douyinfe/semi-ui'
;
import
{
Banner
,
Button
,
Form
,
Space
,
Spin
,
RadioGroup
,
Radio
,
Table
,
Modal
,
Input
,
Divider
,
}
from
'@douyinfe/semi-ui'
;
import
{
IconPlus
,
IconEdit
,
IconDelete
,
IconSearch
,
}
from
'@douyinfe/semi-icons'
;
import
{
import
{
compareObjects
,
compareObjects
,
API
,
API
,
...
@@ -37,6 +55,52 @@ export default function SettingsChats(props) {
...
@@ -37,6 +55,52 @@ export default function SettingsChats(props) {
});
});
const
refForm
=
useRef
();
const
refForm
=
useRef
();
const
[
inputsRow
,
setInputsRow
]
=
useState
(
inputs
);
const
[
inputsRow
,
setInputsRow
]
=
useState
(
inputs
);
const
[
editMode
,
setEditMode
]
=
useState
(
'json'
);
const
[
chatConfigs
,
setChatConfigs
]
=
useState
([]);
const
[
modalVisible
,
setModalVisible
]
=
useState
(
false
);
const
[
editingConfig
,
setEditingConfig
]
=
useState
(
null
);
const
[
isEdit
,
setIsEdit
]
=
useState
(
false
);
const
[
searchText
,
setSearchText
]
=
useState
(
''
);
const
modalFormRef
=
useRef
();
const
jsonToConfigs
=
(
jsonString
)
=>
{
try
{
const
configs
=
JSON
.
parse
(
jsonString
);
return
Array
.
isArray
(
configs
)
?
configs
.
map
((
config
,
index
)
=>
({
id
:
index
,
name
:
Object
.
keys
(
config
)[
0
]
||
''
,
url
:
Object
.
values
(
config
)[
0
]
||
''
,
}))
:
[];
}
catch
(
error
)
{
console
.
error
(
'JSON parse error:'
,
error
);
return
[];
}
};
const
configsToJson
=
(
configs
)
=>
{
const
jsonArray
=
configs
.
map
((
config
)
=>
({
[
config
.
name
]:
config
.
url
,
}));
return
JSON
.
stringify
(
jsonArray
,
null
,
2
);
};
const
syncJsonToConfigs
=
()
=>
{
const
configs
=
jsonToConfigs
(
inputs
.
Chats
);
setChatConfigs
(
configs
);
};
const
syncConfigsToJson
=
(
configs
)
=>
{
const
jsonString
=
configsToJson
(
configs
);
setInputs
((
prev
)
=>
({
...
prev
,
Chats
:
jsonString
,
}));
if
(
refForm
.
current
&&
editMode
===
'json'
)
{
refForm
.
current
.
setValues
({
Chats
:
jsonString
});
}
};
async
function
onSubmit
()
{
async
function
onSubmit
()
{
try
{
try
{
...
@@ -104,15 +168,145 @@ export default function SettingsChats(props) {
...
@@ -104,15 +168,145 @@ export default function SettingsChats(props) {
setInputs
(
currentInputs
);
setInputs
(
currentInputs
);
setInputsRow
(
structuredClone
(
currentInputs
));
setInputsRow
(
structuredClone
(
currentInputs
));
refForm
.
current
.
setValues
(
currentInputs
);
refForm
.
current
.
setValues
(
currentInputs
);
// 同步到可视化配置
const
configs
=
jsonToConfigs
(
currentInputs
.
Chats
||
'[]'
);
setChatConfigs
(
configs
);
},
[
props
.
options
]);
},
[
props
.
options
]);
useEffect
(()
=>
{
if
(
editMode
===
'visual'
)
{
syncJsonToConfigs
();
}
},
[
inputs
.
Chats
,
editMode
]);
useEffect
(()
=>
{
if
(
refForm
.
current
&&
editMode
===
'json'
)
{
refForm
.
current
.
setValues
(
inputs
);
}
},
[
editMode
,
inputs
]);
const
handleAddConfig
=
()
=>
{
setEditingConfig
({
name
:
''
,
url
:
''
});
setIsEdit
(
false
);
setModalVisible
(
true
);
setTimeout
(()
=>
{
if
(
modalFormRef
.
current
)
{
modalFormRef
.
current
.
setValues
({
name
:
''
,
url
:
''
});
}
},
100
);
};
const
handleEditConfig
=
(
config
)
=>
{
setEditingConfig
({
...
config
});
setIsEdit
(
true
);
setModalVisible
(
true
);
setTimeout
(()
=>
{
if
(
modalFormRef
.
current
)
{
modalFormRef
.
current
.
setValues
(
config
);
}
},
100
);
};
const
handleDeleteConfig
=
(
id
)
=>
{
const
newConfigs
=
chatConfigs
.
filter
((
config
)
=>
config
.
id
!==
id
);
setChatConfigs
(
newConfigs
);
syncConfigsToJson
(
newConfigs
);
showSuccess
(
t
(
'删除成功'
));
};
const
handleModalOk
=
()
=>
{
if
(
modalFormRef
.
current
)
{
modalFormRef
.
current
.
validate
()
.
then
((
values
)
=>
{
if
(
isEdit
)
{
const
newConfigs
=
chatConfigs
.
map
((
config
)
=>
config
.
id
===
editingConfig
.
id
?
{
...
editingConfig
,
name
:
values
.
name
,
url
:
values
.
url
}
:
config
,
);
setChatConfigs
(
newConfigs
);
syncConfigsToJson
(
newConfigs
);
}
else
{
const
maxId
=
chatConfigs
.
length
>
0
?
Math
.
max
(...
chatConfigs
.
map
((
c
)
=>
c
.
id
))
:
-
1
;
const
newConfig
=
{
id
:
maxId
+
1
,
name
:
values
.
name
,
url
:
values
.
url
,
};
const
newConfigs
=
[...
chatConfigs
,
newConfig
];
setChatConfigs
(
newConfigs
);
syncConfigsToJson
(
newConfigs
);
}
setModalVisible
(
false
);
setEditingConfig
(
null
);
showSuccess
(
isEdit
?
t
(
'编辑成功'
)
:
t
(
'添加成功'
));
})
.
catch
((
error
)
=>
{
console
.
error
(
'Modal form validation error:'
,
error
);
});
}
};
const
handleModalCancel
=
()
=>
{
setModalVisible
(
false
);
setEditingConfig
(
null
);
};
const
filteredConfigs
=
chatConfigs
.
filter
(
(
config
)
=>
!
searchText
||
config
.
name
.
toLowerCase
().
includes
(
searchText
.
toLowerCase
()),
);
const
columns
=
[
{
title
:
t
(
'聊天应用名称'
),
dataIndex
:
'name'
,
key
:
'name'
,
render
:
(
text
)
=>
text
||
t
(
'未命名'
),
},
{
title
:
t
(
'URL链接'
),
dataIndex
:
'url'
,
key
:
'url'
,
render
:
(
text
)
=>
(
<
div
style=
{
{
maxWidth
:
300
,
wordBreak
:
'break-all'
}
}
>
{
text
}
</
div
>
),
},
{
title
:
t
(
'操作'
),
key
:
'action'
,
render
:
(
_
,
record
)
=>
(
<
Space
>
<
Button
type=
'primary'
icon=
{
<
IconEdit
/>
}
size=
'small'
onClick=
{
()
=>
handleEditConfig
(
record
)
}
>
{
t
(
'编辑'
)
}
</
Button
>
<
Button
type=
'danger'
icon=
{
<
IconDelete
/>
}
size=
'small'
onClick=
{
()
=>
handleDeleteConfig
(
record
.
id
)
}
>
{
t
(
'删除'
)
}
</
Button
>
</
Space
>
),
},
];
return
(
return
(
<
Spin
spinning=
{
loading
}
>
<
Spin
spinning=
{
loading
}
>
<
Form
<
Space
vertical
style=
{
{
width
:
'100%'
}
}
>
values=
{
inputs
}
getFormApi=
{
(
formAPI
)
=>
(
refForm
.
current
=
formAPI
)
}
style=
{
{
marginBottom
:
15
}
}
>
<
Form
.
Section
text=
{
t
(
'聊天设置'
)
}
>
<
Form
.
Section
text=
{
t
(
'聊天设置'
)
}
>
<
Banner
<
Banner
type=
'info'
type=
'info'
...
@@ -120,6 +314,75 @@ export default function SettingsChats(props) {
...
@@ -120,6 +314,75 @@ export default function SettingsChats(props) {
'链接中的
{
key
}
将自动替换为
sk
-
xxxx
,
{
address
}
将自动替换为系统设置的服务器地址,末尾不带
/
和
/
v1
'
,
'链接中的
{
key
}
将自动替换为
sk
-
xxxx
,
{
address
}
将自动替换为系统设置的服务器地址,末尾不带
/
和
/
v1
'
,
)
}
)
}
/>
/>
<
Divider
/>
<
div
style=
{
{
marginBottom
:
16
}
}
>
<
span
style=
{
{
marginRight
:
16
,
fontWeight
:
600
}
}
>
{
t
(
'编辑模式'
)
}
:
</
span
>
<
RadioGroup
type=
'button'
value=
{
editMode
}
onChange=
{
(
e
)
=>
{
const
newMode
=
e
.
target
.
value
;
setEditMode
(
newMode
);
// 确保模式切换时数据正确同步
setTimeout
(()
=>
{
if
(
newMode
===
'json'
&&
refForm
.
current
)
{
refForm
.
current
.
setValues
(
inputs
);
}
},
100
);
}
}
>
<
Radio
value=
'visual'
>
{
t
(
'可视化编辑'
)
}
</
Radio
>
<
Radio
value=
'json'
>
{
t
(
'JSON编辑'
)
}
</
Radio
>
</
RadioGroup
>
</
div
>
{
editMode
===
'visual'
?
(
<
div
>
<
Space
style=
{
{
marginBottom
:
16
}
}
>
<
Button
type=
'primary'
icon=
{
<
IconPlus
/>
}
onClick=
{
handleAddConfig
}
>
{
t
(
'添加聊天配置'
)
}
</
Button
>
<
Input
prefix=
{
<
IconSearch
/>
}
placeholder=
{
t
(
'搜索聊天应用名称'
)
}
value=
{
searchText
}
onChange=
{
(
value
)
=>
setSearchText
(
value
)
}
style=
{
{
width
:
250
}
}
showClear
/>
</
Space
>
<
Table
columns=
{
columns
}
dataSource=
{
filteredConfigs
}
rowKey=
'id'
pagination=
{
{
pageSize
:
10
,
showSizeChanger
:
false
,
showQuickJumper
:
true
,
showTotal
:
(
total
,
range
)
=>
t
(
'共
{{
total
}}
项,当前显示
{{
start
}}
-
{{
end
}}
项'
,
{
total
,
start
:
range
[
0
],
end
:
range
[
1
],
}),
}
}
/>
</
div
>
)
:
(
<
Form
values=
{
inputs
}
getFormApi=
{
(
formAPI
)
=>
(
refForm
.
current
=
formAPI
)
}
>
<
Form
.
TextArea
<
Form
.
TextArea
label=
{
t
(
'聊天配置'
)
}
label=
{
t
(
'聊天配置'
)
}
extraText=
{
''
}
extraText=
{
''
}
...
@@ -143,11 +406,49 @@ export default function SettingsChats(props) {
...
@@ -143,11 +406,49 @@ export default function SettingsChats(props) {
})
})
}
}
/>
/>
</
Form
.
Section
>
</
Form
>
</
Form
>
)
}
</
Form
.
Section
>
<
Space
>
<
Space
>
<
Button
onClick=
{
onSubmit
}
>
{
t
(
'保存聊天设置'
)
}
</
Button
>
<
Button
type=
'primary'
onClick=
{
onSubmit
}
>
{
t
(
'保存聊天设置'
)
}
</
Button
>
</
Space
>
</
Space
>
</
Space
>
<
Modal
title=
{
isEdit
?
t
(
'编辑聊天配置'
)
:
t
(
'添加聊天配置'
)
}
visible=
{
modalVisible
}
onOk=
{
handleModalOk
}
onCancel=
{
handleModalCancel
}
width=
{
600
}
>
<
Form
getFormApi=
{
(
api
)
=>
(
modalFormRef
.
current
=
api
)
}
>
<
Form
.
Input
field=
'name'
label=
{
t
(
'聊天应用名称'
)
}
placeholder=
{
t
(
'请输入聊天应用名称'
)
}
rules=
{
[
{
required
:
true
,
message
:
t
(
'请输入聊天应用名称'
)
},
{
min
:
1
,
message
:
t
(
'名称不能为空'
)
},
]
}
/>
<
Form
.
Input
field=
'url'
label=
{
t
(
'URL链接'
)
}
placeholder=
{
t
(
'请输入完整的URL链接'
)
}
rules=
{
[{
required
:
true
,
message
:
t
(
'请输入URL链接'
)
}]
}
/>
<
Banner
type=
'info'
description=
{
t
(
'提示:链接中的
{
key
}
将被替换为
API
密钥,
{
address
}
将被替换为服务器地址'
,
)
}
style=
{
{
marginTop
:
16
}
}
/>
</
Form
>
</
Modal
>
</
Spin
>
</
Spin
>
);
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment