summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2026-02-09 09:44:54 +0300
committerGrail Finder <wohilas@gmail.com>2026-02-09 09:44:54 +0300
commit5e7ddea6827765ac56155577cf7dcc809fe1128c (patch)
treefd22ed79772a81ea51ce2c230a594af2ba13af69
parent77ad2a7e7e2c3bade4d949d8eb5c36e0126f4668 (diff)
Enha: change __known_by_char tag to @
-rw-r--r--bot.go6
-rw-r--r--bot_test.go70
-rw-r--r--char-specific-context.md16
-rw-r--r--config.example.toml2
-rw-r--r--main.go21
-rw-r--r--popups.go9
-rw-r--r--sysprompts/alice_bob_carl.json2
7 files changed, 62 insertions, 64 deletions
diff --git a/bot.go b/bot.go
index 8e0e856..7209679 100644
--- a/bot.go
+++ b/bot.go
@@ -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 {
diff --git a/bot_test.go b/bot_test.go
index 1710003..01c3e2c 100644
--- a/bot_test.go
+++ b/bot_test.go
@@ -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
}
diff --git a/char-specific-context.md b/char-specific-context.md
index f06fd75..54fa24e 100644
--- a/char-specific-context.md
+++ b/char-specific-context.md
@@ -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
```
diff --git a/config.example.toml b/config.example.toml
index 82aa5f5..1b466eb 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -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
diff --git a/main.go b/main.go
index 0f2df2e..c112fac 100644
--- a/main.go
+++ b/main.go
@@ -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() {
diff --git a/popups.go b/popups.go
index 559a2aa..3087f2d 100644
--- a/popups.go
+++ b/popups.go
@@ -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 {
diff --git a/sysprompts/alice_bob_carl.json b/sysprompts/alice_bob_carl.json
index d575f93..b822b99 100644
--- a/sysprompts/alice_bob_carl.json
+++ b/sysprompts/alice_bob_carl.json
@@ -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"],