* feat(internal-chat): implement internal chat system for agents (Phase 1+2 MVP)
Add a Slack/Discord-style internal messaging system for Chatwoot agents with
text channels (public/private), direct messages, reactions, typing indicators,
and real-time updates via ActionCable.
Backend:
- 6 database migrations (categories, channels, members, messages, attachments, reactions)
- 6 models under InternalChat:: namespace with validations and associations
- API controllers for categories, channels, messages, members, and reactions
- Pundit policies for authorization (public/private/DM access control)
- MessageCreateService, TypingStatusManager, DefaultChannelSetupService
- InternalChatListener for real-time broadcasting to channel members
- Event types for internal chat events
- Default category/channel setup for new and existing accounts
Frontend:
- Vuex store modules for channels, messages, and typing status
- API clients for channels and messages
- Vue 3 components: InternalChatLayout, ChannelSidebar, ChannelView,
ChannelHeader, MessageList, MessageBubble, MessageEditor,
EmojiReactionPicker, ReactionDisplay, TypingIndicator
- Sidebar integration with "Internal Chat" menu item
- ActionCable handlers for real-time message/reaction/typing events
- Route definitions and i18n translations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(internal-chat): add comprehensive specs for models, controllers, policies, services, and listener
- 6 model specs (74 examples) covering associations, validations, scopes, methods
- 5 request specs for all API controllers (categories, channels, messages, members, reactions)
- 4 policy specs testing authorization rules for all actions
- 3 service specs (DefaultChannelSetupService, MessageCreateService, TypingStatusManager)
- 1 listener spec testing real-time broadcasting for all event types
- 6 FactoryBot factories with traits for all InternalChat models
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): fix dispatcher mock in service specs and cursor pagination test
- Allow dispatcher.dispatch in service specs to handle Account.created
callbacks from factory setup before asserting specific event dispatch
- Fix after-cursor pagination test by adding 1 second offset to avoid
timestamp precision issues with iso8601 rounding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): address CodeRabbit review — 7 critical security/correctness fixes
- Scope member creation through Current.account.users to prevent cross-account membership
- Scope member_ids in DM creation through Current.account to prevent cross-account invites
- Scope reaction message lookup through channel account to prevent cross-account access
- Fix Vuex store to commit messages array instead of response envelope
- Add UUID generation callback on Channel model (before_validation)
- Add channel access check to reaction deletion policy
- Validate parent_id belongs to same channel in MessageCreateService
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): address CodeRabbit round 2 + fix ChannelSidebar runtime error
- Re-throw error in fetchMessages instead of swallowing with empty array
- Wrap message + attachment creation in transaction for atomicity
- Fix factory to derive account from message (prevent cross-account fixtures)
- Guard listener against cross-account mismatch (not just missing records)
- Add cross-account regression tests to listener spec
- Fix ChannelSidebar computed properties to default to empty arrays
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): auto-setup default channels on account creation and migration
- Add after_create_commit :setup_internal_chat callback on Account model
- Add data migration to create default channels for existing accounts
- Make DefaultChannelSetupService convergent (find_or_create) instead of
bail-on-exists, so it can sync new members on subsequent runs
- Fix specs to handle default category/channel created by callback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): avoid Vuex state mutation in sort + align muted styling in fallback section
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): fix store module name mismatch — register as 'internalChat' not 'internalChatChannels'
Components dispatch to 'internalChat/get' but the module was registered
as 'internalChatChannels'. Also fix ActionCable handlers to use
'internalChat/messages/' nested module path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* i18n(internal-chat): add pt-BR translations for internal chat feature
Backend: default_category_name ('Canais') and default_channel_name ('Geral')
Frontend: all 40+ keys translated to Brazilian Portuguese
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): handle ISO 8601 timestamps in MessageBubble and MessageList
The API returns created_at as ISO strings but messageTimestamp() expects
Unix seconds and MessageList used `* 1000`. Now handles both formats.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(internal-chat): build swagger output for internal chat API endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(internal-chat): register internal chat tags and paths in swagger index
Add tag definitions and path entries for all 5 internal chat resource
groups in swagger/index.yml and swagger/paths/index.yml. Rebuild output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* i18n(internal-chat): add SIDEBAR.INTERNAL_CHAT key to pt-BR settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): comprehensive review fixes — backend and frontend
Backend:
- Add attachments to message API responses in both controllers
- Add internal_chat_channel_updated listener handler
- Include reactions in message event broadcast data
Frontend:
- Fix ActionCable dispatch paths to use correct action names
(addMessageFromCable, updateMessageFromCable, deleteMessageFromCable)
- Connect typingUsers to internalChatTypingStatus store getter
- Fix message field references (edited → content_attributes.edited_at)
- Fix channel type comparisons (use 'private_channel'/'dm' strings)
- Add parent 'internal_chat' to sidebar activeOn array
- Increment unread_count on ActionCable message receive
- Add loadMore handler for cursor-based pagination
- Remove unused is-direct-message prop from InternalChatLayout
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): implement phases 3-5 — threads, mentions, notifications, polls, drafts
Phase 3 — Threads, Mentions, Notifications:
- MentionService: parse @user mentions, @all (admin only), generate notifications
- NotificationService: create notifications for channel messages (respects mute)
- Add internal_chat_message/mention notification types to Notification model
- ThreadPanel.vue: slide-out panel for threaded replies
- Integrate mentions + notifications into MessageCreateService
Phase 4 — Polls:
- 3 new migrations: polls, poll_options, poll_votes tables
- 3 new models: Poll, PollOption, PollVote with validations
- PollsController: create poll, vote, unvote with routes
- PollService: voting logic with multiple choice + revote support
- PollCreator.vue: modal for creating polls with options
- PollDisplay.vue: vote UI with progress bars and results
- Polls Vuex store module
- INTERNAL_CHAT_POLL_VOTED event type
Phase 5 — Drafts:
- 1 new migration: drafts table
- Draft model with validations
- DraftsController: full CRUD (replace stub)
- DraftsList.vue: list all user drafts with navigation
- Drafts Vuex store module with auto-save
- Draft route and sidebar integration
Phase 6 — Feature Flag:
- Add INTERNAL_CHAT feature flag to features.yml and featureFlags.js
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): fix API routing for drafts and polls, add poll voting ActionCable handler
- Fix draft API client to use channel-scoped PATCH/DELETE endpoints
- Create dedicated polls API client with correct poll-based endpoints
- Update polls store to use InternalChatPollsAPI with pollId-based voting
- Add ActionCable handler for internal_chat.poll.voted events
- Add thread and drafts routes to sidebar activeOn array
- Fix drafts store to pass channelId to API calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): fix poll response format and API client routing (review round 2)
- Return message with embedded poll data instead of raw poll response
- Add poll data to message_response in messages controller
- Create dedicated InternalChatPollsAPI client with correct endpoints
- Update PollDisplay.vue to read from message.poll or content_attributes.poll
- Use option.voted flag instead of checking voters array
- Add missing PERCENTAGE i18n key to pt-BR
- Remove unused currentUserId prop from PollDisplay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): fix poll voting and draft lookup bugs (review round 3)
- Fix draft getter to use internal_chat_channel_id field name
- Split poll set_poll into vote/unvote variants — unvote doesn't need option_id
- Unvote finds user's vote by user_id across all poll options
- Fix ChannelView to extract pollId from message.poll before dispatching
- Fix unvote handler to not require optionId
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 3)
- Expose is_dm, favorited, muted on channel API responses
- Normalize poll cable updates into message-shaped patch
- Add file presence validation to MessageAttachment
- Remove duplicate mention notifications from MentionService
- Make data migration rollback safe (IrreversibleMigration)
- Update factory to include file by default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: return 201 for channel creation and optimize DM lookup
- Return :created (201) instead of :ok (200) for channel creation
- Replace O(n) Ruby scan with SQL-based DM lookup using ARRAY_AGG
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 5)
- Broadcast channel event after create for real-time notifications
- Separate create/update strong params to prevent channel_type transitions
- Use strong params for typing_status input
- Replace find_by with detect on preloaded collections to fix N+1
- Preload attachments with blobs in show response
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 6)
- Serialize DM creation with advisory lock to prevent duplicates
- Broadcast channel deletion event for real-time UI updates
- Use strong params for mark_unread message_id
- Batch unread count computation to eliminate N+1 in index
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: eliminate N+1 in compute_unread_counts with single JOIN query
Replace per-membership COUNT loop with a single JOIN + GROUP BY query
that returns all unread counts in one database call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): quality fixes, missing tests, and Playwright E2E setup
Addresses quality issues found during review and fills test coverage gaps
for the internal chat feature.
Backend fixes:
- Return 201 for all create endpoints (messages, categories, polls, reactions, members)
- Fix N+1 queries: replies.size, poll votes, category channels.count, votes.exists?
- Fix pagination has_more logic to check page size instead of total count
- Scope poll vote/unvote to current account (security fix)
- Add internal_chat.messages.deleted i18n key
- Use find_by! in mark_unread for proper 404 on non-members
- Guard time param parsing with rescue ArgumentError
- Align message response format between channels and messages controllers
- Switch notification service to ActionCable-only (avoid push/email crashes)
Frontend fixes:
- Fix pinned message detection (content_attributes.pinned, not message.pinned)
- Fix thread reply count key (replies_count, not thread_replies_count)
- Fix markUnread to pass message_id parameter
- Fix pagination: PREPEND_MESSAGES mutation instead of overwriting
- Fix typing status to read Vuex reactive state, not stale closure
- Fix deleteDraft argument shape (pass { channelId, draftId })
- Fix DM channel filtering (check both is_dm and channel_type)
- Fix DraftsList navigation to use correct channel ID key
- Wire PollCreator to poll button in MessageEditor
- Wire settings event handler on ChannelHeader
- Reset PollCreator isSubmitting on timeout
New RSpec tests (67 examples):
- Factories: polls, poll_options, poll_votes, drafts
- Model specs: Poll, PollOption, PollVote, Draft
- Controller specs: PollsController, DraftsController
- Service specs: PollService, NotificationService, MentionService
Playwright E2E setup (37 tests):
- Install Playwright with Chromium
- Auth helper with Devise Token Auth login flow
- 8 test suites: navigation, channels, messaging, DMs, reactions, threads, polls, mark-read-unread
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 7)
Backend:
- Use lambda for UUID default in channels migration
- Wrap poll creation in transaction for atomicity
- Preload replies in thread action to avoid N+1
- Broadcast replies_count + attachments in listener (match REST shape)
- Scope draft listing through accessible channels
- Key draft upserts/deletes by parent_id for thread drafts
Frontend:
- Remove duplicate poll methods from internalChatMessages.js (use internalChatPolls.js)
- Persist toggleMute/toggleFavorite to backend via updateMember API
- Clear active channel on DELETE_CHANNEL mutation
- Skip unread increment for active channel in ActionCable handler
- Filter archived channels from sidebar getters
- Fix ChannelHeader isArchived to check status === 'archived'
- Prevent duplicate reactions in ADD_REACTION mutation
- Merge poll data into existing content_attributes on cable updates
- Guard infinite scroll against duplicate loads
- Add response.ok() check in E2E auth helper, remove hardcoded account ID
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 8)
- Remove unused nested typingStatus module from internalChat store
- Add parent_id to draft uniqueness scope and migration index
- Exclude reaction creator from reaction_created broadcast
- Preload attachments and poll associations in thread/messages queries
- Handle `after` fetches with APPEND_MESSAGES mutation
- Wrap channel creation payloads under `channel` key in E2E helpers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: rewrite Playwright E2E tests to use actual UI interactions
Completely rewrote all 8 E2E test suites to work with the live app:
- Test through actual UI interactions, not API bypass
- Use correct Portuguese (pt_BR) locale strings
- Use structural selectors matching real Vue component DOM
- Dynamic account ID from login response (no hardcoded values)
- 3 parallel workers, increased timeouts for reliability
- API calls only for preconditions (seeding test data)
29 tests passing across navigation, channels, messaging, DMs,
reactions, threads, polls, and mark-read-unread suites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use partial unique indexes for draft uniqueness with NULL parent_id
PostgreSQL treats NULL as distinct in unique constraints, so a composite
index on (user_id, channel_id, parent_id) allows duplicate root drafts.
Split into two partial indexes: one for root drafts (WHERE parent_id IS
NULL) and one for thread drafts (WHERE parent_id IS NOT NULL).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 9)
- Remove duplicate index on internal_chat_polls.internal_chat_message_id
(keep only unique index)
- Add options validation in polls create (return 400 instead of 500)
- Add expiration check to unvote action (match vote behavior)
- Use strong params in messages update action
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 10)
- Change channel associations from destroy_async to destroy (FK
constraints are ON DELETE RESTRICT, blocking async deletion)
- Remove unused internal_chat notification types and PRIMARY_ACTORS
entry (notification service uses ActionCable only, no DB records)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 11)
- Scope category_id to current account in channels controller (security)
- Defer message-created event in poll creation until after transaction
- Change message associations from destroy_async to destroy (FK compat)
- Validate option belongs to poll in poll_service
- Use strong params for emoji in reactions controller
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 12)
Backend (9 fixes):
- Gate message update/destroy by channel accessibility in policy
- Guard content_attributes nil before merge in polls controller
- Fix after-cursor pagination to use limit() instead of last()
- Wrap revote in transaction for atomicity in poll service
- Make unvote option-specific for multi-choice polls
- Exclude own messages from unread count
- Make channel activity update monotonic (only write if newer)
- Include actor in message/reaction broadcasts (multi-tab support)
- Return 400 for empty member create instead of 201
Frontend (8 fixes):
- Show uncategorized channels even when categories exist
- Clear editor on channel switch when no draft exists
- Soft-delete messages in store (update in place, don't remove)
- Guard ThreadPanel against out-of-order fetch responses
- Replace hardcoded channel label with i18n key in DraftsList
- Add accessible name to settings button in ChannelHeader
- Add aria-label to search field in ChannelSidebar
- Make MessageBubble actions keyboard-accessible via focus-within
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 13)
- Fix keyword argument mismatch in reactions dispatch_reaction_event
- Add user_id to reaction cable broadcast for shape consistency with REST
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): quality fixes, expanded RSpec + Playwright E2E tests
Fix isArchived computed (checked .archived instead of .status), fix
ReactionDisplay user identification (.user?.id vs .user_id), update
17 spec assertions from :success to :created on create endpoints,
add 32 new RSpec examples (polls, drafts, services), and rewrite
8 Playwright E2E test files with correct selectors, proper test
isolation, and dynamic user ID discovery.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 14)
Prevent duplicate votes on same option in multi-choice polls with
explicit BadRequest guard. Add internal_chat webhook events to
ALLOWED_WEBHOOK_EVENTS so users can subscribe to them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: include poll data in ActionCable broadcast for poll messages
Extract base_message_data helper and enrich message_event_data with
poll options when the message has an associated poll, ensuring
realtime subscribers receive the same poll data as REST API clients.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 15)
Backend: wrap single-choice revotes in transaction, capture member
tokens before channel destroy, exclude own messages from unread count,
strip attachments from deleted messages, enrich poll broadcast payload.
Frontend: use getCurrentRole getter, fix public-results poll display,
sync thread replies via store, add close button a11y, pass option_id
to unvote API, pass parent_id to deleteDraft API.
Models: handle nil last_read_at for new members, skip content
validation for attachment-only messages, align PollService guards
with controller, change category dependent to nullify.
Swagger: add attachments to message schema, fix create status to 201.
E2E: remove fragile waitForTimeout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): fix 21 UX/functional issues
Address 21 UX gaps discovered during product testing:
Sidebar & Navigation:
- Fix search icon overlap, extend search to description + DM members
- Add create channel/DM/category buttons and modals
- Show DM member names instead of null
- Include members data in channel index API for DMs
Message Interactions:
- Add delete confirmation dialog
- Implement inline message editing with cancel support
- Toggle emoji reactions (add/remove)
- Support multiple pinned messages with click-to-scroll
- Prevent thread replies from appearing in main chat
- Fix reply count live updates
- Hide pin button on thread messages
- Improve deleted message styling with greyed-out card
- Replace spinner with skeleton loading
- Add markdown toolbar (bold/italic/code)
- Fix thread editing and add vote/unvote handlers
Features & Polish:
- Implement channel settings slide-over panel
- Fix thread loading not affecting main channel spinner
- Fix poll creation field name mismatch with backend API
- Fix drafts: show channel names, handle DM navigation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): use Dialog modal for delete confirmation
Replace window.confirm with the project's Dialog component for
message delete confirmation, providing a consistent UI experience.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 16)
- Require content field in message update OpenAPI schema
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 17)
- Sanitize advisory lock SQL with sanitize_sql_array
- Use semantic button for pinned message banner
- Add aria-label to ChannelSettings close button
- Add type="button" to all ChannelSettings buttons
- Gate channel/DM/category creation to admins
- Replace hardcoded 'Direct Message' with i18n key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address CodeRabbit review feedback (round 18)
- Wrap DM creation payload in channel key for consistency
- Replace raw text in category select with i18n key
- Add IME composition guard to prevent premature send
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): UX round 2, rich editor, team members, drag-and-drop
- Reduce sidebar spacing between search bar and drafts
- Fix search icon overlapping placeholder text
- Replace inline category form with Dialog modal
- Add collapsible sidebar sections with localStorage persistence
- Add drag-and-drop channels across categories (admin-only, vuedraggable)
- Replace textarea editor with WootWriter ProseMirror rich text editor
- Replace regex markdown rendering with shared MessageFormatter
- Wire draft auto-save pipeline with WootWriter (3s debounce watcher)
- Add team + agent selection when creating private channels
- Auto-add all agents when creating public channels
- Sync team members to linked channels via TeamMember callback
- Fix member list not loading on first settings panel open
- Complete PT-BR translations for all internal chat strings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): UX round 3, Enter-to-send, mentions, copy link, poll modal
- Send with Enter (not Cmd+Enter), Shift+Enter for newlines
- Enable @mentions via WootWriter suggestions plugin
- Refocus editor after sending a message
- Copy link to message button in hover toolbar
- Poll creator refactored to Dialog with confirm-discard on close
- Channel type uses Switch instead of dropdown
- Category uses components-next Select instead of native select
- Skeleton loading: only on initial load, spinner for pagination
- Scroll position preserved when loading older messages
- Mute/Favorite buttons fixed (store members updated after fetch)
- Add/remove channel members after creation (admin-only)
- Save draft immediately when switching channels
- Settings sidebar remembers open/closed state via localStorage
- Search icon overlap fixed (increased padding)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): DM settings, copy updates, input refocus, member UX
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): member edit for private only, emoji overflow, reaction tooltips
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): thread count sync, scroll loading, copy link, thread/settings exclusivity
- Fix thread reply count doubling (remove duplicate INCREMENT_REPLY_COUNT from sendThreadReply, cable handles it)
- Fix copy link button (use window.location.origin + pathname as fallback)
- Hide poll button in thread editor
- Add "Also send in #channel" checkbox for thread replies
- Increase scroll threshold for loading older messages (100px instead of 0)
- Track and stop loading when oldest message reached
- Thread and settings panels are mutually exclusive
- Refocus editor after send with delayed focus
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): scroll to linked message via ?messageId= query param
Read messageId from route query on mount, scroll to and highlight the
target message after messages load, then clean the query param.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): prevent editor from becoming unfocusable after send
Root cause: passing disabled prop to WootWriter applies pointer-events-none
and ProseMirror does not re-enable contenteditable when disabled returns to
false. Fix: never disable the WootWriter, use a local isSending guard to
prevent double-sends. Refocus 300ms after send for ProseMirror state reload.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): simplify send guard, no artificial timeout
Content is cleared immediately before emit, so canSend naturally
returns false (empty content). No isSending guard needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): align poll option remove buttons vertically
Increase padding to p-1.5 and add flex-shrink-0 for consistent sizing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): close poll modal after creating poll
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): poll option X button alignment, discard modal on submit
- Button uses explicit 34px height matching input, no items-center
- Reset form before closing dialog so hasUnsavedChanges is false
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): close settings sidebar when clicking reply
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): update poll UI after voting, fix re-vote error
Vote/unvote actions now dispatch updateMessageFromCable with the API
response to update poll state locally. Pass channelId to enable this.
Clicking an already-voted option correctly triggers unvote.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): include poll data in message response, add timer and voters
Backend: message_response now includes poll data (options, votes, voted
status, voters for public polls) via eager-loaded poll association.
This fixes polls not rendering after page reload.
Frontend PollDisplay:
- Countdown timer showing time remaining until poll closes
- Read-only state when expired (div instead of button, no hover)
- Voter names shown below each option (public polls or admin)
- Prefer content_attributes.poll over message.poll for fresh data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): include channel_id in poll voted cable broadcast
The poll_event_data was missing internal_chat_channel_id, so the
frontend cable handler could not route the update to the correct
channel's message store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): poll vote highlight, typing off, reaction broadcast, translations
- Preserve per-user voted flags when merging cable poll broadcast
- Send typing_off after 3s of no typing activity
- Include internal_chat_channel_id in reaction event broadcasts
- Fix reaction deleted handler to also check channel_id field
- Simplify "also send in" copy (works for both channels and DMs)
- Add PT-BR translation for ALSO_SEND_IN_CHANNEL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): unified reaction popover with all emoji groups
Clicking any reaction badge opens a single popover showing all reactions
grouped by emoji with user names. Current user can remove their own
reaction via X button. Replaces per-reaction popover with unified view.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): wire close DM button to archive and navigate home
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): close DM via hidden flag on channel membership
Add hidden boolean to channel_members table. Close DM sets hidden=true
via member update API. Sidebar filters out hidden DMs. New messages on
a DM channel automatically unhide all members via listener callback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): reaction popover, user names, file upload support
- Include user name in reaction API response (was missing)
- Redesign reaction popover: flat list with emoji + name per row,
aligned X button for removing own reactions
- Add file upload: paperclip button opens file picker, attached files
shown as chips with remove, sent via FormData with message
- Store action and API client support files parameter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): reaction user names, unreact button, attachment rendering
- Include user name in reactions across all endpoints (messages_controller,
listener base_message_data)
- Make unreact X button always visible (bg-n-alpha-2 background)
- Render message attachments as downloadable links with paperclip icon
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): image preview for attachments in messages and editor
Messages: images render inline with max-h-60, non-images as download links.
Editor: image files show thumbnail preview, non-images show file icon + name.
Remove button as floating circle on top-right corner of each attachment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): attachment preview matches conversation pattern
- File previews show name + size (e.g. "2 MB") in a horizontal card
- Image thumbnails as 32px squares, non-images show document emoji
- Remove button is a visible X icon (not a floating circle)
- Layout matches AttachmentsPreview from conversation ReplyBox
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): auto-detect image file type from MIME on upload
MessageCreateService now detects file type from content_type instead of
defaulting to :file. Images are correctly tagged as :image so they
render inline in message bubbles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): include file_url in cable broadcast, fix filename display
Listener attachment_event_data now includes file_url so attachments
render correctly on real-time messages without page refresh.
MessageBubble extracts filename from URL or falls back to file_type+ext.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): pin attachments, edit members modal, settings persistence
- Skip content validation when pinning/unpinning (fixes pin on file-only messages)
- Add EditMembersModal with search, add, and remove members for private channels
- Fix settings sidebar always opening: @close now calls handleToggleSettings
which updates localStorage, not just sets ref to false
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): fix X buttons for attachment remove and reaction unreact
Replace Icon component with inline SVG cross for reliable rendering.
Both attachment remove and reaction unreact buttons now show a visible
X icon at all times with proper vertical alignment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): allow any user to pin messages, not just sender/admin
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): restore Icon component for X buttons (size-4 in size-6 container)
SVG inline approach didn't render. Reverted to Icon i-lucide-x with
larger sizes (size-4 icon in size-6 button) which renders reliably.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): use p-1 + size-4 pattern for X buttons (matches message toolbar)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): thread indicator on messages from threads, allow pinning all
- Show "Thread" badge with icon on messages that have parent_id,
clicking it opens the parent thread
- Remove parent_id restriction from canPin, any non-deleted message
can be pinned
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): thread indicator, poll close loop, thread navigation
- Hide thread indicator inside thread panel (inThread prop)
- Open parent thread when clicking thread badge on messages with parent_id
- Fix PollCreator infinite close loop (handleClose no longer calls
dialogRef.close, since Dialog already triggered the close)
- Look up parent message in store when opening thread from indicator
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(internal-chat): poll duration translations and clickable switch labels
- Duration options use i18n keys (EN + PT-BR: 24 horas, 7 dias, etc.)
- Multiple choice and Public results switches toggle by clicking label
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal_chat): enhance message handling and search functionality
- Added broadcasting of typing off events in InternalChatListener.
- Included member user IDs in channel data for better context.
- Updated message model to allow optional sender association.
- Implemented team mention expansion in MentionService to include team members.
- Enhanced message creation service to store mentioned user IDs in content attributes.
- Introduced a new SearchService for searching channels, DMs, and messages.
- Updated API responses to include has_unread_mention flag for channels.
- Added tests for user deletion behavior in internal chat, ensuring message preservation and reaction handling.
- Improved draft model to allow coexistence of root and thread drafts.
- Added unique indexes for drafts to prevent duplicate entries.
- Implemented foreign key constraints with appropriate delete behaviors for internal chat models.
* feat(internal-chat): swagger docs, webhook events, search UX improvements
Add Swagger documentation for drafts, polls, and search endpoints.
Wire internal_chat_message_deleted and internal_chat_channel_updated
webhook events to the UI and listener. Improve search empty state with
min-chars hint and friendly no-results message. Update CLAUDE.md to
include pt_BR translations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(internal-chat): add draft count display in channel sidebar
* chore: remove playwright config and dependencies
* feat(internal-chat): polish UX, swagger updates, and migration consolidation
- Editor toolbar shortcuts (@ and #) with instant popover trigger,
including accent-insensitive matching and wider conversation popover
- Localized last activity time and inbox name on conversation preview cards
- Thread + main interplay: also-send-in-channel mirror, parent_id filter,
per-message conversation link, hidden buttons inside thread view,
reactions update across both lists, scroll-to-message behavior
- Search service uses f_unaccent for messages, channel names and user names
via dedicated GIN trigram functional indexes
- Renamed InternalChat::ProGating to InternalChat::Limits with neutral
semantics
- Consolidated 17 internal chat migrations into 3 (tables, default channels,
unaccent search) and added a rake task to ensure the f_unaccent function
exists before db:schema:load
- Swagger paths and definitions updated to match the current state of the
feature (also_send_in_channel, status codes, pro-required responses,
hidden member flag, search meta fields, etc.)
* fix(internal-chat): use Rake task augmentation for db:schema:load hook
The previous `Rake::Task['db:schema:load'].enhance(...)` guarded by
`task_defined?` silently no-op'd in CI when the rake file loaded before
ActiveRecord's rake_tasks block ran. Re-opening `db:schema:load` via
Rake's `task name => deps` DSL augments the existing task regardless of
load order, ensuring the f_unaccent function is created before schema.rb
references it.
* fix(internal-chat): enhance db:schema:load from Rakefile after load_tasks
Adding the prereq inside lib/tasks/internal_chat_search.rake (via either
`Rake::Task#enhance` or task DSL augmentation) was being silently dropped
in CI, presumably due to load order between application rake files and
ActiveRecord's `rake_tasks` block. Moving the `enhance` to the Rakefile
itself, after `Rails.application.load_tasks`, guarantees both
`db:schema:load` and `db:internal_chat:ensure_search_functions` are
defined before the prereq is added.
Also leaves a debug `puts` in the task body so future regressions are
visible from CI logs.
* chore(internal-chat): add diagnostic logging to f_unaccent hook
* fix(internal-chat): install f_unaccent on all envs iterated by db:schema:load
Rails' `db:schema:load` in development env iterates over BOTH the
development and test databases (see
`ActiveRecord::Tasks::DatabaseTasks.each_current_environment`), but our
hook was only installing the function on the currently-connected
database. CI defaults to development env (no `RAILS_ENV` set), so the
function landed on `chatwoot_dev` while `chatwoot_test` remained
without it, causing the schema load to fail when creating the functional
indexes against the test DB.
The hook now mirrors the same iteration logic and installs the function
on every relevant config, restoring the original AR connection
afterwards.
* fix(internal-chat): align listener spec with current broadcast payload
- internal_chat_message_created now emits two broadcasts (the message
itself plus an automatic typing_off), so the spec switches to
`allow`/`have_received` to assert the message broadcast without caring
about the additional typing_off call.
- internal_chat_reaction_created payload uses `message_id`, not
`internal_chat_message_id`. Update the spec expectation to match.
* chore(internal-chat): remove redundant DSL augmentation in rake task
* fix(internal-chat): harden gates, kill N+1s and reduce race risk
Closes review findings raised on the internal chat PR:
- Restrict role mass-assignment in ChannelMembersController so only
account administrators can promote new members to channel admin.
- Wrap private-channel create/unarchive in a Postgres advisory lock per
account so concurrent requests can no longer bypass the freemium limit.
- Replace `replies.size` and `votes.size` (per-broadcast queries) with
`replies_count` / `votes_count` counter caches.
- Make `update_channel_activity` an atomic compare-and-set update so
concurrent message creates can never regress `last_activity_at`.
- Optimize `Poll#total_votes_count` to use the cached column and eager-
loaded options instead of a per-poll `votes.count` query.
- Add `internal_chat_messages.account_id` foreign key (`ON DELETE
CASCADE`) to prevent orphan rows.
- Escape HTML in `ChannelSidebar.highlightMatch` to close a v-html XSS
via incomplete tags in message search snippets.
- Cleanup `typingOffTimer` on `ChannelView` unmount.
- Add stable sort to `getChannelsByCategory` (alphabetical) and
`getDMChannels` (last activity) to prevent UI reorder thrash.
- Localize `PollDisplay` time-remaining strings (en + pt-BR).
- Add specs covering the 90-day search history filter and the search
controller endpoint, plus regenerate the consolidated migration
with the new columns and FK.
* docs(swagger): note role mass-assignment restriction on channel members
Document that the `role` field on the channel member create payload is
silently coerced to `member` for callers that are not account
administrators, matching the controller behavior introduced in the
previous commit.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1041 lines
44 KiB
JSON
1041 lines
44 KiB
JSON
{
|
||
"INTEGRATION_SETTINGS": {
|
||
"SHOPIFY": {
|
||
"HEADER": "Shopify",
|
||
"DELETE": {
|
||
"TITLE": "Delete Shopify Integration",
|
||
"MESSAGE": "Are you sure you want to delete the Shopify integration?"
|
||
},
|
||
"STORE_URL": {
|
||
"TITLE": "Connect Shopify Store",
|
||
"LABEL": "Store URL",
|
||
"PLACEHOLDER": "your-store.myshopify.com",
|
||
"HELP": "Enter your Shopify store's myshopify.com URL",
|
||
"CANCEL": "Cancel",
|
||
"SUBMIT": "Connect Store"
|
||
},
|
||
"ERROR": "There was an error connecting to Shopify. Please try again or contact support if the issue persists."
|
||
},
|
||
"HEADER": "Integrations",
|
||
"DESCRIPTION": "Chatwoot integrates with multiple tools and services to improve your team's efficiency. Explore the list below to configure your favorite apps.",
|
||
"LEARN_MORE": "Learn more about integrations",
|
||
"LOADING": "Fetching integrations",
|
||
"SEARCH_PLACEHOLDER": "Search integrations...",
|
||
"NO_RESULTS": "No integrations found matching your search",
|
||
"CAPTAIN": {
|
||
"DISABLED": "Captain is not enabled on your account.",
|
||
"CLICK_HERE_TO_CONFIGURE": "Click here to configure",
|
||
"LOADING_CONSOLE": "Loading Captain Console...",
|
||
"FAILED_TO_LOAD_CONSOLE": "Failed to load Captain Console. Please refresh and try again."
|
||
},
|
||
"WEBHOOK": {
|
||
"SUBSCRIBED_EVENTS": "Subscribed Events",
|
||
"LEARN_MORE": "Learn more about webhooks",
|
||
"SECRET": {
|
||
"LABEL": "Secret",
|
||
"COPY": "Copy secret to clipboard",
|
||
"COPY_SUCCESS": "Secret copied to clipboard",
|
||
"TOGGLE": "Toggle secret visibility",
|
||
"CREATED_DESC": "Your webhook has been created. Use the secret below to verify webhook signatures. Please copy it now — you can also find it later in the webhook edit form.",
|
||
"DONE": "Done"
|
||
},
|
||
"COUNT": "{n} webhook | {n} webhooks",
|
||
"SEARCH_PLACEHOLDER": "Search webhooks...",
|
||
"NO_RESULTS": "No webhooks found matching your search",
|
||
"FORM": {
|
||
"CANCEL": "Cancel",
|
||
"DESC": "Webhook events provide you the realtime information about what's happening in your Chatwoot account. Please enter a valid URL to configure a callback.",
|
||
"SUBSCRIPTIONS": {
|
||
"LABEL": "Events",
|
||
"EVENTS": {
|
||
"CONVERSATION_CREATED": "Conversation Created",
|
||
"CONVERSATION_STATUS_CHANGED": "Conversation Status Changed",
|
||
"CONVERSATION_UPDATED": "Conversation Updated",
|
||
"MESSAGE_CREATED": "Message created",
|
||
"MESSAGE_INCOMING": "Incoming message",
|
||
"MESSAGE_OUTGOING": "Outgoing message",
|
||
"MESSAGE_UPDATED": "Message updated",
|
||
"WEBWIDGET_TRIGGERED": "Live chat widget opened by the user",
|
||
"CONTACT_CREATED": "Contact created",
|
||
"CONTACT_UPDATED": "Contact updated",
|
||
"CONVERSATION_TYPING_ON": "Conversation Typing On",
|
||
"CONVERSATION_TYPING_OFF": "Conversation Typing Off",
|
||
"PROVIDER_EVENT_RECEIVED": "Provider Event Received",
|
||
"INTERNAL_CHAT_MESSAGE_CREATED": "Internal chat message created",
|
||
"INTERNAL_CHAT_MESSAGE_UPDATED": "Internal chat message updated",
|
||
"INTERNAL_CHAT_MESSAGE_DELETED": "Internal chat message deleted",
|
||
"INTERNAL_CHAT_CHANNEL_UPDATED": "Internal chat channel updated"
|
||
}
|
||
},
|
||
"NAME": {
|
||
"LABEL": "Webhook Name",
|
||
"PLACEHOLDER": "Enter the name of the webhook"
|
||
},
|
||
"INBOX": {
|
||
"LABEL": "Inbox",
|
||
"TITLE": "Select the inbox",
|
||
"PLACEHOLDER": "All Inboxes",
|
||
"NO_RESULTS": "No inboxes found",
|
||
"INPUT_PLACEHOLDER": "Search inbox"
|
||
},
|
||
"END_POINT": {
|
||
"LABEL": "Webhook URL",
|
||
"PLACEHOLDER": "Example: {webhookExampleURL}",
|
||
"ERROR": "Please enter a valid URL"
|
||
},
|
||
"EDIT_SUBMIT": "Update webhook",
|
||
"ADD_SUBMIT": "Create webhook"
|
||
},
|
||
"TITLE": "Webhook",
|
||
"CONFIGURE": "Configure",
|
||
"HEADER": "Webhook settings",
|
||
"HEADER_BTN_TXT": "Add new webhook",
|
||
"LOADING": "Fetching attached webhooks",
|
||
"SEARCH_404": "There are no items matching this query",
|
||
"SIDEBAR_TXT": "<p><b>Webhooks</b> </p> <p>Webhooks are HTTP callbacks which can be defined for every account. They are triggered by events like message creation in Chatwoot. You can create more than one webhook for this account. <br /><br /> For creating a <b>webhook</b>, click on the <b>Add new webhook</b> button. You can also remove any existing webhook by clicking on the Delete button.</p>",
|
||
"LIST": {
|
||
"404": "There are no webhooks configured for this account.",
|
||
"TITLE": "Manage webhooks",
|
||
"TABLE_HEADER": {
|
||
"WEBHOOK_ENDPOINT": "Webhook endpoint",
|
||
"ACTIONS": "Actions"
|
||
}
|
||
},
|
||
"EDIT": {
|
||
"BUTTON_TEXT": "Edit",
|
||
"TITLE": "Edit webhook",
|
||
"API": {
|
||
"SUCCESS_MESSAGE": "Webhook configuration updated successfully",
|
||
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||
}
|
||
},
|
||
"ADD": {
|
||
"CANCEL": "Cancel",
|
||
"TITLE": "Add new webhook",
|
||
"API": {
|
||
"SUCCESS_MESSAGE": "Webhook configuration added successfully",
|
||
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||
}
|
||
},
|
||
"DELETE": {
|
||
"BUTTON_TEXT": "Delete",
|
||
"API": {
|
||
"SUCCESS_MESSAGE": "Webhook deleted successfully",
|
||
"ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later"
|
||
},
|
||
"CONFIRM": {
|
||
"TITLE": "Confirm Deletion",
|
||
"MESSAGE": "Are you sure to delete the webhook? ({webhookURL})",
|
||
"YES": "Yes, Delete ",
|
||
"NO": "No, Keep it"
|
||
}
|
||
}
|
||
},
|
||
"SLACK": {
|
||
"HEADER": "Slack",
|
||
"DELETE": "Delete",
|
||
"DELETE_CONFIRMATION": {
|
||
"TITLE": "Delete the integration",
|
||
"MESSAGE": "Are you sure you want to delete the integration? Doing so will result in the loss of access to conversations on your Slack workspace."
|
||
},
|
||
"HELP_TEXT": {
|
||
"TITLE": "How to use the Slack Integration?",
|
||
"BODY": "With this integration, all of your incoming conversations will be synced to the ***{selectedChannelName}*** channel in your Slack workspace. You can manage all your customer conversations right within the channel and never miss a message.\n\nHere are the main features of the integration:\n\n**Respond to conversations from within Slack:** To respond to a conversation in the ***{selectedChannelName}*** Slack channel, simply type out your message and send it as a thread. This will create a response back to the customer through Chatwoot. It's that simple!\n\n **Create private notes:** If you want to create private notes instead of replies, start your message with ***`note:`***. This ensures that your message is kept private and won't be visible to the customer.\n\n**Associate an agent profile:** If the person who replied on Slack has an agent profile in Chatwoot under the same email, the replies will be associated with that agent profile automatically. This means you can easily track who said what and when. On the other hand, when the replier doesn't have an associated agent profile, the replies will appear from the bot profile to the customer.",
|
||
"SELECTED": "selected"
|
||
},
|
||
"SELECT_CHANNEL": {
|
||
"OPTION_LABEL": "Select a channel",
|
||
"UPDATE": "Update",
|
||
"BUTTON_TEXT": "Connect channel",
|
||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||
"ATTENTION_REQUIRED": "Attention required",
|
||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||
},
|
||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||
"FAILED_TO_FETCH_CHANNELS": "There was an error fetching the channels from Slack, please try again"
|
||
},
|
||
"DYTE": {
|
||
"CLICK_HERE_TO_JOIN": "Click here to join",
|
||
"LEAVE_THE_ROOM": "Leave the room",
|
||
"START_VIDEO_CALL_HELP_TEXT": "Start a new video call with the customer",
|
||
"JOIN_ERROR": "There was an error joining the call, please try again",
|
||
"CREATE_ERROR": "There was an error creating a meeting link, please try again"
|
||
},
|
||
"OPEN_AI": {
|
||
"AI_ASSIST": "AI Assist",
|
||
"WITH_AI": " {option} with AI ",
|
||
"OPTIONS": {
|
||
"REPLY_SUGGESTION": "Reply Suggestion",
|
||
"SUMMARIZE": "Summarize",
|
||
"REPHRASE": "Improve Writing",
|
||
"FIX_SPELLING_GRAMMAR": "Fix Spelling and Grammar",
|
||
"SHORTEN": "Shorten",
|
||
"EXPAND": "Expand",
|
||
"MAKE_FRIENDLY": "Change message tone to friendly",
|
||
"MAKE_FORMAL": "Use formal tone",
|
||
"SIMPLIFY": "Simplify",
|
||
"CONFIDENT": "Use confident tone",
|
||
"PROFESSIONAL": "Use professional tone",
|
||
"CASUAL": "Use casual tone",
|
||
"STRAIGHTFORWARD": "Use straightforward tone"
|
||
},
|
||
"REPLY_OPTIONS": {
|
||
"IMPROVE_REPLY": "Improve reply",
|
||
"IMPROVE_REPLY_SELECTION": "Improve the selection",
|
||
"CHANGE_TONE": {
|
||
"TITLE": "Change tone",
|
||
"OPTIONS": {
|
||
"PROFESSIONAL": "Professional",
|
||
"CASUAL": "Casual",
|
||
"STRAIGHTFORWARD": "Straightforward",
|
||
"CONFIDENT": "Confident",
|
||
"FRIENDLY": "Friendly"
|
||
}
|
||
},
|
||
"GRAMMAR": "Fix grammar & spelling",
|
||
"SUGGESTION": "Suggest a reply",
|
||
"SUMMARIZE": "Summarize the conversation",
|
||
"ASK_COPILOT": "Ask Copilot"
|
||
},
|
||
"ASSISTANCE_MODAL": {
|
||
"DRAFT_TITLE": "Draft content",
|
||
"GENERATED_TITLE": "Generated content",
|
||
"AI_WRITING": "AI is writing",
|
||
"BUTTONS": {
|
||
"APPLY": "Use this suggestion",
|
||
"CANCEL": "Cancel"
|
||
}
|
||
},
|
||
"CTA_MODAL": {
|
||
"TITLE": "Integrate with OpenAI",
|
||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||
"BUTTONS": {
|
||
"NEED_HELP": "Need help?",
|
||
"DISMISS": "Dismiss",
|
||
"FINISH": "Finish Setup"
|
||
},
|
||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||
},
|
||
"TITLE": "Improve With AI",
|
||
"SUMMARY_TITLE": "Summary with AI",
|
||
"REPLY_TITLE": "Reply suggestion with AI",
|
||
"SUBTITLE": "An improved reply will be generated using AI, based on your current draft.",
|
||
"TONE": {
|
||
"TITLE": "Tone",
|
||
"OPTIONS": {
|
||
"PROFESSIONAL": "Professional",
|
||
"FRIENDLY": "Friendly"
|
||
}
|
||
},
|
||
"BUTTONS": {
|
||
"GENERATE": "Generate",
|
||
"GENERATING": "Generating...",
|
||
"CANCEL": "Cancel"
|
||
},
|
||
"GENERATE_ERROR": "There was an error processing the content, please verify your OpenAI API key and try again"
|
||
},
|
||
"DELETE": {
|
||
"BUTTON_TEXT": "Delete",
|
||
"API": {
|
||
"SUCCESS_MESSAGE": "Integration deleted successfully"
|
||
}
|
||
},
|
||
"CONNECT": {
|
||
"BUTTON_TEXT": "Connect"
|
||
},
|
||
"DASHBOARD_APPS": {
|
||
"TITLE": "Dashboard Apps",
|
||
"HEADER_BTN_TXT": "Add a new dashboard app",
|
||
"SIDEBAR_TXT": "<p><b>Dashboard Apps</b></p><p>Dashboard Apps allow organizations to embed an application inside the Chatwoot dashboard to provide the context for customer support agents. This feature allows you to create an application independently and embed that inside the dashboard to provide user information, their orders, or their previous payment history.</p><p>When you embed your application using the dashboard in Chatwoot, your application will get the context of the conversation and contact as a window event. Implement a listener for the message event on your page to receive the context.</p><p>To add a new dashboard app, click on the button 'Add a new dashboard app'.</p>",
|
||
"DESCRIPTION": "Dashboard Apps allow organizations to embed an application inside the dashboard to provide the context for customer support agents. This feature allows you to create an application independently and embed that to provide user information, their orders, or their previous payment history.",
|
||
"LEARN_MORE": "Learn more about Dashboard Apps",
|
||
"COUNT": "{n} dashboard app | {n} dashboard apps",
|
||
"SEARCH_PLACEHOLDER": "Search dashboard apps...",
|
||
"NO_RESULTS": "No dashboard apps found matching your search",
|
||
"LIST": {
|
||
"404": "There are no dashboard apps configured on this account yet",
|
||
"LOADING": "Fetching dashboard apps...",
|
||
"TABLE_HEADER": {
|
||
"NAME": "Name",
|
||
"ENDPOINT": "Endpoint",
|
||
"ACTIONS": "Actions"
|
||
},
|
||
"EDIT_TOOLTIP": "Edit app",
|
||
"DELETE_TOOLTIP": "Delete app"
|
||
},
|
||
"VIEW": {
|
||
"NOT_FOUND": "We couldn't find that dashboard app."
|
||
},
|
||
"FORM": {
|
||
"TITLE_LABEL": "Name",
|
||
"TITLE_PLACEHOLDER": "Enter a name for your dashboard app",
|
||
"TITLE_ERROR": "A name for the dashboard app is required",
|
||
"URL_LABEL": "Endpoint",
|
||
"URL_PLACEHOLDER": "Enter the endpoint URL where your app is hosted",
|
||
"URL_ERROR": "A valid URL is required",
|
||
"SHOW_ON_SIDEBAR_LABEL": "Show on sidebar"
|
||
},
|
||
"CREATE": {
|
||
"HEADER": "Add a new dashboard app",
|
||
"FORM_SUBMIT": "Submit",
|
||
"FORM_CANCEL": "Cancel",
|
||
"API_SUCCESS": "Dashboard app configured successfully",
|
||
"API_ERROR": "We couldn't create an app. Please try again later"
|
||
},
|
||
"UPDATE": {
|
||
"HEADER": "Edit dashboard app",
|
||
"FORM_SUBMIT": "Update",
|
||
"FORM_CANCEL": "Cancel",
|
||
"API_SUCCESS": "Dashboard app updated successfully",
|
||
"API_ERROR": "We couldn't update the app. Please try again later"
|
||
},
|
||
"DELETE": {
|
||
"CONFIRM_YES": "Yes, delete it",
|
||
"CONFIRM_NO": "No, keep it",
|
||
"TITLE": "Confirm deletion",
|
||
"MESSAGE": "Are you sure to delete the app - {appName}?",
|
||
"API_SUCCESS": "Dashboard app deleted successfully",
|
||
"API_ERROR": "We couldn't delete the app. Please try again later"
|
||
}
|
||
},
|
||
"LINEAR": {
|
||
"HEADER": "Linear",
|
||
"ADD_OR_LINK_BUTTON": "Create/Link Linear Issue",
|
||
"LOADING": "Fetching linear issues...",
|
||
"LOADING_ERROR": "There was an error fetching the linear issues, please try again",
|
||
"CREATE": "Create",
|
||
"LINK": {
|
||
"SEARCH": "Search issues",
|
||
"SELECT": "Select issue",
|
||
"TITLE": "Link",
|
||
"EMPTY_LIST": "No linear issues found",
|
||
"LOADING": "Loading",
|
||
"ERROR": "There was an error fetching the linear issues, please try again",
|
||
"LINK_SUCCESS": "Issue linked successfully",
|
||
"LINK_ERROR": "There was an error linking the issue, please try again",
|
||
"LINK_TITLE": "Conversation (#{conversationId}) with {name}"
|
||
},
|
||
"ADD_OR_LINK": {
|
||
"TITLE": "Create/link linear issue",
|
||
"DESCRIPTION": "Create Linear issues from conversations, or link existing ones for seamless tracking.",
|
||
"FORM": {
|
||
"TITLE": {
|
||
"LABEL": "Title",
|
||
"PLACEHOLDER": "Enter title",
|
||
"REQUIRED_ERROR": "Title is required"
|
||
},
|
||
"DESCRIPTION": {
|
||
"LABEL": "Description",
|
||
"PLACEHOLDER": "Enter description"
|
||
},
|
||
"TEAM": {
|
||
"LABEL": "Team",
|
||
"PLACEHOLDER": "Select team",
|
||
"SEARCH": "Search team",
|
||
"REQUIRED_ERROR": "Team is required"
|
||
},
|
||
"ASSIGNEE": {
|
||
"LABEL": "Assignee",
|
||
"PLACEHOLDER": "Select assignee",
|
||
"SEARCH": "Search assignee"
|
||
},
|
||
"PRIORITY": {
|
||
"LABEL": "Priority",
|
||
"PLACEHOLDER": "Select priority",
|
||
"SEARCH": "Search priority"
|
||
},
|
||
"LABEL": {
|
||
"LABEL": "Label",
|
||
"PLACEHOLDER": "Select label",
|
||
"SEARCH": "Search label"
|
||
},
|
||
"STATUS": {
|
||
"LABEL": "Status",
|
||
"PLACEHOLDER": "Select status",
|
||
"SEARCH": "Search status"
|
||
},
|
||
"PROJECT": {
|
||
"LABEL": "Project",
|
||
"PLACEHOLDER": "Select project",
|
||
"SEARCH": "Search project"
|
||
}
|
||
},
|
||
"CREATE": "Create",
|
||
"CANCEL": "Cancel",
|
||
"CREATE_SUCCESS": "Issue created successfully",
|
||
"CREATE_ERROR": "There was an error creating the issue, please try again",
|
||
"LOADING_TEAM_ERROR": "There was an error fetching the teams, please try again",
|
||
"LOADING_TEAM_ENTITIES_ERROR": "There was an error fetching the team entities, please try again"
|
||
},
|
||
"ISSUE": {
|
||
"STATUS": "Status",
|
||
"PRIORITY": "Priority",
|
||
"ASSIGNEE": "Assignee",
|
||
"LABELS": "Labels",
|
||
"CREATED_AT": "Created at {createdAt}"
|
||
},
|
||
"UNLINK": {
|
||
"TITLE": "Unlink",
|
||
"SUCCESS": "Issue unlinked successfully",
|
||
"ERROR": "There was an error unlinking the issue, please try again"
|
||
},
|
||
"NO_LINKED_ISSUES": "No linked issues found",
|
||
"DELETE": {
|
||
"TITLE": "Are you sure you want to delete the integration?",
|
||
"MESSAGE": "Are you sure you want to delete the integration?",
|
||
"CONFIRM": "Yes, delete",
|
||
"CANCEL": "Cancel"
|
||
},
|
||
"CTA": {
|
||
"TITLE": "Connect to Linear",
|
||
"AGENT_DESCRIPTION": "Linear workspace is not connected. Request your administrator to connect a workspace to use this integration.",
|
||
"DESCRIPTION": "Linear workspace is not connected. Click the button below to connect your workspace to use this integration.",
|
||
"BUTTON_TEXT": "Connect Linear workspace"
|
||
}
|
||
},
|
||
"NOTION": {
|
||
"HEADER": "Notion",
|
||
"DELETE": {
|
||
"TITLE": "Are you sure you want to delete the Notion integration?",
|
||
"MESSAGE": "Deleting this integration will remove access to your Notion workspace and stop all related functionality.",
|
||
"CONFIRM": "Yes, delete",
|
||
"CANCEL": "Cancel"
|
||
}
|
||
}
|
||
},
|
||
"CAPTAIN": {
|
||
"NAME": "Captain",
|
||
"HEADER_KNOW_MORE": "Know more",
|
||
"ASSISTANT_SWITCHER": {
|
||
"ASSISTANTS": "Assistants",
|
||
"SWITCH_ASSISTANT": "Switch between assistants",
|
||
"NEW_ASSISTANT": "Create Assistant",
|
||
"EMPTY_LIST": "No assistants found, please create one to get started"
|
||
},
|
||
"COPILOT": {
|
||
"TITLE": "Copilot",
|
||
"TRY_THESE_PROMPTS": "Try these prompts",
|
||
"PANEL_TITLE": "Get started with Copilot",
|
||
"KICK_OFF_MESSAGE": "Need a quick summary, want to check past conversations, or draft a better reply? Copilot’s here to speed things up.",
|
||
"SEND_MESSAGE": "Send message...",
|
||
"EMPTY_MESSAGE": "There was an error generating the response. Please try again.",
|
||
"LOADER": "Captain is thinking",
|
||
"YOU": "You",
|
||
"USE": "Use this",
|
||
"RESET": "Reset",
|
||
"SHOW_STEPS": "Show steps",
|
||
"SELECT_ASSISTANT": "Select Assistant",
|
||
"PROMPTS": {
|
||
"SUMMARIZE": {
|
||
"LABEL": "Summarize this conversation",
|
||
"CONTENT": "Summarize the key points discussed between the customer and the support agent, including the customer's concerns, questions, and the solutions or responses provided by the support agent"
|
||
},
|
||
"SUGGEST": {
|
||
"LABEL": "Suggest an answer",
|
||
"CONTENT": "Analyze the customer's inquiry, and draft a response that effectively addresses their concerns or questions. Ensure the reply is clear, concise, and provides helpful information."
|
||
},
|
||
"RATE": {
|
||
"LABEL": "Rate this conversation",
|
||
"CONTENT": "Review the conversation to see how well it meets the customer's needs. Share a rating out of 5 based on tone, clarity, and effectiveness."
|
||
},
|
||
"HIGH_PRIORITY": {
|
||
"LABEL": "High priority conversations",
|
||
"CONTENT": "Give me a summary of all high priority open conversations. Include the conversation ID, customer name (if available), last message content, and assigned agent. Group by status if relevant."
|
||
},
|
||
"LIST_CONTACTS": {
|
||
"LABEL": "List contacts",
|
||
"CONTENT": "Show me the list of top 10 contacts. Include name, email or phone number (if available), last seen time, tags (if any)."
|
||
}
|
||
}
|
||
},
|
||
"PLAYGROUND": {
|
||
"USER": "You",
|
||
"ASSISTANT": "Assistant",
|
||
"MESSAGE_PLACEHOLDER": "Type your message...",
|
||
"HEADER": "Playground",
|
||
"DESCRIPTION": "Use this playground to send messages to your assistant and check if it responds accurately, quickly, and in the tone you expect.",
|
||
"CREDIT_NOTE": "Messages sent here will count toward your Captain credits."
|
||
},
|
||
"PAYWALL": {
|
||
"TITLE": "Upgrade to use Captain AI",
|
||
"AVAILABLE_ON": "Captain is not available on the free plan.",
|
||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||
"UPGRADE_NOW": "Upgrade now",
|
||
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
|
||
},
|
||
"ENTERPRISE_PAYWALL": {
|
||
"AVAILABLE_ON": "Captain AI is only available in the Enterprise plans.",
|
||
"UPGRADE_PROMPT": "Upgrade your plan to get access to our assistants, copilot and more.",
|
||
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
|
||
},
|
||
"BANNER": {
|
||
"RESPONSES": "You've used over 80% of your response limit. To continue using Captain AI, please upgrade.",
|
||
"DOCUMENTS": "Document limit reached. Upgrade to continue using Captain AI."
|
||
},
|
||
"FORM": {
|
||
"CANCEL": "Cancel",
|
||
"CREATE": "Create",
|
||
"EDIT": "Update"
|
||
},
|
||
"ASSISTANTS": {
|
||
"HEADER": "Assistants",
|
||
"NO_ASSISTANTS_AVAILABLE": "There are no assistants available in your account.",
|
||
"ADD_NEW": "Create a new assistant",
|
||
"DELETE": {
|
||
"TITLE": "Are you sure to delete the assistant?",
|
||
"DESCRIPTION": "This action is permanent. Deleting this assistant will remove it from all connected inboxes and permanently erase all generated knowledge.",
|
||
"CONFIRM": "Yes, delete",
|
||
"SUCCESS_MESSAGE": "The assistant has been successfully deleted",
|
||
"ERROR_MESSAGE": "There was an error deleting the assistant, please try again."
|
||
},
|
||
"FORM_DESCRIPTION": "Fill out the details below to name your assistant, describe its purpose, and specify the product it will support.",
|
||
"CREATE": {
|
||
"TITLE": "Create an assistant",
|
||
"SUCCESS_MESSAGE": "The assistant has been successfully created",
|
||
"ERROR_MESSAGE": "There was an error creating the assistant, please try again."
|
||
},
|
||
"FORM": {
|
||
"UPDATE": "Update",
|
||
"SECTIONS": {
|
||
"BASIC_INFO": "Basic Information",
|
||
"SYSTEM_MESSAGES": "System Messages",
|
||
"INSTRUCTIONS": "Instructions",
|
||
"FEATURES": "Features",
|
||
"TOOLS": "Tools "
|
||
},
|
||
"NAME": {
|
||
"LABEL": "Name",
|
||
"PLACEHOLDER": "Enter assistant name",
|
||
"ERROR": "The name is required"
|
||
},
|
||
"TEMPERATURE": {
|
||
"LABEL": "Response Temperature",
|
||
"DESCRIPTION": "Adjust how creative or restrictive the assistant's responses should be. Lower values produce more focused and deterministic responses, while higher values allow for more creative and varied outputs."
|
||
},
|
||
"DESCRIPTION": {
|
||
"LABEL": "Description",
|
||
"PLACEHOLDER": "Enter assistant description",
|
||
"ERROR": "The description is required"
|
||
},
|
||
"PRODUCT_NAME": {
|
||
"LABEL": "Product Name",
|
||
"PLACEHOLDER": "Enter product name",
|
||
"ERROR": "The product name is required"
|
||
},
|
||
"WELCOME_MESSAGE": {
|
||
"LABEL": "Welcome Message",
|
||
"PLACEHOLDER": "Enter welcome message"
|
||
},
|
||
"HANDOFF_MESSAGE": {
|
||
"LABEL": "Handoff Message",
|
||
"PLACEHOLDER": "Enter handoff message"
|
||
},
|
||
"RESOLUTION_MESSAGE": {
|
||
"LABEL": "Resolution Message",
|
||
"PLACEHOLDER": "Enter resolution message"
|
||
},
|
||
"INSTRUCTIONS": {
|
||
"LABEL": "Instructions",
|
||
"PLACEHOLDER": "Enter instructions for the assistant"
|
||
},
|
||
"FEATURES": {
|
||
"TITLE": "Features",
|
||
"ALLOW_CONVERSATION_FAQS": "Generate FAQs from resolved conversations",
|
||
"ALLOW_MEMORIES": "Capture key details as memories from customer interactions.",
|
||
"ALLOW_CITATIONS": "Include source citations in responses"
|
||
}
|
||
},
|
||
"EDIT": {
|
||
"TITLE": "Update the assistant",
|
||
"SUCCESS_MESSAGE": "The assistant has been successfully updated",
|
||
"ERROR_MESSAGE": "There was an error updating the assistant, please try again.",
|
||
"NOT_FOUND": "Could not find the assistant. Please try again."
|
||
},
|
||
"SETTINGS": {
|
||
"HEADER": "Settings",
|
||
"BASIC_SETTINGS": {
|
||
"TITLE": "Basic settings",
|
||
"DESCRIPTION": "Customize what the assistant says when ending a conversation or transferring to a human."
|
||
},
|
||
"SYSTEM_SETTINGS": {
|
||
"TITLE": "System settings",
|
||
"DESCRIPTION": "Customize what the assistant says when ending a conversation or transferring to a human."
|
||
},
|
||
"CONTROL_ITEMS": {
|
||
"TITLE": "The Fun Stuff",
|
||
"DESCRIPTION": "Add more control to the assistant. (a bit more visual like a story : Query guardrail → scenarios → output) Nudges user to actually utilise these.",
|
||
"OPTIONS": {
|
||
"GUARDRAILS": {
|
||
"TITLE": "Guardrails",
|
||
"DESCRIPTION": "Keeps things on track—only the kinds of questions you want your assistant to answer, nothing off-limits or off-topic."
|
||
},
|
||
"RESPONSE_GUIDELINES": {
|
||
"TITLE": "Response guidelines",
|
||
"DESCRIPTION": "The vibe and structure of your assistant’s replies—clear and friendly? Short and snappy? Detailed and formal?"
|
||
}
|
||
}
|
||
},
|
||
"DELETE": {
|
||
"TITLE": "Delete Assistant",
|
||
"DESCRIPTION": "This action is permanent. Deleting this assistant will remove it from all connected inboxes and permanently erase all generated knowledge.",
|
||
"BUTTON_TEXT": "Delete {assistantName}"
|
||
}
|
||
},
|
||
"OPTIONS": {
|
||
"EDIT_ASSISTANT": "Edit Assistant",
|
||
"DELETE_ASSISTANT": "Delete Assistant",
|
||
"VIEW_CONNECTED_INBOXES": "View connected inboxes"
|
||
},
|
||
"EMPTY_STATE": {
|
||
"TITLE": "No assistants available",
|
||
"SUBTITLE": "Create an assistant to provide quick and accurate responses to your users. It can learn from your help articles and past conversations.",
|
||
"FEATURE_SPOTLIGHT": {
|
||
"TITLE": "Captain Assistant",
|
||
"NOTE": "Captain Assistant engages directly with customers, learns from your help docs and past conversations, and delivers instant, accurate responses. It handles the initial queries, providing quick resolutions before transferring to an agent when needed."
|
||
}
|
||
},
|
||
"GUARDRAILS": {
|
||
"TITLE": "Guardrails",
|
||
"DESCRIPTION": "Keeps things on track—only the kinds of questions you want your assistant to answer, nothing off-limits or off-topic.",
|
||
"BULK_ACTION": {
|
||
"SELECTED": "{count} item selected | {count} items selected",
|
||
"SELECT_ALL": "Select all ({count})",
|
||
"UNSELECT_ALL": "Unselect all ({count})",
|
||
"BULK_DELETE_BUTTON": "Delete"
|
||
},
|
||
"ADD": {
|
||
"SUGGESTED": {
|
||
"TITLE": "Example guardrails",
|
||
"ADD": "Add all",
|
||
"ADD_SINGLE": "Add this",
|
||
"SAVE": "Add and save (↵)",
|
||
"PLACEHOLDER": "Type in another guardrail..."
|
||
},
|
||
"NEW": {
|
||
"TITLE": "Add a guardrail",
|
||
"CREATE": "Create",
|
||
"CANCEL": "Cancel",
|
||
"PLACEHOLDER": "Type in another guardrail...",
|
||
"TEST_ALL": "Test all"
|
||
}
|
||
},
|
||
"LIST": {
|
||
"SEARCH_PLACEHOLDER": "Search..."
|
||
},
|
||
"EMPTY_MESSAGE": "No guardrails found. Create or add examples to begin.",
|
||
"SEARCH_EMPTY_MESSAGE": "No guardrails found for this search.",
|
||
"API": {
|
||
"ADD": {
|
||
"SUCCESS": "Guardrails added successfully",
|
||
"ERROR": "There was an error adding guardrails, please try again."
|
||
},
|
||
"UPDATE": {
|
||
"SUCCESS": "Guardrails updated successfully",
|
||
"ERROR": "There was an error updating guardrails, please try again."
|
||
},
|
||
"DELETE": {
|
||
"SUCCESS": "Guardrails deleted successfully",
|
||
"ERROR": "There was an error deleting guardrails, please try again."
|
||
}
|
||
}
|
||
},
|
||
"RESPONSE_GUIDELINES": {
|
||
"TITLE": "Response Guidelines",
|
||
"DESCRIPTION": "The vibe and structure of your assistant’s replies—clear and friendly? Short and snappy? Detailed and formal?",
|
||
"BULK_ACTION": {
|
||
"SELECTED": "{count} item selected | {count} items selected",
|
||
"SELECT_ALL": "Select all ({count})",
|
||
"UNSELECT_ALL": "Unselect all ({count})",
|
||
"BULK_DELETE_BUTTON": "Delete"
|
||
},
|
||
"ADD": {
|
||
"SUGGESTED": {
|
||
"TITLE": "Example response guidelines",
|
||
"ADD": "Add all",
|
||
"ADD_SINGLE": "Add this",
|
||
"SAVE": "Add and save (↵)",
|
||
"PLACEHOLDER": "Type in another response guideline..."
|
||
},
|
||
"NEW": {
|
||
"TITLE": "Add a response guideline",
|
||
"CREATE": "Create",
|
||
"CANCEL": "Cancel",
|
||
"PLACEHOLDER": "Type in another response guideline...",
|
||
"TEST_ALL": "Test all"
|
||
}
|
||
},
|
||
"LIST": {
|
||
"SEARCH_PLACEHOLDER": "Search..."
|
||
},
|
||
"EMPTY_MESSAGE": "No response guidelines found. Create or add examples to begin.",
|
||
"SEARCH_EMPTY_MESSAGE": "No response guidelines found for this search.",
|
||
"API": {
|
||
"ADD": {
|
||
"SUCCESS": "Response Guidelines added successfully",
|
||
"ERROR": "There was an error adding response guidelines, please try again."
|
||
},
|
||
"UPDATE": {
|
||
"SUCCESS": "Response Guidelines updated successfully",
|
||
"ERROR": "There was an error updating response guidelines, please try again."
|
||
},
|
||
"DELETE": {
|
||
"SUCCESS": "Response Guidelines deleted successfully",
|
||
"ERROR": "There was an error deleting response guidelines, please try again."
|
||
}
|
||
}
|
||
},
|
||
"SCENARIOS": {
|
||
"TITLE": "Scenarios",
|
||
"DESCRIPTION": "Give your assistant some context—like “what to do when a user is stuck,” or “how to act during a refund request.”",
|
||
"BULK_ACTION": {
|
||
"SELECTED": "{count} item selected | {count} items selected",
|
||
"SELECT_ALL": "Select all ({count})",
|
||
"UNSELECT_ALL": "Unselect all ({count})",
|
||
"BULK_DELETE_BUTTON": "Delete"
|
||
},
|
||
"ADD": {
|
||
"SUGGESTED": {
|
||
"TITLE": "Example scenarios",
|
||
"ADD": "Add all",
|
||
"ADD_SINGLE": "Add this",
|
||
"TOOLS_USED": "Tools used :"
|
||
},
|
||
"NEW": {
|
||
"CREATE": "Add a scenario",
|
||
"TITLE": "Create a scenario",
|
||
"FORM": {
|
||
"TITLE": {
|
||
"LABEL": "Title",
|
||
"PLACEHOLDER": "Enter a name for the scenario",
|
||
"ERROR": "Scenario name is required"
|
||
},
|
||
"DESCRIPTION": {
|
||
"LABEL": "Description",
|
||
"PLACEHOLDER": "Describe how and where this scenario will be used",
|
||
"ERROR": "Scenario description is required"
|
||
},
|
||
"INSTRUCTION": {
|
||
"LABEL": "How to handle",
|
||
"PLACEHOLDER": "Describe how and where this scenario will be handled",
|
||
"ERROR": "Scenario content is required"
|
||
},
|
||
"CREATE": "Create",
|
||
"CANCEL": "Cancel"
|
||
}
|
||
}
|
||
},
|
||
"UPDATE": {
|
||
"CANCEL": "Cancel",
|
||
"UPDATE": "Update changes"
|
||
},
|
||
"LIST": {
|
||
"SEARCH_PLACEHOLDER": "Search..."
|
||
},
|
||
"EMPTY_MESSAGE": "No scenarios found. Create or add examples to begin.",
|
||
"SEARCH_EMPTY_MESSAGE": "No scenarios found for this search.",
|
||
"API": {
|
||
"ADD": {
|
||
"SUCCESS": "Scenarios added successfully",
|
||
"ERROR": "There was an error adding scenarios, please try again."
|
||
},
|
||
"UPDATE": {
|
||
"SUCCESS": "Scenarios updated successfully",
|
||
"ERROR": "There was an error updating scenarios, please try again."
|
||
},
|
||
"DELETE": {
|
||
"SUCCESS": "Scenarios deleted successfully",
|
||
"ERROR": "There was an error deleting scenarios, please try again."
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"DOCUMENTS": {
|
||
"HEADER": "Documents",
|
||
"ADD_NEW": "Create a new document",
|
||
"RELATED_RESPONSES": {
|
||
"TITLE": "Related FAQs",
|
||
"DESCRIPTION": "These FAQs are generated directly from the document."
|
||
},
|
||
"FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.",
|
||
"CREATE": {
|
||
"TITLE": "Add a document",
|
||
"SUCCESS_MESSAGE": "The document has been successfully created",
|
||
"ERROR_MESSAGE": "There was an error creating the document, please try again."
|
||
},
|
||
"FORM": {
|
||
"TYPE": {
|
||
"LABEL": "Document Type",
|
||
"URL": "URL",
|
||
"PDF": "PDF File"
|
||
},
|
||
"URL": {
|
||
"LABEL": "URL",
|
||
"PLACEHOLDER": "Enter the URL of the document",
|
||
"ERROR": "Please provide a valid URL for the document"
|
||
},
|
||
"PDF_FILE": {
|
||
"LABEL": "PDF File",
|
||
"CHOOSE_FILE": "Choose PDF file",
|
||
"ERROR": "Please select a PDF file",
|
||
"HELP_TEXT": "Maximum file size: 10MB",
|
||
"INVALID_TYPE": "Please select a valid PDF file",
|
||
"TOO_LARGE": "File size exceeds 10MB limit"
|
||
},
|
||
"NAME": {
|
||
"LABEL": "Document Name (Optional)",
|
||
"PLACEHOLDER": "Enter a name for the document"
|
||
}
|
||
},
|
||
"DELETE": {
|
||
"TITLE": "Are you sure to delete the document?",
|
||
"DESCRIPTION": "This action is permanent. Deleting this document will permanently erase all generated knowledge.",
|
||
"CONFIRM": "Yes, delete",
|
||
"SUCCESS_MESSAGE": "The document has been successfully deleted",
|
||
"ERROR_MESSAGE": "There was an error deleting the document, please try again."
|
||
},
|
||
|
||
"OPTIONS": {
|
||
"VIEW_RELATED_RESPONSES": "View Related Responses",
|
||
"DELETE_DOCUMENT": "Delete Document"
|
||
},
|
||
"EMPTY_STATE": {
|
||
"TITLE": "No documents available",
|
||
"SUBTITLE": "Documents are used by your assistant to generate FAQs. You can import documents to provide context for your assistant.",
|
||
"FEATURE_SPOTLIGHT": {
|
||
"TITLE": "Captain Document",
|
||
"NOTE": "A document in Captain serves as a knowledge resource for the assistant. By connecting your help center or guides, Captain can analyze the content and provide accurate responses for customer inquiries."
|
||
}
|
||
}
|
||
},
|
||
"CUSTOM_TOOLS": {
|
||
"HEADER": "Tools",
|
||
"ADD_NEW": "Create a new tool",
|
||
"EMPTY_STATE": {
|
||
"TITLE": "No custom tools available",
|
||
"SUBTITLE": "Create custom tools to connect your assistant with external APIs and services, enabling it to fetch data and perform actions on your behalf.",
|
||
"FEATURE_SPOTLIGHT": {
|
||
"TITLE": "Custom Tools",
|
||
"NOTE": "Custom tools allow your assistant to interact with external APIs and services. Create tools to fetch data, perform actions, or integrate with your existing systems to enhance your assistant's capabilities."
|
||
}
|
||
},
|
||
"FORM_DESCRIPTION": "Configure your custom tool to connect with external APIs",
|
||
"OPTIONS": {
|
||
"EDIT_TOOL": "Edit tool",
|
||
"DELETE_TOOL": "Delete tool"
|
||
},
|
||
"CREATE": {
|
||
"TITLE": "Create Custom Tool",
|
||
"SUCCESS_MESSAGE": "Custom tool created successfully",
|
||
"ERROR_MESSAGE": "Failed to create custom tool"
|
||
},
|
||
"EDIT": {
|
||
"TITLE": "Edit Custom Tool",
|
||
"SUCCESS_MESSAGE": "Custom tool updated successfully",
|
||
"ERROR_MESSAGE": "Failed to update custom tool"
|
||
},
|
||
"DELETE": {
|
||
"TITLE": "Delete Custom Tool",
|
||
"DESCRIPTION": "Are you sure you want to delete this custom tool? This action cannot be undone.",
|
||
"CONFIRM": "Yes, delete",
|
||
"SUCCESS_MESSAGE": "Custom tool deleted successfully",
|
||
"ERROR_MESSAGE": "Failed to delete custom tool"
|
||
},
|
||
"FORM": {
|
||
"TITLE": {
|
||
"LABEL": "Tool Name",
|
||
"PLACEHOLDER": "Order Lookup",
|
||
"ERROR": "Tool name is required"
|
||
},
|
||
"DESCRIPTION": {
|
||
"LABEL": "Description",
|
||
"PLACEHOLDER": "Looks up order details by order ID"
|
||
},
|
||
"HTTP_METHOD": {
|
||
"LABEL": "Method"
|
||
},
|
||
"ENDPOINT_URL": {
|
||
"LABEL": "Endpoint URL",
|
||
"PLACEHOLDER": "https://api.example.com/orders/{'{{'} order_id {'}}'}",
|
||
"ERROR": "Valid URL is required"
|
||
},
|
||
"AUTH_TYPE": {
|
||
"LABEL": "Authentication Type"
|
||
},
|
||
"AUTH_TYPES": {
|
||
"NONE": "None",
|
||
"BEARER": "Bearer Token",
|
||
"BASIC": "Basic Auth",
|
||
"API_KEY": "API Key"
|
||
},
|
||
"AUTH_CONFIG": {
|
||
"BEARER_TOKEN": "Bearer Token",
|
||
"BEARER_TOKEN_PLACEHOLDER": "Enter your bearer token",
|
||
"USERNAME": "Username",
|
||
"USERNAME_PLACEHOLDER": "Enter username",
|
||
"PASSWORD": "Password",
|
||
"PASSWORD_PLACEHOLDER": "Enter password",
|
||
"API_KEY": "Header Name",
|
||
"API_KEY_PLACEHOLDER": "X-API-Key",
|
||
"API_VALUE": "Header Value",
|
||
"API_VALUE_PLACEHOLDER": "Enter API key value"
|
||
},
|
||
"PARAMETERS": {
|
||
"LABEL": "Parameters",
|
||
"HELP_TEXT": "Define the parameters that will be extracted from user queries"
|
||
},
|
||
"ADD_PARAMETER": "Add Parameter",
|
||
"PARAM_NAME": {
|
||
"PLACEHOLDER": "Parameter name (e.g., order_id)"
|
||
},
|
||
"PARAM_TYPE": {
|
||
"PLACEHOLDER": "Type"
|
||
},
|
||
"PARAM_TYPES": {
|
||
"STRING": "String",
|
||
"NUMBER": "Number",
|
||
"BOOLEAN": "Boolean",
|
||
"ARRAY": "Array",
|
||
"OBJECT": "Object"
|
||
},
|
||
"PARAM_DESCRIPTION": {
|
||
"PLACEHOLDER": "Description of the parameter"
|
||
},
|
||
"PARAM_REQUIRED": {
|
||
"LABEL": "Required"
|
||
},
|
||
"REQUEST_TEMPLATE": {
|
||
"LABEL": "Request Body Template (Optional)",
|
||
"PLACEHOLDER": "{'{'}\n \"order_id\": \"{'{{'} order_id {'}}'}\"\n{'}'}"
|
||
},
|
||
"RESPONSE_TEMPLATE": {
|
||
"LABEL": "Response Template (Optional)",
|
||
"PLACEHOLDER": "Order {'{{'} order_id {'}}'} status: {'{{'} status {'}}'}"
|
||
},
|
||
"ERRORS": {
|
||
"PARAM_NAME_REQUIRED": "Parameter name is required"
|
||
}
|
||
}
|
||
},
|
||
"RESPONSES": {
|
||
"HEADER": "FAQs",
|
||
"PENDING_FAQS": "Pending FAQs",
|
||
"ADD_NEW": "Create new FAQ",
|
||
"DOCUMENTABLE": {
|
||
"CONVERSATION": "Conversation #{id}"
|
||
},
|
||
"SELECTED": "{count} selected",
|
||
"SELECT_ALL": "Select all ({count})",
|
||
"UNSELECT_ALL": "Unselect all ({count})",
|
||
"SEARCH_PLACEHOLDER": "Search FAQs...",
|
||
"BULK_APPROVE_BUTTON": "Approve",
|
||
"BULK_DELETE_BUTTON": "Delete",
|
||
"BULK_APPROVE": {
|
||
"SUCCESS_MESSAGE": "FAQs approved successfully",
|
||
"ERROR_MESSAGE": "There was an error approving the FAQs, please try again."
|
||
},
|
||
"BULK_DELETE": {
|
||
"TITLE": "Delete FAQs?",
|
||
"DESCRIPTION": "Are you sure you want to delete the selected FAQs? This action cannot be undone.",
|
||
"CONFIRM": "Yes, delete all",
|
||
"SUCCESS_MESSAGE": "FAQs deleted successfully",
|
||
"ERROR_MESSAGE": "There was an error deleting the FAQs, please try again."
|
||
},
|
||
"DELETE": {
|
||
"TITLE": "Are you sure to delete the FAQ?",
|
||
"DESCRIPTION": "",
|
||
"CONFIRM": "Yes, delete",
|
||
"SUCCESS_MESSAGE": "FAQ deleted successfully",
|
||
"ERROR_MESSAGE": "There was an error deleting the FAQ, please try again."
|
||
},
|
||
"FILTER": {
|
||
"ASSISTANT": "Assistant: {selected}",
|
||
"STATUS": "Status: {selected}",
|
||
"ALL_ASSISTANTS": "All"
|
||
},
|
||
"STATUS": {
|
||
"TITLE": "Status",
|
||
"PENDING": "Pending",
|
||
"APPROVED": "Approved",
|
||
"ALL": "All"
|
||
},
|
||
"PENDING_BANNER": {
|
||
"TITLE": "Captain has found some FAQs your customers were looking for.",
|
||
"ACTION": "Click here to review"
|
||
},
|
||
"FORM_DESCRIPTION": "Add a question and its corresponding answer to the knowledge base and select the assistant it should be associated with.",
|
||
"CREATE": {
|
||
"TITLE": "Add an FAQ",
|
||
"SUCCESS_MESSAGE": "The response has been added successfully.",
|
||
"ERROR_MESSAGE": "An error occurred while adding the response. Please try again."
|
||
},
|
||
"FORM": {
|
||
"QUESTION": {
|
||
"LABEL": "Question",
|
||
"PLACEHOLDER": "Enter the question here",
|
||
"ERROR": "Please provide a valid question."
|
||
},
|
||
"ANSWER": {
|
||
"LABEL": "Answer",
|
||
"PLACEHOLDER": "Enter the answer here",
|
||
"ERROR": "Please provide a valid answer."
|
||
}
|
||
},
|
||
"EDIT": {
|
||
"TITLE": "Update the FAQ",
|
||
"SUCCESS_MESSAGE": "The FAQ has been successfully updated",
|
||
"ERROR_MESSAGE": "There was an error updating the FAQ, please try again",
|
||
"APPROVE_SUCCESS_MESSAGE": "The FAQ was marked as approved"
|
||
},
|
||
"OPTIONS": {
|
||
"APPROVE": "Approve",
|
||
"EDIT_RESPONSE": "Edit",
|
||
"DELETE_RESPONSE": "Delete"
|
||
},
|
||
"EMPTY_STATE": {
|
||
"TITLE": "No FAQs Found",
|
||
"NO_PENDING_TITLE": "There are no more pending FAQs to review",
|
||
"SUBTITLE": "FAQs help your assistant provide quick and accurate answers to questions from your customers. They can be generated automatically from your content or can be added manually.",
|
||
"CLEAR_SEARCH": "Clear active filters",
|
||
"FEATURE_SPOTLIGHT": {
|
||
"TITLE": "Captain FAQ",
|
||
"NOTE": "Captain FAQs detects common customer questions—whether missing from your knowledge base or frequently asked—and generates relevant FAQs to improve support. You can review each suggestion and decide whether to approve or reject it."
|
||
}
|
||
}
|
||
},
|
||
"INBOXES": {
|
||
"HEADER": "Connected Inboxes",
|
||
"ADD_NEW": "Connect a new inbox",
|
||
"OPTIONS": {
|
||
"DISCONNECT": "Disconnect"
|
||
},
|
||
"DELETE": {
|
||
"TITLE": "Are you sure to disconnect the inbox?",
|
||
"DESCRIPTION": "",
|
||
"CONFIRM": "Yes, delete",
|
||
"SUCCESS_MESSAGE": "The inbox was successfully disconnected.",
|
||
"ERROR_MESSAGE": "There was an error disconnecting the inbox, please try again."
|
||
},
|
||
"FORM_DESCRIPTION": "Choose an inbox to connect with the assistant.",
|
||
"CREATE": {
|
||
"TITLE": "Connect an Inbox",
|
||
"SUCCESS_MESSAGE": "The inbox was successfully connected.",
|
||
"ERROR_MESSAGE": "An error occurred while connecting the inbox. Please try again."
|
||
},
|
||
"FORM": {
|
||
"INBOX": {
|
||
"LABEL": "Inbox",
|
||
"PLACEHOLDER": "Choose the inbox to deploy the assistant.",
|
||
"ERROR": "An inbox selection is required."
|
||
}
|
||
},
|
||
"EMPTY_STATE": {
|
||
"TITLE": "No Connected Inboxes",
|
||
"SUBTITLE": "Connecting an inbox allows the assistant to handle initial questions from your customers before transferring them to you."
|
||
}
|
||
}
|
||
}
|
||
}
|