@@ -427,14 +427,23 @@ func (s *CharacterService) processRegexScriptsFromExtensions(userID, characterID
|
||||
OwnerCharID: &characterID,
|
||||
}
|
||||
|
||||
// 提取字段
|
||||
if name, ok := scriptData["name"].(string); ok {
|
||||
// 提取字段 - 兼容 SillyTavern 的字段名
|
||||
// 脚本名称:优先使用 scriptName,其次 name
|
||||
if scriptName, ok := scriptData["scriptName"].(string); ok {
|
||||
script.Name = scriptName
|
||||
} else if name, ok := scriptData["name"].(string); ok {
|
||||
script.Name = name
|
||||
}
|
||||
|
||||
// 查找正则表达式
|
||||
if findRegex, ok := scriptData["findRegex"].(string); ok {
|
||||
script.FindRegex = findRegex
|
||||
}
|
||||
if replaceWith, ok := scriptData["replaceWith"].(string); ok {
|
||||
|
||||
// 替换字符串:优先使用 replaceString,其次 replaceWith
|
||||
if replaceString, ok := scriptData["replaceString"].(string); ok {
|
||||
script.ReplaceWith = replaceString
|
||||
} else if replaceWith, ok := scriptData["replaceWith"].(string); ok {
|
||||
script.ReplaceWith = replaceWith
|
||||
}
|
||||
if placement, ok := scriptData["placement"].(float64); ok {
|
||||
|
||||
@@ -627,7 +627,7 @@ export default function ChatArea({ conversation, character, onConversationUpdate
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`max-w-[70%] min-w-0 flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
|
||||
<div className={`min-w-0 flex flex-col ${msg.role === 'user' ? 'max-w-[70%] items-end' : 'w-[70%] items-start'}`}>
|
||||
{/* 助手名称 */}
|
||||
{msg.role === 'assistant' && (
|
||||
<span className="text-xs text-white/40 mb-1 ml-1">{character.name}</span>
|
||||
@@ -638,7 +638,7 @@ export default function ChatArea({ conversation, character, onConversationUpdate
|
||||
className={`relative px-4 py-3 rounded-2xl ${
|
||||
msg.role === 'user'
|
||||
? 'bg-primary/25 border border-primary/20 rounded-br-md'
|
||||
: 'glass rounded-bl-md'
|
||||
: 'glass rounded-bl-md w-full'
|
||||
}`}
|
||||
style={{ wordBreak: 'break-word', overflowWrap: 'anywhere' }}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {useEffect, useState} from 'react'
|
||||
import {useNavigate, useSearchParams} from 'react-router-dom'
|
||||
import Navbar from '../components/Navbar'
|
||||
import {Book, Code2, Download, Edit, FileJson, Image as ImageIcon, Plus, Search, Trash2, X} from 'lucide-react'
|
||||
import {Book, Code2, Download, Edit, FileJson, FileUp, Image as ImageIcon, Plus, Search, Trash2, X} from 'lucide-react'
|
||||
import {type Character, characterApi} from '../api/character'
|
||||
import {type RegexScript, regexScriptApi} from '../api/regex'
|
||||
|
||||
@@ -36,6 +36,14 @@ export default function CharacterManagePage() {
|
||||
const [showRegexScriptEditor, setShowRegexScriptEditor] = useState(false)
|
||||
const [regexScripts, setRegexScripts] = useState<RegexScript[]>([])
|
||||
const [editingTab, setEditingTab] = useState<'basic' | 'worldbook' | 'regex'>('basic')
|
||||
const [showAddRegexModal, setShowAddRegexModal] = useState(false)
|
||||
const [newRegexForm, setNewRegexForm] = useState({
|
||||
name: '',
|
||||
findRegex: '',
|
||||
replaceWith: '',
|
||||
placement: 1,
|
||||
order: 100,
|
||||
})
|
||||
const navigate = useNavigate()
|
||||
const [searchParams] = useSearchParams()
|
||||
|
||||
@@ -759,13 +767,18 @@ export default function CharacterManagePage() {
|
||||
try {
|
||||
await regexScriptApi.createRegexScript({
|
||||
name: '新脚本',
|
||||
findRegex: '',
|
||||
findRegex: '.*', // 默认匹配所有内容
|
||||
replaceWith: '',
|
||||
scope: 1,
|
||||
ownerCharId: selectedCharacter.id,
|
||||
disabled: false,
|
||||
placement: 1, // 默认输出阶段
|
||||
order: 100,
|
||||
})
|
||||
loadRegexScripts(selectedCharacter.id)
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.msg || '创建失败')
|
||||
console.error('创建脚本失败:', err)
|
||||
alert(err.response?.data?.msg || err.message || '创建失败')
|
||||
}
|
||||
}}
|
||||
className="px-4 py-2 bg-gradient-to-r from-primary to-secondary rounded-lg text-sm cursor-pointer"
|
||||
@@ -913,10 +926,19 @@ export default function CharacterManagePage() {
|
||||
if (!testInput) return
|
||||
|
||||
try {
|
||||
const result = await regexScriptApi.testRegexScript(script.id, testInput)
|
||||
alert(`原文:${result.data.original}\n\n结果:${result.data.result}`)
|
||||
const response = await regexScriptApi.testRegexScript(script.id, testInput)
|
||||
console.log('测试响应:', response)
|
||||
|
||||
// 处理响应数据结构
|
||||
const testResult = response.data || response
|
||||
if (testResult.success === false && testResult.error) {
|
||||
alert(`测试失败:${testResult.error}`)
|
||||
} else {
|
||||
alert(`原文:${testResult.original}\n\n结果:${testResult.result}`)
|
||||
}
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.msg || '测试失败')
|
||||
console.error('测试失败:', err)
|
||||
alert(err.response?.data?.msg || err.message || '测试失败')
|
||||
}
|
||||
}}
|
||||
className="px-3 py-1.5 glass-hover rounded-lg text-xs cursor-pointer"
|
||||
@@ -953,19 +975,7 @@ export default function CharacterManagePage() {
|
||||
|
||||
<div className="flex gap-3 mt-6 pt-6 border-t border-white/10">
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await regexScriptApi.createRegexScript({
|
||||
name: '新脚本',
|
||||
findRegex: '',
|
||||
scope: 1,
|
||||
ownerCharId: selectedCharacter.id,
|
||||
})
|
||||
loadRegexScripts(selectedCharacter.id)
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.msg || '创建失败')
|
||||
}
|
||||
}}
|
||||
onClick={() => setShowAddRegexModal(true)}
|
||||
className="px-4 py-2 glass-hover rounded-lg text-sm cursor-pointer flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
@@ -982,6 +992,219 @@ export default function CharacterManagePage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 添加正则脚本弹窗 */}
|
||||
{showAddRegexModal && selectedCharacter && (
|
||||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||
<div className="glass rounded-3xl p-8 max-w-2xl w-full">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-2xl font-bold">添加正则脚本</h2>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowAddRegexModal(false)
|
||||
setNewRegexForm({
|
||||
name: '',
|
||||
findRegex: '',
|
||||
replaceWith: '',
|
||||
placement: 1,
|
||||
order: 100,
|
||||
})
|
||||
}}
|
||||
className="p-2 glass-hover rounded-lg cursor-pointer"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 导入 JSON 文件 */}
|
||||
<div>
|
||||
<label className="block text-sm text-white/80 mb-2">从 JSON 文件导入</label>
|
||||
<label className="glass-hover rounded-xl p-6 border-2 border-dashed border-white/20 cursor-pointer block text-center">
|
||||
<FileUp className="w-8 h-8 mx-auto mb-2 text-white/40" />
|
||||
<span className="text-sm text-white/60">点击选择 JSON 文件</span>
|
||||
<input
|
||||
type="file"
|
||||
accept=".json"
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
try {
|
||||
const text = await file.text()
|
||||
const jsonData = JSON.parse(text)
|
||||
|
||||
// 支持单个脚本或脚本数组
|
||||
const scripts = Array.isArray(jsonData) ? jsonData : [jsonData]
|
||||
|
||||
for (const scriptData of scripts) {
|
||||
await regexScriptApi.createRegexScript({
|
||||
name: scriptData.scriptName || scriptData.name || '导入的脚本',
|
||||
findRegex: scriptData.findRegex || '.*',
|
||||
replaceWith: scriptData.replaceString || scriptData.replaceWith || '',
|
||||
placement: scriptData.placement || 1,
|
||||
disabled: scriptData.disabled || false,
|
||||
order: scriptData.order || 100,
|
||||
scope: 1,
|
||||
ownerCharId: selectedCharacter.id,
|
||||
markdownOnly: scriptData.markdownOnly || false,
|
||||
runOnEdit: scriptData.runOnEdit || false,
|
||||
promptOnly: scriptData.promptOnly || false,
|
||||
substituteRegex: scriptData.substituteRegex !== false,
|
||||
})
|
||||
}
|
||||
|
||||
loadRegexScripts(selectedCharacter.id)
|
||||
setShowAddRegexModal(false)
|
||||
alert(`成功导入 ${scripts.length} 个正则脚本`)
|
||||
} catch (err: any) {
|
||||
console.error('导入失败:', err)
|
||||
alert('导入失败:' + (err.message || '文件格式不正确'))
|
||||
}
|
||||
}}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
<p className="text-xs text-white/40 mt-2">
|
||||
支持 SillyTavern 格式的正则脚本 JSON 文件
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-white/10"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-4 glass text-white/60">或手动创建</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 手动输入表单 */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm text-white/80 mb-2 block">脚本名称 *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newRegexForm.name}
|
||||
onChange={(e) => setNewRegexForm({ ...newRegexForm, name: e.target.value })}
|
||||
placeholder="例如: 移除 Markdown 加粗"
|
||||
className="w-full px-4 py-3 glass rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-white/80 mb-2 block">查找正则表达式 *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newRegexForm.findRegex}
|
||||
onChange={(e) => setNewRegexForm({ ...newRegexForm, findRegex: e.target.value })}
|
||||
placeholder="例如: \*\*(.+?)\*\*"
|
||||
className="w-full px-4 py-3 glass rounded-xl text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-white/80 mb-2 block">替换为</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newRegexForm.replaceWith}
|
||||
onChange={(e) => setNewRegexForm({ ...newRegexForm, replaceWith: e.target.value })}
|
||||
placeholder="例如: <strong>$1</strong>"
|
||||
className="w-full px-4 py-3 glass rounded-xl text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-white/80 mb-2 block">执行阶段</label>
|
||||
<select
|
||||
value={newRegexForm.placement}
|
||||
onChange={(e) => setNewRegexForm({ ...newRegexForm, placement: parseInt(e.target.value) })}
|
||||
className="w-full px-4 py-3 glass rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
>
|
||||
<option value="0">输入</option>
|
||||
<option value="1">输出</option>
|
||||
<option value="2">世界书</option>
|
||||
<option value="3">推理</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-white/80 mb-2 block">执行顺序</label>
|
||||
<input
|
||||
type="number"
|
||||
value={newRegexForm.order}
|
||||
onChange={(e) => setNewRegexForm({ ...newRegexForm, order: parseInt(e.target.value) })}
|
||||
className="w-full px-4 py-3 glass rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowAddRegexModal(false)
|
||||
setNewRegexForm({
|
||||
name: '',
|
||||
findRegex: '',
|
||||
replaceWith: '',
|
||||
placement: 1,
|
||||
order: 100,
|
||||
})
|
||||
}}
|
||||
className="flex-1 px-6 py-3 glass-hover rounded-xl cursor-pointer"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!newRegexForm.name.trim()) {
|
||||
alert('请输入脚本名称')
|
||||
return
|
||||
}
|
||||
if (!newRegexForm.findRegex.trim()) {
|
||||
alert('请输入查找正则表达式')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await regexScriptApi.createRegexScript({
|
||||
name: newRegexForm.name,
|
||||
findRegex: newRegexForm.findRegex,
|
||||
replaceWith: newRegexForm.replaceWith,
|
||||
placement: newRegexForm.placement,
|
||||
order: newRegexForm.order,
|
||||
scope: 1,
|
||||
ownerCharId: selectedCharacter.id,
|
||||
disabled: false,
|
||||
substituteRegex: true,
|
||||
})
|
||||
|
||||
loadRegexScripts(selectedCharacter.id)
|
||||
setShowAddRegexModal(false)
|
||||
setNewRegexForm({
|
||||
name: '',
|
||||
findRegex: '',
|
||||
replaceWith: '',
|
||||
placement: 1,
|
||||
order: 100,
|
||||
})
|
||||
alert('创建成功')
|
||||
} catch (err: any) {
|
||||
console.error('创建失败:', err)
|
||||
alert(err.response?.data?.msg || err.message || '创建失败')
|
||||
}
|
||||
}}
|
||||
className="flex-1 px-6 py-3 bg-gradient-to-r from-primary to-secondary rounded-xl font-medium hover:opacity-90 transition-opacity cursor-pointer"
|
||||
>
|
||||
创建
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user