feat(chat): 动作/旁白格式统一——兼容星号(*…*/*…) + 人设强制 XML
模型常漂移成 *动作:弯起眼睛*(星号),前端/后端只认 <action>/<narration>,导致星号版 没被剥成灰斜体注记/没从 IM 剥除→裸出在气泡、上下文不连贯。 - parseContent.ts(新): 把 XML <action>/<narration>、全角*…*(带'动作/旁白/内心/神态/轻声' 关键词或整段≤40字)、半角*词…*(仅关键词前缀)统一解析成 ContentPart,剥出正文进 BubbleNotes 灰斜体注记(*动作:…*); 超长无关键词*…*保留为文本; <p>拆气泡不变 - sanitize.go: stripNarration 同样剥星号动作(与 XML 一致); splitBubbles 不变 - cyrene.yaml: 新增格式铁律——动作/旁白必须用 <action>/<narration>,禁用*/Markdown等 验收: 后端 build/vet/test 全绿 + 前端 build 绿 + Node parseContent/splitBubble 实测(5组)
This commit is contained in:
1 parent
06fd2f4aaf
commit
817f1e6355
4 files changed
+197
-20
No files matched your search
@@ -89,3 +89,72 @@ func TestSplitBubbles_OnlyActionIsEmpty(t *testing.T) {
|
||||
t.Errorf("splitBubbles = %#v, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripNarration_StripsFullStarRoleplay(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := "*动作:弯起眼睛笑了*\n当然看得到呀,亲爱的。"
|
||||
got := stripNarration(in)
|
||||
want := "当然看得到呀,亲爱的。"
|
||||
if got != want {
|
||||
t.Errorf("stripNarration = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripNarration_StripsKeywordHalfStarRoleplay(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := "*旁白:心里有点甜*\n这是正文。"
|
||||
got := stripNarration(in)
|
||||
want := "这是正文。"
|
||||
if got != want {
|
||||
t.Errorf("stripNarration = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripNarration_KeepsPlainEmphasis(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := "这是一段 *强调* 文字,不是动作。"
|
||||
if got := stripNarration(in); got != in {
|
||||
t.Errorf("stripNarration stripped emphasis: %q -> %q", in, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripNarration_MixedStarAndXml(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := "<action>拍拍肩膀</action> *旁白:其实有点开心* 你来了呀。对了 *强调*。"
|
||||
got := stripNarration(in)
|
||||
for _, leaked := range []string{"action", "旁白", "拍拍", "其实有点开心"} {
|
||||
if strings.Contains(got, leaked) {
|
||||
t.Errorf("stripNarration left role-play %q: %q", leaked, got)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(got, "你来了呀") || !strings.Contains(got, "*强调*") {
|
||||
t.Errorf("stripNarration dropped chat/emphasis: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripNarration_OnlyStarActionBecomesEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := stripNarration("*弯起眼睛*"); got != "" {
|
||||
t.Errorf("stripNarration = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripNarration_KeepsLongKeywordlessFullStar(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := "*这是一段很长的全角星号包裹的普通段落,足足有几十个字,超过阈值所以应当被保留而不是被剥掉。*"
|
||||
if got := stripNarration(in); got != in {
|
||||
t.Errorf("stripNarration dropped long full-width star text: %q -> %q", in, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitBubbles_StripsStarRoleplayInParagraphs(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := "<p>*动作:弯起眼睛*当然看得到呀。</p><p>*旁白:心里甜* 好。</p>"
|
||||
got := splitBubbles(in)
|
||||
if len(got) != 2 || got[0] != "当然看得到呀。" {
|
||||
t.Errorf("splitBubbles = %#v", got)
|
||||
}
|
||||
if got[1] != "好。" {
|
||||
t.Errorf("splitBubbles[1] = %q, want 好。", got[1])
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user