iachat/app/javascript/dashboard/i18n/locale/pt_BR/integrations.json
Gabriel Jablonski 3aca86aa43
feat(internal-chat): implement internal chat system for agents (#247)
* 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>
2026-04-11 13:50:15 -03:00

1043 lines
48 KiB
JSON

{
"INTEGRATION_SETTINGS": {
"SHOPIFY": {
"HEADER": "Shopify",
"DELETE": {
"TITLE": "Excluir Integração com o Shopify",
"MESSAGE": "Tem certeza que você deseja excluir a integração com o Shopify?"
},
"STORE_URL": {
"TITLE": "Conectar Loja Shopify",
"LABEL": "URL da Loja",
"PLACEHOLDER": "your-store.myshopify.com",
"HELP": "Insira a URL meushopify.com da sua loja Shopify",
"CANCEL": "Cancelar",
"SUBMIT": "Conectar Loja"
},
"ERROR": "Ocorreu um erro ao tentar conectar com o Shopify. Por favor, tente novamente ou entre em contato com o suporte se o problema persistir."
},
"HEADER": "Integrações",
"DESCRIPTION": "Chatwoot se integra com várias ferramentas e serviços para melhorar a eficiência de seu time. Explore a lista abaixo para configurar seus aplicativos favoritos.",
"LEARN_MORE": "Aprenda mais sobre integrações",
"LOADING": "Obtendo integrações",
"SEARCH_PLACEHOLDER": "Pesquisar integrações...",
"NO_RESULTS": "Nenhuma integração encontrada correspondente à sua busca",
"CAPTAIN": {
"DISABLED": "O capitão não está ativado na sua conta.",
"CLICK_HERE_TO_CONFIGURE": "Clique aqui para configurar",
"LOADING_CONSOLE": "Carregando console do Capitão...",
"FAILED_TO_LOAD_CONSOLE": "Falha ao carregar o Console do Capitão. Atualize e tente novamente."
},
"WEBHOOK": {
"SUBSCRIBED_EVENTS": "Eventos Inscritos",
"LEARN_MORE": "Saiba mais sobre webhooks",
"SECRET": {
"LABEL": "Segredo",
"COPY": "Copiar segredo para a área de transferência",
"COPY_SUCCESS": "Segredo copiado para a área de transferência",
"TOGGLE": "Alternar visibilidade do segredo",
"CREATED_DESC": "Seu webhook foi criado. Use o segredo abaixo para verificar as assinaturas do webhook. Copie-o agora — você também poderá encontrá-lo posteriormente no formulário de edição do webhook.",
"DONE": "Concluído"
},
"COUNT": "{n} webhook | {n} webhooks",
"SEARCH_PLACEHOLDER": "Pesquisar webhooks...",
"NO_RESULTS": "Nenhum webhook encontrado correspondente à sua busca",
"FORM": {
"CANCEL": "Cancelar",
"DESC": "Webhook Os eventos fornecem informações em tempo real sobre o que está acontecendo na sua conta do Chatwoot. Digite um URL válido para configurar um retorno de chamada.",
"SUBSCRIPTIONS": {
"LABEL": "Eventos",
"EVENTS": {
"CONVERSATION_CREATED": "Conversa Criada",
"CONVERSATION_STATUS_CHANGED": "Status de conversa alterado",
"CONVERSATION_UPDATED": "Conversa Atualizada",
"MESSAGE_CREATED": "Mensagem criada",
"MESSAGE_INCOMING": "Mensagem recebida",
"MESSAGE_OUTGOING": "Mensagem enviada",
"MESSAGE_UPDATED": "Mensagem atualizada",
"WEBWIDGET_TRIGGERED": "Widget de chat aberto pelo usuário",
"CONTACT_CREATED": "Contato criado",
"CONTACT_UPDATED": "Contato atualizado",
"CONVERSATION_TYPING_ON": "Status de Digitação ativado",
"CONVERSATION_TYPING_OFF": "Status de Digitação desativado",
"PROVIDER_EVENT_RECEIVED": "Evento do Provedor Recebido",
"INTERNAL_CHAT_MESSAGE_CREATED": "Mensagem do chat interno criada",
"INTERNAL_CHAT_MESSAGE_UPDATED": "Mensagem do chat interno atualizada",
"INTERNAL_CHAT_MESSAGE_DELETED": "Mensagem do chat interno excluída",
"INTERNAL_CHAT_CHANNEL_UPDATED": "Canal do chat interno atualizado"
}
},
"NAME": {
"LABEL": "Nome do Webhook",
"PLACEHOLDER": "Insira o nome do webhook"
},
"INBOX": {
"LABEL": "Caixa de Entrada",
"TITLE": "Selecione a caixa de entrada",
"PLACEHOLDER": "Todas as caixas de entrada",
"NO_RESULTS": "Nenhuma caixa de entrada encontrada",
"INPUT_PLACEHOLDER": "Buscar caixa de entrada"
},
"END_POINT": {
"LABEL": "URL do Webhook",
"PLACEHOLDER": "Exemplo: {webhookExampleURL}",
"ERROR": "Por favor, insira uma URL válida"
},
"EDIT_SUBMIT": "Atualizar webhook",
"ADD_SUBMIT": "Criar webhook"
},
"TITLE": "Webhook",
"CONFIGURE": "Configurar",
"HEADER": "Configurações do Webhook",
"HEADER_BTN_TXT": "Adicionar novo Webhook",
"LOADING": "Buscando Webhooks anexados",
"SEARCH_404": "Não existem itens correspondentes a esta consulta",
"SIDEBAR_TXT": "<p><b>Webhooks</b> </p> <p>Webhooks são retornos de chamada HTTP que podem ser definidos para todas as contas. Eles são acionados por eventos como a criação de mensagens no Chatwoot. Você pode criar mais de um webhook para esta conta. <br /> <br /> Para criar um <b> webhook </b>, clique no botão <b> Adicionar novo webhook </b>. Você também pode remover qualquer webhook existente clicando no botão Excluir.</p>",
"LIST": {
"404": "Não há webhooks configurados para esta conta.",
"TITLE": "Gerenciar webhooks",
"TABLE_HEADER": {
"WEBHOOK_ENDPOINT": "Webhook URL (endpoint)",
"ACTIONS": "Ações"
}
},
"EDIT": {
"BUTTON_TEXT": "Alterar",
"TITLE": "Editar webhook",
"API": {
"SUCCESS_MESSAGE": "Configuração de Webhook atualizada com sucesso",
"ERROR_MESSAGE": "Não foi possível conectar ao servidor Woot, por favor tente novamente mais tarde"
}
},
"ADD": {
"CANCEL": "Cancelar",
"TITLE": "Adicionar novo webhook",
"API": {
"SUCCESS_MESSAGE": "Configuração de Webhook adicionada com sucesso",
"ERROR_MESSAGE": "Não foi possível conectar ao servidor Woot, por favor tente novamente mais tarde"
}
},
"DELETE": {
"BUTTON_TEXT": "Excluir",
"API": {
"SUCCESS_MESSAGE": "Webhook excluído com sucesso",
"ERROR_MESSAGE": "Não foi possível conectar ao servidor Woot, por favor tente novamente mais tarde"
},
"CONFIRM": {
"TITLE": "Confirmar exclusão",
"MESSAGE": "Tem certeza que deseja excluir o webhook? ({webhookURL})",
"YES": "Sim, excluir ",
"NO": "Não, mantenha"
}
}
},
"SLACK": {
"HEADER": "Slack",
"DELETE": "Excluir",
"DELETE_CONFIRMATION": {
"TITLE": "Excluir integração",
"MESSAGE": "Tem certeza que deseja excluir a integração? Isso resultará na perda de acesso a conversas no seu espaço de trabalho Slack."
},
"HELP_TEXT": {
"TITLE": "Usando a integração com Slack",
"BODY": "Com essa integração, todas as suas conversas recebidas serão sincronizadas com o canal ***{selectedChannelName}*** em seu espaço de trabalho Slack. Você pode gerenciar todas as suas conversas com clientes diretamente no canal e nunca perder uma mensagem.\n\nAqui estão os principais recursos da integração:\n\n**Responda a conversas de dentro do Slack:** Para responder a uma conversa no canal ***{selectedChannelName}*** Slack, simplesmente digite a sua mensagem e envie-a como um tópico. Isso criará uma resposta ao cliente através do Chatwoot. É tão simples!\n\n **Crie notas privadas:** Se você quiser criar notas privadas em vez de respostas, inicie sua mensagem com ***`nota:`***. Isso garante que sua mensagem seja privada e não seja visível para o cliente.\n\n**Associar um perfil de agente:** Se a pessoa que respondeu no Slack tem um perfil de agente no Chatwoot sob o mesmo e-mail, as respostas serão associadas automaticamente com esse perfil de agente. Isso significa que você pode facilmente controlar quem disse o quê e quando. Por outro lado, quando o respondente não tiver um perfil de agente associado, as respostas aparecerão do perfil do bot para o cliente.",
"SELECTED": "selecionar"
},
"SELECT_CHANNEL": {
"OPTION_LABEL": "Selecione um canal",
"UPDATE": "Atualizar",
"BUTTON_TEXT": "Conectar canal",
"DESCRIPTION": "Seu espaço de trabalho Slack agora está ligado com o Chatwoot. No entanto, a integração está inativa no momento. Para ativar a integração e conectar um canal ao Chatwoot, por favor, clique no botão abaixo.\n\n**Nota:** Se você está tentando conectar-se a um canal privado, adicione o aplicativo Chatwoot no canal do Slack antes de prosseguir com esta etapa.",
"ATTENTION_REQUIRED": "Atenção necessária",
"EXPIRED": "Sua integração com o Slack expirou. Para continuar recebendo mensagens no Slack, exclua a integração e conecte seu espaço de trabalho novamente."
},
"UPDATE_ERROR": "Ocorreu um erro ao atualizar a integração, por favor, tente novamente",
"UPDATE_SUCCESS": "O canal foi conectado com sucesso",
"FAILED_TO_FETCH_CHANNELS": "Ocorreu um erro ao obter os canais do Slack, por favor, tente novamente"
},
"DYTE": {
"CLICK_HERE_TO_JOIN": "Clique aqui para entrar",
"LEAVE_THE_ROOM": "Sair da sala",
"START_VIDEO_CALL_HELP_TEXT": "Iniciar uma nova chamada de vídeo com o cliente",
"JOIN_ERROR": "Houve um erro ao entrar na chamada, por favor tente novamente",
"CREATE_ERROR": "Houve um erro ao criar o link da reunião, por favor tente novamente"
},
"OPEN_AI": {
"AI_ASSIST": "Inteligência Artificial",
"WITH_AI": " {option} com IA ",
"OPTIONS": {
"REPLY_SUGGESTION": "Sugestão de resposta",
"SUMMARIZE": "Resumir",
"REPHRASE": "Melhorar Escrita",
"FIX_SPELLING_GRAMMAR": "Corrigir Ortografia e Gramática",
"SHORTEN": "Encurtar",
"EXPAND": "Expandir",
"MAKE_FRIENDLY": "Alterar o tom de mensagem para amigável",
"MAKE_FORMAL": "Usar tom formal",
"SIMPLIFY": "Simplificar",
"CONFIDENT": "Use um tom confiante",
"PROFESSIONAL": "Use um tom profissional",
"CASUAL": "Use um tom casual",
"STRAIGHTFORWARD": "Use um tom direto"
},
"REPLY_OPTIONS": {
"IMPROVE_REPLY": "Melhorar resposta",
"IMPROVE_REPLY_SELECTION": "Melhorar a seleção",
"CHANGE_TONE": {
"TITLE": "Alterar tom",
"OPTIONS": {
"PROFESSIONAL": "Profissional",
"CASUAL": "Casual",
"STRAIGHTFORWARD": "Direto",
"CONFIDENT": "Confiante",
"FRIENDLY": "Amigável"
}
},
"GRAMMAR": "Corrigir gramática e ortografia",
"SUGGESTION": "Sugerir uma resposta",
"SUMMARIZE": "Resumir a conversa",
"ASK_COPILOT": "Perguntar ao Copiloto"
},
"ASSISTANCE_MODAL": {
"DRAFT_TITLE": "Conteúdo do rascunho",
"GENERATED_TITLE": "Conteúdo gerado",
"AI_WRITING": "A Inteligência Artificial está escrevendo",
"BUTTONS": {
"APPLY": "Use esta sugestão",
"CANCEL": "Cancelar"
}
},
"CTA_MODAL": {
"TITLE": "Integração com o OpenAI",
"DESC": "Traga funcionalidades avançadas de IA para o seu painel com os modelos GPT da OpenAI. Para começar, insira a chave API da sua conta OpenAI.",
"KEY_PLACEHOLDER": "Insira sua chave de API da OpenAI",
"BUTTONS": {
"NEED_HELP": "Precisa de ajuda?",
"DISMISS": "Recusar",
"FINISH": "Concluir Configuração"
},
"DISMISS_MESSAGE": "Você pode configurar a integração com o OpenAI mais tarde.",
"SUCCESS_MESSAGE": "Integração com o OpenAI configurada com sucesso"
},
"TITLE": "Melhorar com IA",
"SUMMARY_TITLE": "Resumir com IA",
"REPLY_TITLE": "Responder sugestão com IA",
"SUBTITLE": "Uma resposta melhor será gerada usando IA, com base no seu rascunho atual.",
"TONE": {
"TITLE": "Tom",
"OPTIONS": {
"PROFESSIONAL": "Profissional",
"FRIENDLY": "Amigável",
"CASUAL": "Casual",
"STRAIGHTFORWARD": "Direto",
"CONFIDENT": "Confiante"
}
},
"BUTTONS": {
"GENERATE": "Gerar",
"GENERATING": "Gerando...",
"CANCEL": "Cancelar"
},
"GENERATE_ERROR": "Ocorreu um erro ao processar o conteúdo, por favor, tente novamente"
},
"DELETE": {
"BUTTON_TEXT": "Excluir",
"API": {
"SUCCESS_MESSAGE": "Integração excluída com sucesso"
}
},
"CONNECT": {
"BUTTON_TEXT": "Conectar"
},
"DASHBOARD_APPS": {
"TITLE": "Painel de Aplicativos",
"HEADER_BTN_TXT": "Adicionar um novo aplicativo",
"SIDEBAR_TXT": "<p><b>Painel de Aplicativos</b></p><p>O Painel de Aplicativos permite que as organizações incorporem um aplicativo dentro do Painel de Aplicativos do Chatwoot para fornecer o contexto para agentes de suporte ao cliente. Este recurso permite-lhe criar uma aplicação independente e incorporá-la dentro da dashboard para fornecer informações do usuário, seus pedidos ou seu histórico de pagamentos anteriores.</p><p>Quando você incorpora seu aplicativo usando o Painel de Aplicativos do Chatwoot, sua aplicação irá obter o contexto da conversa e entrar em contato como um evento de janela. Implementar um ouvinte para o evento de mensagem em sua página receber o contexto.</p><p>Para adicionar um novo aplicativo no Painel de Aplicativos, clique no botão 'Adicionar um novo aplicativo'.</p>",
"DESCRIPTION": "O Painel de Aplicativos permite que organizações incorporem um aplicativo dentro do painel para fornecer o contexto para agentes de suporte ao cliente. Este recurso permite que você crie uma aplicação independente e incorpore para fornecer informações de usuário, seus pedidos ou seu histórico de pagamentos anteriores.",
"LEARN_MORE": "Saiba mais sobre o Painel de Aplicativos",
"COUNT": "{n} painel de aplicativo | {n} painéis de aplicativos",
"SEARCH_PLACEHOLDER": "Pesquisar painéis de aplicativos...",
"NO_RESULTS": "Nenhum painel de aplicativo encontrado correspondente à sua busca",
"LIST": {
"404": "Ainda não há aplicativos nesta conta",
"LOADING": "Buscando aplicativos...",
"TABLE_HEADER": {
"NAME": "Nome",
"ENDPOINT": "Endpoint",
"ACTIONS": "Ações"
},
"EDIT_TOOLTIP": "Alterar aplicativo",
"DELETE_TOOLTIP": "Excluir aplicativo"
},
"VIEW": {
"NOT_FOUND": "Não encontramos este aplicativo do painel."
},
"FORM": {
"TITLE_LABEL": "Nome",
"TITLE_PLACEHOLDER": "Digite um nome para o aplicativo",
"TITLE_ERROR": "É necessário um nome para o aplicativo",
"URL_LABEL": "Endpoint",
"URL_PLACEHOLDER": "Digite a URL do endpoint onde seu aplicativo está hospedado",
"URL_ERROR": "É necessário uma URL válida",
"SHOW_ON_SIDEBAR_LABEL": "Mostrar na barra lateral"
},
"CREATE": {
"HEADER": "Adicionar um novo aplicativo",
"FORM_SUBMIT": "Enviar",
"FORM_CANCEL": "Cancelar",
"API_SUCCESS": "Aplicativo configurado com sucesso",
"API_ERROR": "Não conseguimos criar um aplicativo. Por favor, tente novamente mais tarde"
},
"UPDATE": {
"HEADER": "Alterar aplicativo",
"FORM_SUBMIT": "Atualizar",
"FORM_CANCEL": "Cancelar",
"API_SUCCESS": "Aplicativo atualizado com sucesso",
"API_ERROR": "Não conseguimos atualizar o aplicativo. Por favor, tente novamente mais tarde"
},
"DELETE": {
"CONFIRM_YES": "Sim, exclua-o",
"CONFIRM_NO": "Não, mantenha-o",
"TITLE": "Confirmar exclusão",
"MESSAGE": "Tem certeza que deseja excluir o aplicativo - {appName}?",
"API_SUCCESS": "Aplicativo excluído com sucesso",
"API_ERROR": "Não conseguimos excluir o aplicativo. Por favor, tente novamente mais tarde"
}
},
"LINEAR": {
"HEADER": "Linear",
"ADD_OR_LINK_BUTTON": "Criar/Ligar Issue Linear",
"LOADING": "Buscando problemas lineares...",
"LOADING_ERROR": "Houve um erro ao buscar as entidades do time, por favor, tente novamente",
"CREATE": "Criar",
"LINK": {
"SEARCH": "Pesquisar issues",
"SELECT": "Selecionar problema",
"TITLE": "Link",
"EMPTY_LIST": "Nenhum problema linear encontrado",
"LOADING": "Carregando",
"ERROR": "Houve um erro ao buscar as questões lineares, por favor, tente novamente",
"LINK_SUCCESS": "Questão vinculada com sucesso",
"LINK_ERROR": "Houve um erro ao vincular a questão, tente novamente mais tarde",
"LINK_TITLE": "Conversa (#{conversationId}) com {name}"
},
"ADD_OR_LINK": {
"TITLE": "Criar/ligar questão linear",
"DESCRIPTION": "Crie issues no Linear a partir de conversas, ou vincule às existentes para um rastreamento integrado.",
"FORM": {
"TITLE": {
"LABEL": "Título",
"PLACEHOLDER": "Insira o título",
"REQUIRED_ERROR": "Título é obrigatório"
},
"DESCRIPTION": {
"LABEL": "Descrição",
"PLACEHOLDER": "Insira a descrição"
},
"TEAM": {
"LABEL": "Times",
"PLACEHOLDER": "Selecionar time",
"SEARCH": "Pesquisar time",
"REQUIRED_ERROR": "O time é obrigatório"
},
"ASSIGNEE": {
"LABEL": "Responsável",
"PLACEHOLDER": "Selecione o atribuído",
"SEARCH": "Pesquisar atribuído"
},
"PRIORITY": {
"LABEL": "Prioridade",
"PLACEHOLDER": "Selecionar prioridade",
"SEARCH": "Pesquisar prioridade"
},
"LABEL": {
"LABEL": "Nome do campo",
"PLACEHOLDER": "Selecione a etiqueta",
"SEARCH": "Pesquisar etiqueta"
},
"STATUS": {
"LABEL": "Status",
"PLACEHOLDER": "Selecione Status",
"SEARCH": "Pesquisar status"
},
"PROJECT": {
"LABEL": "Projeto",
"PLACEHOLDER": "Selecionar projeto",
"SEARCH": "Pesquisar projeto"
}
},
"CREATE": "Criar",
"CANCEL": "Cancelar",
"CREATE_SUCCESS": "Pasta criada com sucesso",
"CREATE_ERROR": "Houve um erro ao criar a questão, por favor, tente novamente",
"LOADING_TEAM_ERROR": "Houve um erro ao buscar os times, por favor, tente novamente",
"LOADING_TEAM_ENTITIES_ERROR": "Houve um erro ao buscar as entidades do time, por favor, tente novamente"
},
"ISSUE": {
"STATUS": "Status",
"PRIORITY": "Prioridade",
"ASSIGNEE": "Responsável",
"LABELS": "Etiquetas",
"CREATED_AT": "Criado em {createdAt}"
},
"UNLINK": {
"TITLE": "Desvincular",
"SUCCESS": "Issue desvinculada com sucesso",
"ERROR": "Houve um erro ao desvincular o atributo, por favor, tente novamente"
},
"NO_LINKED_ISSUES": "Nenhuma tarefa vinculada foi encontrada",
"DELETE": {
"TITLE": "Tem certeza que deseja excluir esta integração?",
"MESSAGE": "Tem certeza que deseja excluir esta integração?",
"CONFIRM": "Sim, excluir",
"CANCEL": "Cancelar"
},
"CTA": {
"TITLE": "Conectar ao Linear",
"AGENT_DESCRIPTION": "O espaço de trabalho do Linear não está conectado. Solicite ao seu administrador para conectar um espaço de trabalho para usar essa integração.",
"DESCRIPTION": "O espaço de trabalho do Linear não está conectado. Clique no botão abaixo para conectar seu espaço de trabalho para usar essa integração.",
"BUTTON_TEXT": "Conectar espaço de trabalho do Linear"
}
},
"NOTION": {
"HEADER": "Notion",
"DELETE": {
"TITLE": "Você tem certeza que deseja excluir a integração com Notion?",
"MESSAGE": "Excluir essa integração removerá o acesso ao seu espaço de trabalho Notion e encerrará todas as funcionalidades relacionadas.",
"CONFIRM": "Sim, excluir",
"CANCEL": "Cancelar"
}
}
},
"CAPTAIN": {
"NAME": "Capitão",
"HEADER_KNOW_MORE": "Saiba mais",
"ASSISTANT_SWITCHER": {
"ASSISTANTS": "Assistentes",
"SWITCH_ASSISTANT": "Alternar entre assistentes",
"NEW_ASSISTANT": "Criar Assistente",
"EMPTY_LIST": "Nenhum assistente encontrado. Crie um para começar"
},
"COPILOT": {
"TITLE": "Copiloto",
"TRY_THESE_PROMPTS": "Experimente estes comandos",
"PANEL_TITLE": "Comece a usar o Copiloto",
"KICK_OFF_MESSAGE": "Precisa de um resumo rápido, quer verificar conversas passadas ou redigir uma resposta melhor? O Copiloto está aqui para acelerar as coisas.",
"SEND_MESSAGE": "Enviar mensagem...",
"EMPTY_MESSAGE": "Houve um erro ao gerar a resposta. Por favor, tente novamente.",
"LOADER": "Capitão está pensando",
"YOU": "Você",
"USE": "Use isto",
"RESET": "Reiniciar",
"SHOW_STEPS": "Mostrar etapas",
"SELECT_ASSISTANT": "Selecione o Assistente",
"PROMPTS": {
"SUMMARIZE": {
"LABEL": "Resumir esta conversa",
"CONTENT": "Resuma os pontos-chave discutidos entre o cliente e o agente de suporte, incluindo as preocupações do cliente, as questões e as soluções ou respostas dadas pelo agente de suporte"
},
"SUGGEST": {
"LABEL": "Sugerir uma resposta",
"CONTENT": "Analise a pergunta do cliente e elabore uma resposta que responda efetivamente às suas preocupações ou perguntas. Certifique-se de que a resposta seja clara, concisa e forneça informações úteis."
},
"RATE": {
"LABEL": "Avaliar esta conversa",
"CONTENT": "Revise a conversa para ver o quanto ela atende às necessidades do cliente. Compartilhe uma classificação de 0 a 5 com base em tom, clareza e eficácia."
},
"HIGH_PRIORITY": {
"LABEL": "Conversas de alta prioridade",
"CONTENT": "Dê um resumo de todas as conversas abertas com prioridade alta. Incluir o ID da conversa, nome do cliente (se disponível), o conteúdo da última mensagem e o agente atribuído. O grupo por status, se relevante."
},
"LIST_CONTACTS": {
"LABEL": "Listar contatos",
"CONTENT": "Mostre-me a lista dos 10 contatos mais frequentes. Inclua nome, e-mail ou número de telefone (se disponível), visto por última vez, etiquetas (se houver)."
}
}
},
"PLAYGROUND": {
"USER": "Você",
"ASSISTANT": "Assistente",
"MESSAGE_PLACEHOLDER": "Digite sua mensagem...",
"HEADER": "Playground",
"DESCRIPTION": "Use este playground para enviar mensagens para seu assistente e verificar se ele responde com precisão, rápido e no tom que você espera.",
"CREDIT_NOTE": "As mensagens enviadas aqui usam os créditos do seu Capitão."
},
"PAYWALL": {
"TITLE": "Atualize para usar o Capitão IA",
"AVAILABLE_ON": "O Capitão não está disponível no plano gratuito.",
"UPGRADE_PROMPT": "Atualize seu plano para ter acesso aos nossos assistentes, copilotos e muito mais.",
"UPGRADE_NOW": "Atualizar agora",
"CANCEL_ANYTIME": "Você pode alterar ou cancelar seu plano a qualquer momento"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "O Capitão IA está disponível apenas nos planos Enterprise.",
"UPGRADE_PROMPT": "Atualize seu plano para ter acesso aos nossos assistentes, copilotos e muito mais.",
"ASK_ADMIN": "Entre em contato com seu administrador para fazer a atualização."
},
"BANNER": {
"RESPONSES": "Você usou mais de 80% do seu limite de respostas. Para continuar usando o Capitão AI, faça o upgrade.",
"DOCUMENTS": "Limite de documentos atingido. Atualize para continuar usando o Capitão AI."
},
"FORM": {
"CANCEL": "Cancelar",
"CREATE": "Criar",
"EDIT": "Atualizar"
},
"ASSISTANTS": {
"HEADER": "Assistentes",
"NO_ASSISTANTS_AVAILABLE": "Não há assistentes disponíveis em sua conta.",
"ADD_NEW": "Criar um novo assistente",
"DELETE": {
"TITLE": "Tem certeza que deseja excluir o assistente?",
"DESCRIPTION": "Esta ação é permanente. Excluir este assistente irá removê-lo de todas as caixas de entrada conectadas e apagará permanentemente todo o conhecimento gerado.",
"CONFIRM": "Sim, excluir",
"SUCCESS_MESSAGE": "O assistente foi excluído com sucesso",
"ERROR_MESSAGE": "Ocorreu um erro ao excluir o assistente, por favor tente novamente."
},
"FORM_DESCRIPTION": "Preencha os detalhes abaixo para nomear seu assistente, descrever sua finalidade e especificar o produto que ele oferecerá suporte.",
"CREATE": {
"TITLE": "Criar um assistente",
"SUCCESS_MESSAGE": "O assistente foi criado com sucesso",
"ERROR_MESSAGE": "Ocorreu um erro ao criar o assistente, por favor tente novamente."
},
"FORM": {
"UPDATE": "Atualizar",
"SECTIONS": {
"BASIC_INFO": "Informações Básicas",
"SYSTEM_MESSAGES": "Mensagens do Sistema",
"INSTRUCTIONS": "Instruções",
"FEATURES": "Funcionalidades",
"TOOLS": "Ferramentas "
},
"NAME": {
"LABEL": "Nome",
"PLACEHOLDER": "Digite o nome do assistente",
"ERROR": "O nome é obrigatório"
},
"TEMPERATURE": {
"LABEL": "Temperatura da resposta",
"DESCRIPTION": "Ajuste o quão criativo ou restritivo as respostas do assistente devem ser. Valores mais baixos produzem respostas mais focadas e deterministas, enquanto valores mais altos permitem resultados mais criativos e variados."
},
"DESCRIPTION": {
"LABEL": "Descrição",
"PLACEHOLDER": "Digite a descrição do assistente",
"ERROR": "A descrição é obrigatória"
},
"PRODUCT_NAME": {
"LABEL": "Nome do Produto",
"PLACEHOLDER": "Digite o nome do produto",
"ERROR": "O nome do produto é obrigatório"
},
"WELCOME_MESSAGE": {
"LABEL": "Mensagem de boas-vindas",
"PLACEHOLDER": "Digite a mensagem de boas-vindas"
},
"HANDOFF_MESSAGE": {
"LABEL": "Mensagem de transferência",
"PLACEHOLDER": "Digite a mensagem de transferência"
},
"RESOLUTION_MESSAGE": {
"LABEL": "Mensagem de resolução",
"PLACEHOLDER": "Digite a mensagem de resolução"
},
"INSTRUCTIONS": {
"LABEL": "Instruções",
"PLACEHOLDER": "Digite as instruções para o assistente"
},
"FEATURES": {
"TITLE": "Funcionalidades",
"ALLOW_CONVERSATION_FAQS": "Gerar perguntas frequentes a partir de conversas resolvidas",
"ALLOW_MEMORIES": "Capture os principais detalhes como memórias de interações do cliente.",
"ALLOW_CITATIONS": "Incluir fonte de citações nas respostas"
}
},
"EDIT": {
"TITLE": "Atualizar o assistente",
"SUCCESS_MESSAGE": "O assistente foi criado com sucesso",
"ERROR_MESSAGE": "Ocorreu um erro ao criar o assistente, por favor tente novamente.",
"NOT_FOUND": "Não foi possível encontrar o assistente. Tente novamente."
},
"SETTINGS": {
"HEADER": "Configurações",
"BASIC_SETTINGS": {
"TITLE": "Configurações básicas",
"DESCRIPTION": "Personalize o que o assistente diz quando termina uma conversa ou transfere para um humano."
},
"SYSTEM_SETTINGS": {
"TITLE": "Configurações do sistema",
"DESCRIPTION": "Personalize o que o assistente diz quando termina uma conversa ou transfere para um humano."
},
"CONTROL_ITEMS": {
"TITLE": "As Coisas Divertidas",
"DESCRIPTION": "Adicione mais controle ao assistente. (algo mais visual como uma história: Consulta → cenários → saída) Força o usuário para realmente utilizá-los.",
"OPTIONS": {
"GUARDRAILS": {
"TITLE": "Proteções",
"DESCRIPTION": "Mantém as coisas no caminho — apenas os tipos de perguntas que você quer que seu assistente responda, nada fora de limites ou fora do tópico."
},
"RESPONSE_GUIDELINES": {
"TITLE": "Diretrizes de resposta",
"DESCRIPTION": "O jeito e a estrutura das respostas do seu assistente — tranquilo e amigável? Curto e ágil? Detalhado e formal?"
}
}
},
"DELETE": {
"TITLE": "Excluir Assistente",
"DESCRIPTION": "Esta ação é permanente. Excluir este assistente irá removê-lo de todas as caixas de entrada conectadas e apagará permanentemente todo o conhecimento gerado.",
"BUTTON_TEXT": "Excluir {assistantName}"
}
},
"OPTIONS": {
"EDIT_ASSISTANT": "Editar Assistente",
"DELETE_ASSISTANT": "Excluir Assistente",
"VIEW_CONNECTED_INBOXES": "Ver caixas de entrada conectadas"
},
"EMPTY_STATE": {
"TITLE": "Não há assistentes disponíveis",
"SUBTITLE": "Crie um assistente para fornecer respostas rápidas e precisas aos seus usuários. Ele pode aprender com seus artigos de ajuda e conversas passadas.",
"FEATURE_SPOTLIGHT": {
"TITLE": "Assistente Capitão",
"NOTE": "O Assistente Capitão interage diretamente com os clientes, aprende com seus documentos de ajuda e conversas anteriores e fornece respostas instantâneas e precisas. Ele lida com as solicitações iniciais, oferecendo resoluções rápidas antes de transferir para um agente quando necessário."
}
},
"GUARDRAILS": {
"TITLE": "Proteções",
"DESCRIPTION": "Mantém as coisas no caminho — apenas os tipos de perguntas que você quer que seu assistente responda, nada fora de limites ou fora do tópico.",
"BULK_ACTION": {
"SELECTED": "{count} item selecionado | {count} itens selecionados",
"SELECT_ALL": "Selecionar todos ({count})",
"UNSELECT_ALL": "Desmarcar todos ({count})",
"BULK_DELETE_BUTTON": "Excluir"
},
"ADD": {
"SUGGESTED": {
"TITLE": "Exemplos de proteções",
"ADD": "Adicionar todos",
"ADD_SINGLE": "Adicionar este",
"SAVE": "Adicionar e salvar (↵)",
"PLACEHOLDER": "Escreva outra proteção"
},
"NEW": {
"TITLE": "Adicionar proteção",
"CREATE": "Criar",
"CANCEL": "Cancelar",
"PLACEHOLDER": "Escreva outra proteção",
"TEST_ALL": "Testar tudo"
}
},
"LIST": {
"SEARCH_PLACEHOLDER": "Pesquisar..."
},
"EMPTY_MESSAGE": "Nenhuma proteção encontrada. Crie uma ou adicione exemplos para começar.",
"SEARCH_EMPTY_MESSAGE": "Nenhuma proteção encontrada para essa pesquisa.",
"API": {
"ADD": {
"SUCCESS": "Proteções adicionadas com sucesso",
"ERROR": "Ocorreu um erro ao adicionar as proteções. Por favor, tente novamente."
},
"UPDATE": {
"SUCCESS": "Proteções atualizados com sucesso",
"ERROR": "Ocorreu um erro ao atualizar as proteções. Por favor, tente novamente."
},
"DELETE": {
"SUCCESS": "Proteções removidas com sucesso",
"ERROR": "Ocorreu um erro ao excluir as proteções, por favor, tente novamente."
}
}
},
"RESPONSE_GUIDELINES": {
"TITLE": "Diretrizes de Resposta",
"DESCRIPTION": "O jeito e a estrutura das respostas do seu assistente — tranquilo e amigável? Curto e ágil? Detalhado e formal?",
"BULK_ACTION": {
"SELECTED": "{count} item selecionado | {count} itens selecionados",
"SELECT_ALL": "Selecionar todos ({count})",
"UNSELECT_ALL": "Desmarcar todos ({count})",
"BULK_DELETE_BUTTON": "Excluir"
},
"ADD": {
"SUGGESTED": {
"TITLE": "Exemplos de diretrizes de resposta",
"ADD": "Adicionar todos",
"ADD_SINGLE": "Adicionar este",
"SAVE": "Adicionar e salvar (↵)",
"PLACEHOLDER": "Escreva uma outra diretriz de resposta..."
},
"NEW": {
"TITLE": "Adicione uma diretriz de resposta",
"CREATE": "Criar",
"CANCEL": "Cancelar",
"PLACEHOLDER": "Escreva uma outra diretriz de resposta...",
"TEST_ALL": "Testar tudo"
}
},
"LIST": {
"SEARCH_PLACEHOLDER": "Pesquisar..."
},
"EMPTY_MESSAGE": "Nenhuma diretriz de resposta encontrada. Crie uma ou adicione exemplos para começar.",
"SEARCH_EMPTY_MESSAGE": "Nenhuma diretriz de resposta encotrada para essa pesquisa.",
"API": {
"ADD": {
"SUCCESS": "Diretrizes de resposta adicionadas com sucesso",
"ERROR": "Houve um erro ao adicionar diretrizes de resposta, por favor, tente novamente."
},
"UPDATE": {
"SUCCESS": "Diretrizes de Resposta atualizadas com sucesso",
"ERROR": "Houve um erro ao atualizar as diretrizes de resposta, por favor, tente novamente."
},
"DELETE": {
"SUCCESS": "Diretrizes de resposta removidas com sucesso",
"ERROR": "Houve um erro ao excluir as diretrizes de resposta, por favor, tente novamente."
}
}
},
"SCENARIOS": {
"TITLE": "Cenários",
"DESCRIPTION": "Dê algum contexto ao seu assistente — como \"o que fazer quando um usuário estiver com problemas\", ou \"como agir durante uma solicitação de reembolso\".",
"BULK_ACTION": {
"SELECTED": "{count} item selecionado | {count} itens selecionados",
"SELECT_ALL": "Selecionar todos ({count})",
"UNSELECT_ALL": "Desmarcar todos ({count})",
"BULK_DELETE_BUTTON": "Excluir"
},
"ADD": {
"SUGGESTED": {
"TITLE": "Exemplos de cenários",
"ADD": "Adicionar todos",
"ADD_SINGLE": "Adicionar este",
"TOOLS_USED": "Ferramentas usadas :"
},
"NEW": {
"CREATE": "Adicionar um cenário",
"TITLE": "Criar um cenário",
"FORM": {
"TITLE": {
"LABEL": "Título",
"PLACEHOLDER": "Digite um nome para o cenário",
"ERROR": "O nome do cenário é obrigatório"
},
"DESCRIPTION": {
"LABEL": "Descrição",
"PLACEHOLDER": "Descreva como e onde este cenário será utilizado",
"ERROR": "Descrição do cenário é obrigatória"
},
"INSTRUCTION": {
"LABEL": "Como lidar",
"PLACEHOLDER": "Descreva como e onde este cenário será utilizado",
"ERROR": "Conteúdo do cenário é obrigatório"
},
"CREATE": "Criar",
"CANCEL": "Cancelar"
}
}
},
"UPDATE": {
"CANCEL": "Cancelar",
"UPDATE": "Atualizar alterações"
},
"LIST": {
"SEARCH_PLACEHOLDER": "Pesquisar..."
},
"EMPTY_MESSAGE": "Nenhum cenário encontrado. Crie ou adicione exemplos para começar.",
"SEARCH_EMPTY_MESSAGE": "Nenhum cenário encontrado para esta pesquisa.",
"API": {
"ADD": {
"SUCCESS": "Cenários adicionados com sucesso",
"ERROR": "Ocorreu um erro ao adicionar cenários, por favor tente novamente."
},
"UPDATE": {
"SUCCESS": "Cenários atualizados com sucesso",
"ERROR": "Ocorreu um erro ao atualizar cenários, por favor tente novamente."
},
"DELETE": {
"SUCCESS": "Cenários excluídos com sucesso",
"ERROR": "Ocorreu um erro ao excluir os cenários, por favor tente novamente."
}
}
}
},
"DOCUMENTS": {
"HEADER": "Documentos",
"ADD_NEW": "Criar um novo documento",
"RELATED_RESPONSES": {
"TITLE": "FAQs Relacionadas",
"DESCRIPTION": "Estes FAQs são gerados diretamente a partir do documento."
},
"FORM_DESCRIPTION": "Digite o URL do documento para adicioná-lo como fonte de conhecimento e escolha o assistente para associá-lo.",
"CREATE": {
"TITLE": "Adicionar um documento",
"SUCCESS_MESSAGE": "O documento foi criado com sucesso",
"ERROR_MESSAGE": "Ocorreu um erro ao criar o documento, por favor, tente novamente."
},
"FORM": {
"TYPE": {
"LABEL": "Tipo do documento",
"URL": "URL:",
"PDF": "Arquivo PDF"
},
"URL": {
"LABEL": "URL:",
"PLACEHOLDER": "Insira a URL do documento",
"ERROR": "Por favor forneça uma URL válida para o documento"
},
"PDF_FILE": {
"LABEL": "Arquivo PDF",
"CHOOSE_FILE": "Escolher arquivo PDF",
"ERROR": "Por favor, selecione um arquivo PDF",
"HELP_TEXT": "Tamanho máximo do arquivo: 10 MB",
"INVALID_TYPE": "Por favor, selecione um arquivo PDF válido",
"TOO_LARGE": "O tamanho do arquivo excede o limite de 10 MB"
},
"NAME": {
"LABEL": "Nome do documento (opcional)",
"PLACEHOLDER": "Insira um nome para o documento"
}
},
"DELETE": {
"TITLE": "Tem certeza de que deseja excluir este documento?",
"DESCRIPTION": "Esta ação é permanente. Eliminar este documento irá apagar permanentemente todo o conhecimento gerado.",
"CONFIRM": "Sim, excluir",
"SUCCESS_MESSAGE": "O documento foi excluído com sucesso",
"ERROR_MESSAGE": "Ocorreu um erro ao excluir o documento, por favor, tente novamente."
},
"OPTIONS": {
"VIEW_RELATED_RESPONSES": "Visualizar Respostas relacionadas",
"DELETE_DOCUMENT": "Excluir Documento"
},
"EMPTY_STATE": {
"TITLE": "Nenhum documento disponível",
"SUBTITLE": "Os documentos são usados pelo seu assistente para gerar perguntas frequentes. Pode importar documentos para fornecer um contexto para seu assistente.",
"FEATURE_SPOTLIGHT": {
"TITLE": "Documento do Capitão",
"NOTE": "Um documento no Capitão serve como um recurso de conhecimento para o assistente. Ao conectar sua central de ajuda ou guias, o Capitão pode analisar o conteúdo e fornecer respostas precisas para as dúvidas dos clientes."
}
}
},
"CUSTOM_TOOLS": {
"HEADER": "Ferramentas",
"ADD_NEW": "Criar ferramenta",
"EMPTY_STATE": {
"TITLE": "Não há ferramentas personalizadas disponíveis",
"SUBTITLE": "Crie ferramentas personalizadas para conectar com APIs e serviços externos, permitindo obter dados e agir por você.",
"FEATURE_SPOTLIGHT": {
"TITLE": "Ferramentas Personalizadas",
"NOTE": "Ferramentas personalizadas permitem seu assistente interagir com APIs e serviços externos. Crie ferramentas para obter dados, realizar ações ou integre com seus sistemas existentes para melhorar as capacidades do seu assistente."
}
},
"FORM_DESCRIPTION": "Configure sua ferramenta personalizada para se conectar a APIs externas",
"OPTIONS": {
"EDIT_TOOL": "Editar ferramenta",
"DELETE_TOOL": "Excluir ferramenta"
},
"CREATE": {
"TITLE": "Criar Ferramenta Personalizada",
"SUCCESS_MESSAGE": "Ferramenta personalizada criada com sucesso",
"ERROR_MESSAGE": "Falha ao criar a ferramenta personalizada"
},
"EDIT": {
"TITLE": "Editar Ferramenta Personalizada",
"SUCCESS_MESSAGE": "Ferramenta personalizada atualizada com sucesso",
"ERROR_MESSAGE": "Falha ao atualizar a ferramenta personalizada"
},
"DELETE": {
"TITLE": "Excluir ferramenta personalizada",
"DESCRIPTION": "Tem certeza que deseja excluir está ferramenta customizável? Está ação não pode ser desfeita.",
"CONFIRM": "Sim, excluir",
"SUCCESS_MESSAGE": "Ferramenta personalizada excluída com sucesso",
"ERROR_MESSAGE": "Falha ao excluir a ferramenta personalizada"
},
"FORM": {
"TITLE": {
"LABEL": "Nome da Ferramenta",
"PLACEHOLDER": "Consulta de pedido",
"ERROR": "Nome da ferramente obrigatória"
},
"DESCRIPTION": {
"LABEL": "Descrição",
"PLACEHOLDER": "Consulta os detalhes do pedido pelo ID do pedido"
},
"HTTP_METHOD": {
"LABEL": "Método"
},
"ENDPOINT_URL": {
"LABEL": "URL do endpoint",
"PLACEHOLDER": "https://api.example.com/orders/{'{{'} order_id {'}}'}",
"ERROR": "É necessário informar uma URL válida"
},
"AUTH_TYPE": {
"LABEL": "Tipo de Autenticação"
},
"AUTH_TYPES": {
"NONE": "Nenhuma",
"BEARER": "Bearer Token",
"BASIC": "Basic Auth",
"API_KEY": "Chave API"
},
"AUTH_CONFIG": {
"BEARER_TOKEN": "Bearer Token",
"BEARER_TOKEN_PLACEHOLDER": "Insira seu bearer token",
"USERNAME": "Nome de usuário",
"USERNAME_PLACEHOLDER": "Informe o nome de usuário",
"PASSWORD": "Senha",
"PASSWORD_PLACEHOLDER": "Informe a senha",
"API_KEY": "Nome do Cabeçalho",
"API_KEY_PLACEHOLDER": "X-API-Key",
"API_VALUE": "Valor do Cabeçalho",
"API_VALUE_PLACEHOLDER": "Insira o valor da chave de API"
},
"PARAMETERS": {
"LABEL": "Parâmetros",
"HELP_TEXT": "Defina os parâmetros que serão extraídos das consultas dos usuários"
},
"ADD_PARAMETER": "Adicionar Parâmetro",
"PARAM_NAME": {
"PLACEHOLDER": "Nome do parâmetro (ex.: order_id)"
},
"PARAM_TYPE": {
"PLACEHOLDER": "Tipo"
},
"PARAM_TYPES": {
"STRING": "String",
"NUMBER": "Número",
"BOOLEAN": "Boolean",
"ARRAY": "Array",
"OBJECT": "Object"
},
"PARAM_DESCRIPTION": {
"PLACEHOLDER": "Descrição do parâmetro"
},
"PARAM_REQUIRED": {
"LABEL": "Obrigatório"
},
"REQUEST_TEMPLATE": {
"LABEL": "Modelo do Corpo da Requisição (Opcional)",
"PLACEHOLDER": "{'{'}\n \"order_id\": \"{'{{'} order_id {'}}'}\"\n{'}'}"
},
"RESPONSE_TEMPLATE": {
"LABEL": "Modelo de Resposta (Opcional)",
"PLACEHOLDER": "Order {'{{'} order_id {'}}'} status: {'{{'} status {'}}'}"
},
"ERRORS": {
"PARAM_NAME_REQUIRED": "O nome do parâmetro é obrigatório"
}
}
},
"RESPONSES": {
"HEADER": "FAQs",
"PENDING_FAQS": "FAQs pendentes",
"ADD_NEW": "Criar nova FAQ",
"DOCUMENTABLE": {
"CONVERSATION": "Conversação #{id}"
},
"SELECTED": "{count} selecionado",
"SELECT_ALL": "Selecionar todos ({count})",
"UNSELECT_ALL": "Desmarcar todos ({count})",
"SEARCH_PLACEHOLDER": "Pesquisar FAQs...",
"BULK_APPROVE_BUTTON": "Aprovar",
"BULK_DELETE_BUTTON": "Excluir",
"BULK_APPROVE": {
"SUCCESS_MESSAGE": "Perguntas Frequentes aprovadas com sucesso",
"ERROR_MESSAGE": "Ocorreu um erro ao aprovar as Perguntas Frequentes. Tente novamente."
},
"BULK_DELETE": {
"TITLE": "Excluir as Perguntas Frequentes?",
"DESCRIPTION": "Tem certeza que deseja excluir as Perguntas Frequentes selecionadas? Esta ação não pode ser desfeita.",
"CONFIRM": "Sim, excluir todas",
"SUCCESS_MESSAGE": "Perguntas Frequentes excluídas com sucesso/",
"ERROR_MESSAGE": "Ocorreu um erro ao excluir as Perguntas Frequentes, por favor tente novamente."
},
"DELETE": {
"TITLE": "Tem certeza que deseja excluir o FAQ?",
"DESCRIPTION": "",
"CONFIRM": "Sim, excluir",
"SUCCESS_MESSAGE": "FAQ excluída com sucesso",
"ERROR_MESSAGE": "Ocorreu um erro ao excluir a FAQ, por favor tente novamente."
},
"FILTER": {
"ASSISTANT": "Assistente: {selected}",
"STATUS": "Status: {selected}",
"ALL_ASSISTANTS": "Todos"
},
"STATUS": {
"TITLE": "Status",
"PENDING": "Pendentes",
"APPROVED": "Aceito",
"ALL": "Todos"
},
"PENDING_BANNER": {
"TITLE": "O Capitão encontrou algumas FAQs que seus clientes estavam procurando.",
"ACTION": "Clique aqui para revisar"
},
"FORM_DESCRIPTION": "Adicione uma pergunta e sua resposta correspondente à base de conhecimento e selecione o assistente ao qual deve estar associado.",
"CREATE": {
"TITLE": "Adicionar uma FAQ",
"SUCCESS_MESSAGE": "A resposta foi adicionada com sucesso.",
"ERROR_MESSAGE": "Ocorreu um erro ao adicionar a resposta. Por favor, tente novamente."
},
"FORM": {
"QUESTION": {
"LABEL": "Pergunta",
"PLACEHOLDER": "Digite a pergunta aqui",
"ERROR": "Por favor, forneça uma pergunta válida."
},
"ANSWER": {
"LABEL": "Responder",
"PLACEHOLDER": "Digite a resposta aqui",
"ERROR": "Por favor, forneça uma resposta válida."
}
},
"EDIT": {
"TITLE": "Atualizar as Perguntas Frequentes",
"SUCCESS_MESSAGE": "As Perguntas Frequentes foram atualizadas com sucesso",
"ERROR_MESSAGE": "Ocorreu um erro ao atualizar as Perguntas Frequentes, por favor tente novamente",
"APPROVE_SUCCESS_MESSAGE": "As Perguntas Frequentes foram marcadas como aprovadas"
},
"OPTIONS": {
"APPROVE": "Aprovar",
"EDIT_RESPONSE": "Alterar",
"DELETE_RESPONSE": "Excluir"
},
"EMPTY_STATE": {
"TITLE": "Nenhuma FAQ encontrada",
"NO_PENDING_TITLE": "Não há mais FAQs pendentes para revisão",
"SUBTITLE": "Perguntas Frequentes ajudam seu assistente a fornecer respostas rápidas e precisas para perguntas de seus clientes. Eles podem ser gerados automaticamente a partir do seu conteúdo ou podem ser adicionados manualmente.",
"CLEAR_SEARCH": "Limpar filtros ativos",
"FEATURE_SPOTLIGHT": {
"TITLE": "FAQ do Capitão",
"NOTE": "As FAQs do Capitão identificam as perguntas mais comuns dos clientes — tanto as que ainda não existem na sua base de conhecimento quanto as mais recorrentes — e gera FAQs relevantes para melhorar o atendimento. Você pode revisar cada sugestão e decidir se aprova ou rejeita."
}
}
},
"INBOXES": {
"HEADER": "Caixas de entrada conectadas",
"ADD_NEW": "Conectar uma nova caixa de entrada",
"OPTIONS": {
"DISCONNECT": "Desconectar"
},
"DELETE": {
"TITLE": "Tem certeza que deseja desconectar a caixa de entrada?",
"DESCRIPTION": "",
"CONFIRM": "Sim, excluir",
"SUCCESS_MESSAGE": "A caixa de entrada foi desconectada com sucesso.",
"ERROR_MESSAGE": "Ocorreu um erro ao desconectar a caixa de entrada, por favor, tente novamente."
},
"FORM_DESCRIPTION": "Escolha uma caixa de entrada para conectar com o assistente.",
"CREATE": {
"TITLE": "Conecte uma caixa de entrada",
"SUCCESS_MESSAGE": "A caixa de entrada foi conectada com sucesso.",
"ERROR_MESSAGE": "Ocorreu um erro ao conectar à caixa de entrada. Por favor, tente novamente."
},
"FORM": {
"INBOX": {
"LABEL": "Caixa de Entrada",
"PLACEHOLDER": "Selecione a caixa de entrada para implantar o assistente.",
"ERROR": "É obrigatório selecionar uma caixa de entrada."
}
},
"EMPTY_STATE": {
"TITLE": "Caixa de entrada não conectada",
"SUBTITLE": "Conectar uma caixa de entrada permite ao assistente lidar com perguntas iniciais de seus clientes antes de transferi-las para você."
}
}
}
}