feat: local model auto-reaction on user messages by channel type#181
Conversation
- Added functionality to automatically react to user messages with emojis based on the local AI's evaluation of the message content and channel type. - Introduced `maybeAutoReact` function to handle the decision-making process for emoji reactions. - Updated the `Conversations` component to store the last user message and trigger reactions accordingly. - Enhanced the `tauriCommands` with a new method `openhumanLocalAiShouldReact` to facilitate local model evaluations for reactions. - Updated local AI operations to include reaction decision logic, ensuring efficient and context-aware responses. This feature enhances user engagement by adding a personal touch to interactions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds an automatic emoji reaction flow: frontend tracks pending messages and calls a new Tauri RPC; Tauri forwards to a newly-registered backend controller that prompts the local AI, parses an emoji decision, and returns a ReactionDecision used to dispatch addReaction. Changes
Sequence DiagramsequenceDiagram
participant User
participant Frontend as Conversations.tsx
participant Tauri as Tauri Bridge
participant Backend as openhuman Backend
participant LocalAI as Local AI Model
User->>Frontend: send message
Frontend->>Frontend: addMessageLocal() + set pendingReactionRef
alt local-model branch
Frontend->>Frontend: local-model delivers -> clear pending & call maybeAutoReact
else cloud branch
Frontend->>Tauri: cloud onDone -> consumes pending and calls maybeAutoReact
end
Frontend->>Tauri: openhumanLocalAiShouldReact(message, channelType)
Tauri->>Backend: RPC openhuman.local_ai_should_react(params)
Backend->>LocalAI: prompt for emoji decision
LocalAI-->>Backend: model response (emoji or NONE)
Backend->>Backend: extract_first_emoji() -> ReactionDecision
Backend-->>Tauri: CommandResponse<ReactionDecision>
Tauri-->>Frontend: { should_react, emoji }
alt should_react == true
Frontend->>Frontend: dispatch addReaction(msgId, emoji)
else
Frontend->>Frontend: clear pendingReactionRef
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
app/src/utils/tauriCommands.ts (1)
1376-1396: Please extract this new wrapper out oftauriCommands.ts.This file is already ~2k lines, so adding another feature-specific local-AI type/command here makes the module harder to navigate and keeps pushing past the repo’s ~500-line cap. A dedicated
localAiCommands.tsorlocalAiReactions.tsmodule would keep this surface maintainable.As per coding guidelines "Keep source files at ≤ ~500 lines per file; split modules when growing larger".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/utils/tauriCommands.ts` around lines 1376 - 1396, The new local-AI wrapper (ReactionDecision interface and openhumanLocalAiShouldReact function) should be moved out of tauriCommands.ts into its own module (e.g., localAiCommands.ts or localAiReactions.ts); create the new file, paste the ReactionDecision type and the openhumanLocalAiShouldReact implementation there, export them, and replace the original definitions in tauriCommands.ts with an import of those exports; also update any call sites to import openhumanLocalAiShouldReact and/or ReactionDecision from the new module so behavior is unchanged and tauriCommands.ts stays under the ~500-line guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/pages/Conversations.tsx`:
- Line 131: lastUserMessageRef currently holds a single {id, content, threadId}
and races across retries/cancels; replace it with a per-request correlation
token so onDone only applies to the matching round. Concretely: generate a
unique token (e.g., UUID or incrementing requestId) for each send/round, store
the token and message info in a Map or attach it to the request object instead
of using lastUserMessageRef, pass that token into the onDone callback and have
onDone verify the token matches the current entry before applying/clearing
reactions; update all usages that read/write lastUserMessageRef (including the
onDone handlers referenced) to use the per-request map/token check. Ensure
cancellation/retry updates the current token so stale onDone invocations are
ignored.
- Around line 493-499: The auto-reactions dispatched by
maybeAutoReact/openhumanLocalAiShouldReact are being added to userMessageId but
the Conversations component only renders reactions for agent messages (see
myReactions rendered inside the msg.sender === 'agent' branch), so
auto-reactions are invisible; fix by either rendering myReactions for user
bubbles (add the same myReactions render logic into the user msg.sender !==
'agent' branch) or change maybeAutoReact to target the assistant message instead
(find the corresponding assistant messageId in the same thread/turn and dispatch
addReaction({ threadId, messageId: assistantMessageId, emoji }) rather than
using userMessageId); update references to addReaction, userMessageId, threadId,
maybeAutoReact/openhumanLocalAiShouldReact and the msg.sender === 'agent' render
branch accordingly.
In `@src/openhuman/local_ai/ops.rs`:
- Around line 588-607: The extractor extract_first_emoji currently splits flag
emojis because it only treats additional emoji scalars after a ZWJ as part of
the same emoji; update extract_first_emoji to detect regional indicator symbols
(U+1F1E6..U+1F1FF) and, when the first scalar is a regional indicator, consume
any immediately consecutive regional indicator scalars to form a single emoji
token (do this same fix where the other emoji-scanning logic exists around the
645-676 region). Also add a regression unit test that calls
extract_first_emoji("🇺🇸") and asserts the returned Some("🇺🇸") to prevent
regressions.
- Around line 543-546: The debug call in should_react currently logs the model's
raw output via tracing::debug!(raw_output = trimmed, ...), which can persist
user PII; change the tracing call to avoid logging verbatim model
responses—either remove the raw_output field altogether or log a
redacted/hashed/length-only representation (e.g., first N characters +
"[REDACTED]" or a hash) and keep the same context string; update the
tracing::debug invocation in the should_react path (the lines using
tracing::debug and variable trimmed) accordingly.
---
Nitpick comments:
In `@app/src/utils/tauriCommands.ts`:
- Around line 1376-1396: The new local-AI wrapper (ReactionDecision interface
and openhumanLocalAiShouldReact function) should be moved out of
tauriCommands.ts into its own module (e.g., localAiCommands.ts or
localAiReactions.ts); create the new file, paste the ReactionDecision type and
the openhumanLocalAiShouldReact implementation there, export them, and replace
the original definitions in tauriCommands.ts with an import of those exports;
also update any call sites to import openhumanLocalAiShouldReact and/or
ReactionDecision from the new module so behavior is unchanged and
tauriCommands.ts stays under the ~500-line guideline.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4bb69cf8-382c-431a-9fdc-7945a763a43c
📒 Files selected for processing (4)
app/src/pages/Conversations.tsxapp/src/utils/tauriCommands.tssrc/openhuman/local_ai/ops.rssrc/openhuman/local_ai/schemas.rs
…oji, PII log - Replace lastUserMessageRef with per-thread pendingReactionRef Map so cancellation/retry clears stale entries and onDone only applies to the matching request round - Render reactions on user message bubbles (not just agent messages) so auto-reactions are actually visible; manual picker stays agent-only - Fix extract_first_emoji to consume consecutive regional indicator symbols as a single flag emoji (e.g. 🇺🇸); add regression test - Replace raw_output in should_react debug log with output_len to avoid persisting user PII in traces Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion and TTS - Cleaned up the Conversations component by removing unused imports related to local AI transcription and text-to-speech functionalities, streamlining the codebase and improving maintainability.es
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
app/src/pages/Conversations.tsx (1)
131-133:⚠️ Potential issue | 🟠 MajorPer-thread pending entries still race within the same thread.
Keying the map by
threadIdfixes cross-thread overlap, but a lateonDonefrom an earlier cancelled/retried round can still consume the newer entry for that thread and react to the wrong message. This still needs a per-request/round token thatonDonevalidates before applying or clearing the pending entry.Also applies to: 359-364, 537-541
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/pages/Conversations.tsx` around lines 131 - 133, pendingReactionRef keyed by threadId still allows races within a thread; modify the pending entry shape stored in pendingReactionRef to include a per-request token (e.g., roundId or requestId) when you set a pending reaction in the functions that create entries, then have onDone validate that token before applying or clearing the entry. Specifically, update the Map value type used by pendingReactionRef to include a token, generate a unique token when creating the pending entry (in the functions that currently set msgId/content for a thread), attach that token to responses/promises, and in onDone compare the provided token to the token currently in pendingReactionRef for the threadId—only proceed to react or clear if they match; apply the same token-check pattern to the other occurrences referenced (the other pending-reaction blocks).
🧹 Nitpick comments (2)
app/src/pages/Conversations.tsx (1)
491-509: Extract the auto-reaction flow out ofConversations.This adds request bookkeeping, RPC coordination, and extra render logic to a component that is already very large. A small
useAutoReaction-style hook/helper would make the send lifecycle easier to test and keep this file from growing further. As per coding guidelines "Keep source files at ≤ ~500 lines per file; split modules when growing larger".Also applies to: 537-541, 912-978
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/pages/Conversations.tsx` around lines 491 - 509, Extract the auto-reaction flow into a dedicated hook (e.g., useAutoReaction) to remove request bookkeeping, RPC coordination, and render logic from Conversations: move logic currently in maybeAutoReact that calls openhumanLocalAiShouldReact and dispatches addReaction (and checks isTauri/isLocalModelActiveRef) into the new hook, expose a simple API like triggerAutoReact(threadId, messageId, messageContent) that handles promise lifecycle, deduplication/bookkeeping, and error logging, and replace the inline calls in Conversations (including other occurrences around lines 537-541 and 912-978) with calls to this hook to keep the component small and testable.src/openhuman/local_ai/ops.rs (1)
483-585: Split the reaction-decision code out ofops.rs.This prompt/parser/test bundle is a clean standalone slice of functionality, and
ops.rsis already well past the repo’s file-size target. Moving it into a dedicated local-AI reactions module would keep the controller surface easier to navigate and test. As per coding guidelines "Keep source files at ≤ ~500 lines per file; split modules when growing larger".Also applies to: 587-704
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/openhuman/local_ai/ops.rs` around lines 483 - 585, The reaction decision logic (ReactionDecision struct and local_ai_should_react function, plus extract_first_emoji usage and its tests) should be moved out of ops.rs into a new module (e.g., local_ai::reactions) to keep ops.rs small: create a new file/module containing the ReactionDecision definition, the local_ai_should_react async function (preserving the prompt, service usage, and error handling), and any unit tests that exercise this behavior; update mod declarations and add the necessary use imports (RpcOutcome, Config, local_ai::global, extract_first_emoji or move its implementation alongside if tightly coupled), then replace the original code in ops.rs with a concise call to local_ai::reactions::local_ai_should_react (and apply the same split for the related block referenced at 587-704). Ensure public visibility where needed and run tests to confirm no missing imports or visibility issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/pages/Conversations.tsx`:
- Around line 128-130: The socket callback captures a stale defaultChannelType
because subscribeChatEvents only re-subscribes on rustChat/socketStatus; update
the effect so maybeAutoReact sees current defaultChannelType by either adding
defaultChannelType (from useAppSelector) to the subscribeChatEvents effect
dependency array or by reading defaultChannelType from a ref inside
maybeAutoReact (create a ref updated on selector changes and reference
ref.current in maybeAutoReact). Ensure the change targets the
subscribeChatEvents effect and the maybeAutoReact/onDone callback so
auto-reactions always use the latest defaultChannelType.
In `@src/openhuman/local_ai/ops.rs`:
- Around line 529-538: The prompt builder currently uses the full user message
variable when constructing the reaction prompt (the prompt string and call to
service.prompt in ops.rs), which can feed very large content into Ollama;
truncate the message to a small character budget (e.g., 256–512 chars) before
formatting the prompt—create or use a helper like
truncate_message(original_message, max_chars) that produces a shorter string
(append an ellipsis when truncated) and pass that truncated value into format!
so the prompt and subsequent service.prompt(config, &prompt, ...) call only
include the bounded excerpt.
- Around line 589-604: extract_first_emoji currently consumes all consecutive
regional indicators, so adjacent flags like "🇺🇸🇨🇦" are returned as one
reaction; change the regional-indicator handling in extract_first_emoji to
collect at most two regional indicators (a single pair) and then stop/return
immediately (i.e., if the first char is a regional indicator, optionally consume
one more regional indicator to form the flag and return that string), and add a
regression test asserting extract_first_emoji("🇺🇸🇨🇦") ==
Some("🇺🇸".to_string()); apply the same one-or-two regional-indicator limit to
the other similar regional-indicator handling block in this file.
---
Duplicate comments:
In `@app/src/pages/Conversations.tsx`:
- Around line 131-133: pendingReactionRef keyed by threadId still allows races
within a thread; modify the pending entry shape stored in pendingReactionRef to
include a per-request token (e.g., roundId or requestId) when you set a pending
reaction in the functions that create entries, then have onDone validate that
token before applying or clearing the entry. Specifically, update the Map value
type used by pendingReactionRef to include a token, generate a unique token when
creating the pending entry (in the functions that currently set msgId/content
for a thread), attach that token to responses/promises, and in onDone compare
the provided token to the token currently in pendingReactionRef for the
threadId—only proceed to react or clear if they match; apply the same
token-check pattern to the other occurrences referenced (the other
pending-reaction blocks).
---
Nitpick comments:
In `@app/src/pages/Conversations.tsx`:
- Around line 491-509: Extract the auto-reaction flow into a dedicated hook
(e.g., useAutoReaction) to remove request bookkeeping, RPC coordination, and
render logic from Conversations: move logic currently in maybeAutoReact that
calls openhumanLocalAiShouldReact and dispatches addReaction (and checks
isTauri/isLocalModelActiveRef) into the new hook, expose a simple API like
triggerAutoReact(threadId, messageId, messageContent) that handles promise
lifecycle, deduplication/bookkeeping, and error logging, and replace the inline
calls in Conversations (including other occurrences around lines 537-541 and
912-978) with calls to this hook to keep the component small and testable.
In `@src/openhuman/local_ai/ops.rs`:
- Around line 483-585: The reaction decision logic (ReactionDecision struct and
local_ai_should_react function, plus extract_first_emoji usage and its tests)
should be moved out of ops.rs into a new module (e.g., local_ai::reactions) to
keep ops.rs small: create a new file/module containing the ReactionDecision
definition, the local_ai_should_react async function (preserving the prompt,
service usage, and error handling), and any unit tests that exercise this
behavior; update mod declarations and add the necessary use imports (RpcOutcome,
Config, local_ai::global, extract_first_emoji or move its implementation
alongside if tightly coupled), then replace the original code in ops.rs with a
concise call to local_ai::reactions::local_ai_should_react (and apply the same
split for the related block referenced at 587-704). Ensure public visibility
where needed and run tests to confirm no missing imports or visibility issues.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: da59ee5f-a611-4de9-a6da-2df54338db2d
📒 Files selected for processing (2)
app/src/pages/Conversations.tsxsrc/openhuman/local_ai/ops.rs
| const defaultChannelType = useAppSelector( | ||
| state => state.channelConnections?.defaultMessagingChannel ?? 'web' | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
wc -l app/src/pages/Conversations.tsxRepository: tinyhumansai/openhuman
Length of output: 102
🏁 Script executed:
cat -n app/src/pages/Conversations.tsx | sed -n '120,140p'Repository: tinyhumansai/openhuman
Length of output: 1065
🏁 Script executed:
# Find the subscribeChatEvents effect to understand its dependencies
rg -n "subscribeChatEvents" app/src/pages/Conversations.tsx -A 20 -B 5Repository: tinyhumansai/openhuman
Length of output: 1795
🏁 Script executed:
# Also check around line 498-499 mentioned
cat -n app/src/pages/Conversations.tsx | sed -n '490,510p'Repository: tinyhumansai/openhuman
Length of output: 862
🏁 Script executed:
# Look for onDone callback usage
rg -n "onDone" app/src/pages/Conversations.tsx -A 5 -B 5Repository: tinyhumansai/openhuman
Length of output: 1122
🏁 Script executed:
# Get the full subscribeChatEvents effect and its dependency array
cat -n app/src/pages/Conversations.tsx | sed -n '320,450p'Repository: tinyhumansai/openhuman
Length of output: 5630
🏁 Script executed:
# Search specifically for defaultChannelType usage in the file
rg -n "defaultChannelType" app/src/pages/Conversations.tsx -A 3 -B 3Repository: tinyhumansai/openhuman
Length of output: 828
🏁 Script executed:
# Get the end of the subscribeChatEvents effect to see the dependency array
cat -n app/src/pages/Conversations.tsx | sed -n '450,470p'Repository: tinyhumansai/openhuman
Length of output: 987
🏁 Script executed:
# Find the dependency array of the subscribeChatEvents effect
cat -n app/src/pages/Conversations.tsx | sed -n '470,490p'Repository: tinyhumansai/openhuman
Length of output: 794
🏁 Script executed:
# Check if maybeAutoReact is defined and where it uses defaultChannelType
cat -n app/src/pages/Conversations.tsx | sed -n '523,540p'Repository: tinyhumansai/openhuman
Length of output: 949
🏁 Script executed:
# Look for other potential uses of defaultChannelType in callbacks
rg -n "defaultChannelType" app/src/pages/Conversations.tsxRepository: tinyhumansai/openhuman
Length of output: 191
🏁 Script executed:
# Double-check what's actually at lines 498-499 in context
cat -n app/src/pages/Conversations.tsx | sed -n '495,505p'Repository: tinyhumansai/openhuman
Length of output: 430
defaultChannelType goes stale in the socket callback due to missing dependency.
The subscribeChatEvents effect only re-subscribes on rustChat / socketStatus (line 485), so onDone keeps the maybeAutoReact function from when the effect was created. Since maybeAutoReact reads defaultChannelType directly (line 526), if the user changes the default channel while this page stays mounted, auto-reactions will use the stale channel value until reconnect/remount.
Suggested fix
const defaultChannelType = useAppSelector(
state => state.channelConnections?.defaultMessagingChannel ?? 'web'
);
+ const defaultChannelTypeRef = useRef(defaultChannelType);
const pendingReactionRef = useRef<
Map<string, { msgId: string; content: string; threadId: string }>
>(new Map());
+ useEffect(() => {
+ defaultChannelTypeRef.current = defaultChannelType;
+ }, [defaultChannelType]);
+
const maybeAutoReact = (userMessageId: string, messageContent: string, threadId: string) => {
if (!isTauri() || !isLocalModelActiveRef.current) return;
- void openhumanLocalAiShouldReact(messageContent, defaultChannelType)
+ void openhumanLocalAiShouldReact(messageContent, defaultChannelTypeRef.current)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src/pages/Conversations.tsx` around lines 128 - 130, The socket callback
captures a stale defaultChannelType because subscribeChatEvents only
re-subscribes on rustChat/socketStatus; update the effect so maybeAutoReact sees
current defaultChannelType by either adding defaultChannelType (from
useAppSelector) to the subscribeChatEvents effect dependency array or by reading
defaultChannelType from a ref inside maybeAutoReact (create a ref updated on
selector changes and reference ref.current in maybeAutoReact). Ensure the change
targets the subscribeChatEvents effect and the maybeAutoReact/onDone callback so
auto-reactions always use the latest defaultChannelType.
| let prompt = format!( | ||
| "You decide whether an AI assistant should react to a user message with a single emoji. \ | ||
| Consider the channel context: casual channels (discord, telegram) get more frequent \ | ||
| reactions with playful emojis, while professional channels (web, slack, email) are more \ | ||
| reserved — only react to clearly emotional or noteworthy messages.\n\n\ | ||
| Channel: {channel_type}\nUser message: {message}\n\n\ | ||
| Reply with EXACTLY one word: either NONE (no reaction) or a single emoji character." | ||
| ); | ||
|
|
||
| let output = service.prompt(config, &prompt, Some(8), true).await; |
There was a problem hiding this comment.
Bound the message excerpt before this reaction prompt.
This path is meant to be lightweight, but it currently feeds the full user message back through Ollama. Large pasted content turns auto-reactions into another full-context inference and can contend with the next local task. Truncate to a small character budget before building the prompt.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/openhuman/local_ai/ops.rs` around lines 529 - 538, The prompt builder
currently uses the full user message variable when constructing the reaction
prompt (the prompt string and call to service.prompt in ops.rs), which can feed
very large content into Ollama; truncate the message to a small character budget
(e.g., 256–512 chars) before formatting the prompt—create or use a helper like
truncate_message(original_message, max_chars) that produces a shorter string
(append an ellipsis when truncated) and pass that truncated value into format!
so the prompt and subsequent service.prompt(config, &prompt, ...) call only
include the bounded excerpt.
| fn extract_first_emoji(text: &str) -> Option<String> { | ||
| let mut chars = text.chars(); | ||
| while let Some(ch) = chars.next() { | ||
| // Regional indicator pair → flag emoji (e.g. 🇺🇸 = U+1F1FA U+1F1F8) | ||
| if is_regional_indicator(ch) { | ||
| let mut emoji = String::new(); | ||
| emoji.push(ch); | ||
| // Consume consecutive regional indicators (flags are pairs) | ||
| for next in chars.by_ref() { | ||
| if is_regional_indicator(next) { | ||
| emoji.push(next); | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| return Some(emoji); |
There was a problem hiding this comment.
Adjacent flags are still parsed as one reaction.
extract_first_emoji now keeps a flag pair together, but it also keeps consuming every following regional indicator. extract_first_emoji("🇺🇸🇨🇦") will return both flags instead of the first emoji. Stop after the first pair and add a regression test for adjacent flags.
Suggested fix
-fn extract_first_emoji(text: &str) -> Option<String> {
- let mut chars = text.chars();
+fn extract_first_emoji(text: &str) -> Option<String> {
+ let mut chars = text.chars().peekable();
while let Some(ch) = chars.next() {
// Regional indicator pair → flag emoji (e.g. 🇺🇸 = U+1F1FA U+1F1F8)
if is_regional_indicator(ch) {
let mut emoji = String::new();
emoji.push(ch);
- // Consume consecutive regional indicators (flags are pairs)
- for next in chars.by_ref() {
- if is_regional_indicator(next) {
- emoji.push(next);
- } else {
- break;
- }
+ if let Some(next) = chars.peek().copied() {
+ if is_regional_indicator(next) {
+ emoji.push(next);
+ chars.next();
+ return Some(emoji);
+ }
}
- return Some(emoji);
+ continue;
} fn extract_flag_emoji_keeps_pair_together() {
assert_eq!(extract_first_emoji("🇺🇸"), Some("🇺🇸".to_string()));
assert_eq!(
extract_first_emoji("🇬🇧 Great Britain"),
Some("🇬🇧".to_string())
);
+ assert_eq!(
+ extract_first_emoji("🇺🇸🇨🇦"),
+ Some("🇺🇸".to_string())
+ );
}Also applies to: 687-693
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/openhuman/local_ai/ops.rs` around lines 589 - 604, extract_first_emoji
currently consumes all consecutive regional indicators, so adjacent flags like
"🇺🇸🇨🇦" are returned as one reaction; change the regional-indicator handling
in extract_first_emoji to collect at most two regional indicators (a single
pair) and then stop/return immediately (i.e., if the first char is a regional
indicator, optionally consume one more regional indicator to form the flag and
return that string), and add a regression test asserting
extract_first_emoji("🇺🇸🇨🇦") == Some("🇺🇸".to_string()); apply the same
one-or-two regional-indicator limit to the other similar regional-indicator
handling block in this file.
…yhumansai#181) * feat(conversations): implement auto-reaction feature for user messages - Added functionality to automatically react to user messages with emojis based on the local AI's evaluation of the message content and channel type. - Introduced `maybeAutoReact` function to handle the decision-making process for emoji reactions. - Updated the `Conversations` component to store the last user message and trigger reactions accordingly. - Enhanced the `tauriCommands` with a new method `openhumanLocalAiShouldReact` to facilitate local model evaluations for reactions. - Updated local AI operations to include reaction decision logic, ensuring efficient and context-aware responses. This feature enhances user engagement by adding a personal touch to interactions. * style: fix prettier formatting in Conversations.tsx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply cargo fmt to ops.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — race condition, invisible reactions, flag emoji, PII log - Replace lastUserMessageRef with per-thread pendingReactionRef Map so cancellation/retry clears stale entries and onDone only applies to the matching request round - Render reactions on user message bubbles (not just agent messages) so auto-reactions are actually visible; manual picker stays agent-only - Fix extract_first_emoji to consume consecutive regional indicator symbols as a single flag emoji (e.g. 🇺🇸); add regression test - Replace raw_output in should_react debug log with output_len to avoid persisting user PII in traces Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply cargo fmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(conversations): remove unused imports for local AI transcription and TTS - Cleaned up the Conversations component by removing unused imports related to local AI transcription and text-to-speech functionalities, streamlining the codebase and improving maintainability.es --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
local_ai_should_react) that uses the Ollama model to decide whether to auto-react to a user's message with an emoji, based on channel typeChanges
src/openhuman/local_ai/ops.rs): newlocal_ai_should_reactoperation withReactionDecisionreturn type, emoji extraction helpers, and unit testssrc/openhuman/local_ai/schemas.rs): registered controller schema and handler forlocal_ai_should_reactapp/src/utils/tauriCommands.ts): newopenhumanLocalAiShouldReact()bindingapp/src/pages/Conversations.tsx):maybeAutoReact()helper called after both local and cloud response pathsTest plan
cargo test -- local_ai::ops)cargo checkpasses cleantsc --noEmitpasses clean🤖 Generated with Claude Code
Summary by CodeRabbit