Enha: change __known_by_char tag to @

This commit is contained in:
Grail Finder
2026-02-09 09:44:54 +03:00
parent 77ad2a7e7e
commit 5e7ddea682
7 changed files with 62 additions and 64 deletions

6
bot.go
View File

@@ -76,10 +76,10 @@ func parseKnownToTag(content string) []string {
}
tag := cfg.CharSpecificContextTag
if tag == "" {
tag = "__known_to_chars__"
tag = "@"
}
// Pattern: tag + list + "__"
pattern := regexp.QuoteMeta(tag) + `(.*?)__`
// Pattern: tag + list + "@"
pattern := regexp.QuoteMeta(tag) + `(.*?)@`
re := regexp.MustCompile(pattern)
matches := re.FindAllStringSubmatch(content, -1)
if len(matches) == 0 {

View File

@@ -299,81 +299,81 @@ func TestParseKnownToTag(t *testing.T) {
}{
{
name: "feature disabled returns original",
content: "Hello __known_to_chars__Alice__",
content: "Hello @Alice@",
enabled: false,
tag: "__known_to_chars__",
wantCleaned: "Hello __known_to_chars__Alice__",
tag: "@",
wantCleaned: "Hello @Alice@",
wantKnownTo: nil,
},
{
name: "no tag returns original",
content: "Hello Alice",
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantCleaned: "Hello Alice",
wantKnownTo: nil,
},
{
name: "single tag with one char",
content: "Hello __known_to_chars__Alice__",
content: "Hello @Alice@",
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantCleaned: "Hello",
wantKnownTo: []string{"Alice"},
},
{
name: "single tag with two chars",
content: "Secret __known_to_chars__Alice,Bob__ message",
content: "Secret @Alice,Bob@ message",
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantCleaned: "Secret message",
wantKnownTo: []string{"Alice", "Bob"},
},
{
name: "tag at beginning",
content: "__known_to_chars__Alice__ Hello",
content: "@Alice@ Hello",
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantCleaned: "Hello",
wantKnownTo: []string{"Alice"},
},
{
name: "tag at end",
content: "Hello __known_to_chars__Alice__",
content: "Hello @Alice@",
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantCleaned: "Hello",
wantKnownTo: []string{"Alice"},
},
{
name: "multiple tags",
content: "First __known_to_chars__Alice__ then __known_to_chars__Bob__",
content: "First @Alice@ then @Bob@",
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantCleaned: "First then",
wantKnownTo: []string{"Alice", "Bob"},
},
{
name: "custom tag",
content: "Secret __secret__Alice,Bob__ message",
content: "Secret @Alice,Bob@ message",
enabled: true,
tag: "__secret__",
tag: "@",
wantCleaned: "Secret message",
wantKnownTo: []string{"Alice", "Bob"},
},
{
name: "empty list",
content: "Secret __known_to_chars____",
content: "Secret @@@",
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantCleaned: "Secret",
wantKnownTo: nil,
},
{
name: "whitespace around commas",
content: "__known_to_chars__ Alice , Bob , Carl __",
content: "@ Alice , Bob , Carl @",
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantCleaned: "",
wantKnownTo: []string{"Alice", "Bob", "Carl"},
},
@@ -415,13 +415,13 @@ func TestProcessMessageTag(t *testing.T) {
name: "feature disabled returns unchanged",
msg: models.RoleMsg{
Role: "Alice",
Content: "Secret __known_to_chars__Bob__",
Content: "Secret @Bob@",
},
enabled: false,
tag: "__known_to_chars__",
tag: "@",
wantMsg: models.RoleMsg{
Role: "Alice",
Content: "Secret __known_to_chars__Bob__",
Content: "Secret @Bob@",
KnownTo: nil,
},
},
@@ -432,7 +432,7 @@ func TestProcessMessageTag(t *testing.T) {
Content: "Hello everyone",
},
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantMsg: models.RoleMsg{
Role: "Alice",
Content: "Hello everyone",
@@ -443,10 +443,10 @@ func TestProcessMessageTag(t *testing.T) {
name: "tag with Bob, adds Alice automatically",
msg: models.RoleMsg{
Role: "Alice",
Content: "Secret __known_to_chars__Bob__",
Content: "Secret @Bob@",
},
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantMsg: models.RoleMsg{
Role: "Alice",
Content: "Secret",
@@ -457,10 +457,10 @@ func TestProcessMessageTag(t *testing.T) {
name: "tag already includes sender",
msg: models.RoleMsg{
Role: "Alice",
Content: "__known_to_chars__Alice,Bob__",
Content: "@Alice,Bob@",
},
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantMsg: models.RoleMsg{
Role: "Alice",
Content: "",
@@ -471,11 +471,11 @@ func TestProcessMessageTag(t *testing.T) {
name: "knownTo already set (from DB), tag still processed",
msg: models.RoleMsg{
Role: "Alice",
Content: "Secret __known_to_chars__Bob__",
Content: "Secret @Bob@",
KnownTo: []string{"Alice"}, // from previous processing
},
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantMsg: models.RoleMsg{
Role: "Alice",
Content: "Secret",
@@ -486,14 +486,14 @@ func TestProcessMessageTag(t *testing.T) {
name: "example from real use",
msg: models.RoleMsg{
Role: "Alice",
Content: "I'll start with a simple one! The word is 'banana'. (ooc: __known_to_chars__Bob__)",
Content: "I'll start with a simple one! The word is 'banana'. (ooc: @Bob@)",
KnownTo: []string{"Alice"}, // from previous processing
},
enabled: true,
tag: "__known_to_chars__",
tag: "@",
wantMsg: models.RoleMsg{
Role: "Alice",
Content: "I'll start with a simple one! The word is 'banana'. (ooc: __known_to_chars__Bob__)",
Content: "I'll start with a simple one! The word is 'banana'. (ooc: @Bob@)",
KnownTo: []string{"Bob", "Alice"},
},
},
@@ -588,7 +588,7 @@ func TestFilterMessagesForCharacter(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
testCfg := &config.Config{
CharSpecificContextEnabled: tt.enabled,
CharSpecificContextTag: "__known_to_chars__",
CharSpecificContextTag: "@",
}
cfg = testCfg
@@ -640,7 +640,7 @@ func TestKnownToFieldPreservationScenario(t *testing.T) {
// Test the specific scenario from the log where KnownTo field was getting lost
originalMsg := models.RoleMsg{
Role: "Alice",
Content: `Alice: "Okay, Bob. The word is... **'Ephemeral'**. (ooc: __known_to_chars__Bob__)"`,
Content: `Alice: "Okay, Bob. The word is... **'Ephemeral'**. (ooc: @Bob@)"`,
KnownTo: []string{"Bob"}, // This was detected in the log
}

View File

@@ -12,16 +12,16 @@ Character-Specific Context is a feature that enables private communication betwe
### Tagging Messages
Messages can be tagged with a special string (by default `__known_to_chars__`) followed by a comma-separated list of character names. The tag can appear anywhere in the message content. **After csv of characters tag should be closed with `__` (for regexp to know where it ends).**
Messages can be tagged with a special string (by default `@`) followed by a comma-separated list of character names. The tag can appear anywhere in the message content. **After csv of characters tag should be closed with `@` (for regexp to know where it ends).**
**Example:**
```
Alice: __known_to_chars__Bob__ Can you keep a secret?
Alice: @Bob@ Can you keep a secret?
```
**To avoid breaking immersion, it is better to place the tag in (ooc:)**
```
Alice: (ooc: __known_to_chars__Bob__) Can you keep a secret?
Alice: (ooc: @Bob@) Can you keep a secret?
```
This message will be visible only to Alice (the sender) and Bob. The tag is parsed by `parseKnownToTag` and the resulting list of character names is stored in the `KnownTo` field of the message (`RoleMsg`). The sender is automatically added to the `KnownTo` list (if not already present) by `processMessageTag`.
@@ -44,7 +44,7 @@ The filtered history is then used to construct the prompt sent to the LLM. This
Two configuration settings control this feature:
- `CharSpecificContextEnabled` boolean; enables or disables the feature globally.
- `CharSpecificContextTag` string; the tag used to mark private messages. Default is `__known_to_chars__`.
- `CharSpecificContextTag` string; the tag used to mark private messages. Default is `@`.
These are set in `config.toml` (see `config.example.toml` for the default values).
@@ -62,7 +62,7 @@ These are set in `config.toml` (see `config.example.toml` for the default values
Alice wants to tell Bob something without Carl knowing:
```
Alice: __known_to_chars__Bob__ Meet me at the library tonight.
Alice: @Bob@ Meet me at the library tonight.
```
Result:
@@ -75,7 +75,7 @@ Result:
Alice shares a secret with Bob and Carl, but not David:
```
Alice: (ooc: __known_to_chars__Bob,Carl__) The treasure is hidden under the old oak.
Alice: (ooc: @Bob,Carl@) The treasure is hidden under the old oak.
```
### Public Message
@@ -116,7 +116,7 @@ So far only json format supports multiple characters.
Card example:
```
{
"sys_prompt": "This is a chat between Alice, Bob and Carl. Normally what is said by any character is seen by all others. But characters also might write messages intended to specific targets if their message contain string tag '__known_to_chars__{CharName1,CharName2,CharName3}__'.\nFor example:\nAlice:\n\"Hey, Bob. I have a secret for you... (ooc: __known_to_chars__Bob__)\"\nThis message would be seen only by Bob and Alice (sender always sees their own message).",
"sys_prompt": "This is a chat between Alice, Bob and Carl. Normally what is said by any character is seen by all others. But characters also might write messages intended to specific targets if their message contain string tag '@{CharName1,CharName2,CharName3}@'.\nFor example:\nAlice:\n\"Hey, Bob. I have a secret for you... (ooc: @Bob@)\"\nThis message would be seen only by Bob and Alice (sender always sees their own message).",
"role": "Alice",
"filepath": "sysprompts/alice_bob_carl.json",
"chars": ["Alice", "Bob", "Carl"],
@@ -147,6 +147,6 @@ The `KnownTo` field is stored as a JSON array in the database. Existing messages
```toml
CharSpecificContextEnabled = true
CharSpecificContextTag = "__known_to_chars__"
CharSpecificContextTag = "@"
AutoTurn = false
```

View File

@@ -46,5 +46,5 @@ FilePickerExts = "png,jpg,jpeg,gif,webp" # Comma-separated list of allowed file
EnableMouse = false # Enable mouse support in the UI
# character specific context
CharSpecificContextEnabled = true
CharSpecificContextTag = "__known_to_chars__"
CharSpecificContextTag = "@"
AutoTurn = true

21
main.go
View File

@@ -8,18 +8,15 @@ import (
)
var (
boolColors = map[bool]string{true: "green", false: "red"}
botRespMode = false
editMode = false
roleEditMode = false
injectRole = true
selectedIndex = int(-1)
currentAPIIndex = 0 // Index to track current API in ApiLinks slice
currentORModelIndex = 0 // Index to track current OpenRouter model in ORFreeModels slice
currentLocalModelIndex = 0 // Index to track current llama.cpp model
shellMode = false
indexLineCompletion = "F12 to show keys help | llm turn: [%s:-:b]%v[-:-:-] (F6) | chat: [orange:-:b]%s[-:-:-] (F1) | toolUseAdviced: [%s:-:b]%v[-:-:-] (ctrl+k) | model: [orange:-:b]%s[-:-:-] (ctrl+l) | skip LLM resp: [%s:-:b]%v[-:-:-] (F10)\nAPI: [orange:-:b]%s[-:-:-] (ctrl+v) | recording: [%s:-:b]%v[-:-:-] (ctrl+r) | writing as: [orange:-:b]%s[-:-:-] (ctrl+q) | bot will write as [orange:-:b]%s[-:-:-] (ctrl+x) | role injection (alt+7) [%s:-:b]%v[-:-:-]"
focusSwitcher = map[tview.Primitive]tview.Primitive{}
boolColors = map[bool]string{true: "green", false: "red"}
botRespMode = false
editMode = false
roleEditMode = false
injectRole = true
selectedIndex = int(-1)
shellMode = false
indexLineCompletion = "F12 to show keys help | llm turn: [%s:-:b]%v[-:-:-] (F6) | chat: [orange:-:b]%s[-:-:-] (F1) | toolUseAdviced: [%s:-:b]%v[-:-:-] (ctrl+k) | model: [orange:-:b]%s[-:-:-] (ctrl+l) | skip LLM resp: [%s:-:b]%v[-:-:-] (F10)\nAPI: [orange:-:b]%s[-:-:-] (ctrl+v) | recording: [%s:-:b]%v[-:-:-] (ctrl+r) | writing as: [orange:-:b]%s[-:-:-] (ctrl+q) | bot will write as [orange:-:b]%s[-:-:-] (ctrl+x) | role injection (alt+7) [%s:-:b]%v[-:-:-]"
focusSwitcher = map[tview.Primitive]tview.Primitive{}
)
func main() {

View File

@@ -28,12 +28,13 @@ func showModelSelectionPopup() {
// Check for empty options list
if len(modelList) == 0 {
logger.Warn("empty model list for", "api", cfg.CurrentAPI, "localModelsLen", len(LocalModels), "orModelsLen", len(ORFreeModels))
message := "No models available for selection"
if strings.Contains(cfg.CurrentAPI, "openrouter.ai") {
var message string
switch {
case strings.Contains(cfg.CurrentAPI, "openrouter.ai"):
message = "No OpenRouter models available. Check token and connection."
} else if strings.Contains(cfg.CurrentAPI, "api.deepseek.com") {
case strings.Contains(cfg.CurrentAPI, "api.deepseek.com"):
message = "DeepSeek models should be available. Please report bug."
} else {
default:
message = "No llama.cpp models loaded. Ensure llama.cpp server is running with models."
}
if err := notifyUser("Empty list", message); err != nil {

View File

@@ -1,5 +1,5 @@
{
"sys_prompt": "This is a chat between Alice, Bob and Carl. Normally what is said by any character is seen by all others. But characters also might write messages intended to specific targets if their message contain string tag '__known_to_chars__{CharName1,CharName2,CharName3}__'.\nFor example:\nAlice:\n\"Hey, Bob. I have a secret for you... (ooc: __known_to_chars__Bob__)\"\nThis message would be seen only by Bob and Alice (sender always sees their own message).",
"sys_prompt": "This is a chat between Alice, Bob and Carl. Normally what is said by any character is seen by all others. But characters also might write messages intended to specific targets if their message contain string tag '@{CharName1,CharName2,CharName3}@'.\nFor example:\nAlice:\n\"Hey, Bob. I have a secret for you... (ooc: @Bob@)\"\nThis message would be seen only by Bob and Alice (sender always sees their own message).",
"role": "Alice",
"filepath": "sysprompts/alice_bob_carl.json",
"chars": ["Alice", "Bob", "Carl"],