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
4599941e
authored
Oct 02, 2025
by
Seefs
Committed by
GitHub
Oct 02, 2025
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1947 from HynoR/feat/newedit
feat: Add visual editing mode for chat configurations
parents
7f6c814a
019704f0
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
386 additions
and
32 deletions
+386
-32
web/src/pages/Setting/Chat/SettingsChats.jsx
+386
-32
No files found.
web/src/pages/Setting/Chat/SettingsChats.jsx
View file @
4599941e
...
@@ -18,7 +18,26 @@ For commercial licensing, please contact support@quantumnous.com
...
@@ -18,7 +18,26 @@ 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
,
IconSaveStroked
,
}
from
'@douyinfe/semi-icons'
;
import
{
import
{
compareObjects
,
compareObjects
,
API
,
API
,
...
@@ -37,6 +56,52 @@ export default function SettingsChats(props) {
...
@@ -37,6 +56,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
(
'visual'
);
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
{
...
@@ -103,16 +168,184 @@ export default function SettingsChats(props) {
...
@@ -103,16 +168,184 @@ export default function SettingsChats(props) {
}
}
setInputs
(
currentInputs
);
setInputs
(
currentInputs
);
setInputsRow
(
structuredClone
(
currentInputs
));
setInputsRow
(
structuredClone
(
currentInputs
));
refForm
.
current
.
setValues
(
currentInputs
);
if
(
refForm
.
current
)
{
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
)
=>
{
// 检查名称是否重复
const
isDuplicate
=
chatConfigs
.
some
(
(
config
)
=>
config
.
name
===
values
.
name
&&
(
!
isEdit
||
config
.
id
!==
editingConfig
.
id
)
);
if
(
isDuplicate
)
{
showError
(
t
(
'聊天应用名称已存在,请使用其他名称'
));
return
;
}
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
highlightKeywords
=
(
text
)
=>
{
if
(
!
text
)
return
text
;
const
parts
=
text
.
split
(
/
(\{
address
\}
|
\{
key
\})
/g
);
return
parts
.
map
((
part
,
index
)
=>
{
if
(
part
===
'{address}'
)
{
return
(
<
span
key=
{
index
}
style=
{
{
color
:
'#0077cc'
,
fontWeight
:
600
}
}
>
{
part
}
</
span
>
);
}
else
if
(
part
===
'{key}'
)
{
return
(
<
span
key=
{
index
}
style=
{
{
color
:
'#ff6b35'
,
fontWeight
:
600
}
}
>
{
part
}
</
span
>
);
}
return
part
;
});
};
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'
}
}
>
{
highlightKeywords
(
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,34 +353,155 @@ export default function SettingsChats(props) {
...
@@ -120,34 +353,155 @@ export default function SettingsChats(props) {
'链接中的
{
key
}
将自动替换为
sk
-
xxxx
,
{
address
}
将自动替换为系统设置的服务器地址,末尾不带
/
和
/
v1
'
,
'链接中的
{
key
}
将自动替换为
sk
-
xxxx
,
{
address
}
将自动替换为系统设置的服务器地址,末尾不带
/
和
/
v1
'
,
)
}
)
}
/>
/>
<
Form
.
TextArea
label=
{
t
(
'聊天配置'
)
}
<
Divider
/>
extraText=
{
''
}
placeholder=
{
t
(
'为一个 JSON 文本'
)
}
<
div
style=
{
{
marginBottom
:
16
}
}
>
field=
{
'Chats'
}
<
span
style=
{
{
marginRight
:
16
,
fontWeight
:
600
}
}
>
autosize=
{
{
minRows
:
6
,
maxRows
:
12
}
}
{
t
(
'编辑模式'
)
}
:
trigger=
'blur'
</
span
>
stopValidateWithError
<
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
>
<
Button
type=
'primary'
theme=
'solid'
icon=
{
<
IconSaveStroked
/>
}
onClick=
{
onSubmit
}
>
{
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
label=
{
t
(
'聊天配置'
)
}
extraText=
{
''
}
placeholder=
{
t
(
'为一个 JSON 文本'
)
}
field=
{
'Chats'
}
autosize=
{
{
minRows
:
6
,
maxRows
:
12
}
}
trigger=
'blur'
stopValidateWithError
rules=
{
[
{
validator
:
(
rule
,
value
)
=>
{
return
verifyJSON
(
value
);
},
message
:
t
(
'不是合法的 JSON 字符串'
),
},
]
}
onChange=
{
(
value
)
=>
setInputs
({
...
inputs
,
Chats
:
value
,
})
}
/>
</
Form
>
)
}
</
Form
.
Section
>
{
editMode
===
'json'
&&
(
<
Space
>
<
Button
type=
'primary'
icon=
{
<
IconSaveStroked
/>
}
onClick=
{
onSubmit
}
>
{
t
(
'保存聊天设置'
)
}
</
Button
>
</
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=
{
[
rules=
{
[
{
{
required
:
true
,
message
:
t
(
'请输入聊天应用名称'
)
},
validator
:
(
rule
,
value
)
=>
{
{
min
:
1
,
message
:
t
(
'名称不能为空'
)
},
return
verifyJSON
(
value
);
},
message
:
t
(
'不是合法的 JSON 字符串'
),
},
]
}
]
}
onChange=
{
(
value
)
=>
setInputs
({
...
inputs
,
Chats
:
value
,
})
}
/>
/>
</
Form
.
Section
>
<
Form
.
Input
</
Form
>
field=
'url'
<
Space
>
label=
{
t
(
'URL链接'
)
}
<
Button
onClick=
{
onSubmit
}
>
{
t
(
'保存聊天设置'
)
}
</
Button
>
placeholder=
{
t
(
'请输入完整的URL链接'
)
}
</
Space
>
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