find_ai_chat 0.8.0
find_ai_chat: ^0.8.0 copied to clipboard
Flutter SDK for the Find AI chat assistant. Drop-in widget with floating, drawer and embedded modes, SSE streaming, and signed-mode visitor auth.
Changelog #
0.8.0 #
-
Links inside a message are now tappable. Markdown links were already painted as links (primary colour, underlined) but nothing happened when you tapped them: the widget never passed
onTapLinkto the Markdown body and the package only fires its tap recognizer when that callback is set. Since the URL hides behind the link text, a visitor could neither open it nor copy it. This matters most when the assistant answers with a product or a course: the one thing the visitor is meant to do with that answer did not work. -
FindChatCallbacks.onLinkTaplets the host app take a link before the SDK opens it. Returntrueif you handled it,false(or leave the callback out) to let the SDK open it externally. It exists because assistants usually link back into the host app itself, and sending a visitor to a browser tab to reach a screen they already have in front of them loses them:FindChatCallbacks( onLinkTap: (url) { if (url.host != 'app.mycompany.com') return false; // browser context.go(url.fragment); // in-app return true; }, ) -
Only
http,https,mailtoandtelare opened. The link target is written by a model out of tenant data, not by the host app, sojavascript:anddata:are dropped before anything runs: on Flutter Web opening one of those means executing code in the page that hosts the chat. A dropped link is silent, which is what the widget already did before links were tappable. Relative links are dropped too: a message has no base to resolve them against. -
New
link_tappedevent onFindChatCallbacks.onEvent, carrying the targetUri. It is emitted only for links the SDK accepted, and before the host callback decides, so analytics sees the same link that is about to open. -
Adds a dependency on
url_launcher.
0.7.0 #
- Deleting a conversation now deletes it on the server
(
deleteConversation()), instead of only on the device. It takes the thread, its messages, its trace and the engine's state — the flow node, the history the AI nodes read, a half-finished data collection — so a visitor who starts over is not left answering a form they can no longer see. Pass aconversationIdto delete another one from your own list screen; without it the active conversation goes, and the panel empties with it. One rule now covers the surface:clear*only touches this device,delete*talks to the server, soclearConversation()andstartNewConversation()keep their old, local behaviour. emptyConversation()wipes the same content but keeps the thread: same id, still inlistConversations(), no messages.- Two new items in the "+" menu, "Vaciar conversación" and "Eliminar
conversación" (
FindChatActionIds.emptyConversation/.deleteConversation), which appear only when the connection allows deleting —visitor_can_delete, new onFindChatConfig, always true on signed connections and opt-in per connection on public ones. It defaults tofalse, so against a backend that does not send the flag neither item is drawn. Both are disabled during a turn (the worker would recreate the conversation when the turn ends) and when there is no conversation yet. The three actions leave an equally empty panel but are three different outcomes in a visitor's chat list — start over keeps the old thread and adds one, emptying keeps the thread without its messages, deleting removes it — so all three ship, andhiddenActionsshortens the menu where the distinction does not apply. - Neither method throws: the action menu invokes them without awaiting, so a
failure lands in
state.errorMessageand shows in the panel's error banner, and the call returnsfalse. Success shows nothing at all, on purpose — the API answers204whether or not the conversation existed, deliberately, since a404would let a visitor probe other people's ids, so "conversation deleted" would claim more than the server said. For the same reason retrying is always safe. - Ten new
FindChatLabelsfields, for the two new actions and their confirmations. - The widget speaks English too (
strings, newFindChatStrings). It is the complete bundle — panel, composer, voice and error messages — so one parameter switches the language, and the per-area label objects still apply on top for single strings.FindChatStrings.en.copyWith(...)covers a language the SDK does not ship. Nothing changes by default: withoutstringsthe chat stays in Spanish. Following the app's own language is opt-in (strings: FindChatStrings.of(context)) becauseMaterialAppreportsen_USuntilsupportedLocalesis configured — deducing it would have turned existing Spanish chats English on upgrade. - Error messages are translatable at last. They used to be the one text the
SDK baked in: the controller has no
BuildContext, so it wrote Spanish straight into the state. It now stores a typed reason —FindChatState.errorKind, pluserrorDetailfor the backend's own words — and the widget resolves it against the chosen language when it renders.state.errorMessagestill carries the Spanish text for code that reads it. - Fix: the confirmation card's buttons wrap instead of overflowing. Their labels come from whoever defines the action, and a slightly longer pair — "Cancelar" and "Eliminar", or any translation — used to run past the edge.
0.6.0 #
- Voice input in the composer (
audioonFindChatWidget, newFindChatAudioConfig). Off unless configured: passing it adds a mic button to the left of send that records, transcribes with the platform's speech recognition, and hands the text over. While recording, the text field is replaced by a row with cancel, an animated waveform, the elapsed time and finish; the draft that was in the field survives the round trip. Three ways to land the transcript, viatranscriptMode: into the composer to review (the default — dictation usually needs a fix and sending on its own is surprising), sent straight away, or shown in a preview card to confirm. Auto-send falls back to the composer when a turn is in flight so a dictation is never dropped. Permission, network, "nothing heard" and unsupported-engine failures each get their own message; only the unsupported one hides the mic, because it is the only one retrying cannot fix. - Audio visualization driven by the real microphone amplitude
(
getUserMedia→AudioContext→AnalyserNode→ RMS on web, the engine's level on native): louder speech, taller bars; silence leaves them nearly still. When amplitude cannot be measured it degrades instead of freezing — voice-activity detection first, then a smoothed pseudo-random animation while recording. A visualization failure never blocks recording or transcription. Rendering rebuilds no widgets per frame: one ticker advances the animation and only the bars repaint. - Configurable empty state (
emptyState, newFindChatEmptyStateandFindChatSuggestion). Title, description and clickable shortcuts before the first message, in place of the welcome bubble.labelis what the visitor reads andmessagewhat gets sent, so a short button can carry a long prompt; title and description fall back to the connection's own, so an existing setup needs no duplication. Not passing it keeps the previous behaviour. - Callbacks and events (
callbacks, newFindChatCallbacksandFindChatEvent): typed hooks for suggestions, recording and transcription, plus a singleonEventchannel for analytics — one hook to wire instead of one per feature.message_sentcarries where the message came from (typed, dictated, or a suggestion). - Every visible string is now overridable (
labels, newFindChatLabels, andFindChatAudioLabelsinside the audio config): header title, close tooltip, action menu, the new-conversation action and its confirmation, and the load error. WithcomposerLabelsthis covers the whole widget, which is what translating it required. SettingloadErrorMessagealso replaces the reason the backend returned, so a technical or foreign-language string cannot reach the visitor. Turn error messages are still not overridable. - Internally, the four new options did not multiply the plumbing: the three
view widgets now pass a single
FindChatViewPropsbundle down to the panel, so adding an option touches one place instead of eleven (each view, its overlay twin, and the hand-written comparison that decides whether the overlay entry needs rebuilding). - New dependencies:
speech_to_textandweb. Both are only used by the voice module; a widget withoutaudionever touches them.
0.5.0 #
- Fix: the message list opens on the last message. On first mount (history already loaded), after switching conversations and after clearing, the list jumps to the bottom instead of showing the first message. Auto-follow during streaming now only applies while the visitor is at the bottom: scrolling up to re-read something no longer drags them back down, and sending a message re-engages it.
- Composer redesign, after the Claude mobile / Claude Code composers: a
bordered, rounded box with a single row ("+" action button, text field,
send button), a focus ring, up to 6 auto-growing lines with bottom-aligned
buttons, and a caption line under the box for the keyboard hint or the
in-flight status. Shared by both visual styles; the accent follows the style
(brand color in
classic, monochrome ingptStyle).inputHeightkeeps pinning the text field height. - Enter sends, Shift+Enter inserts a newline on desktop platforms (native and web). Before this, on web, Shift+Enter also sent, so multi-line messages could not be typed. On touch platforms the keyboard's action key still sends.
- The text field stays editable and focused while a turn is in flight: the send button is disabled and the caption shows "Pensando…", but the visitor can type the next message without waiting.
- Action menu moves to a "+" button on the left of the composer,
replacing the long press on the send button, which was hard to discover.
The menu opens upward from the composer in all three modes: the chat panel
now hosts its own
Overlay, so popups are positioned and clipped within the panel instead of spilling out of the floating card. The header no longer has the ↻ button: withshowHeader: falseit was unreachable, while the composer is always visible. - The two built-in entries collapse into one, Nueva conversación
(
FindChatActionIds.newConversation, new exported class): both only cleared the device-side state and left the backend conversation untouched. It confirms before runningstartNewConversation(). Theclear_conversationid is gone, andFindChatActionConfirmation.confirmLabelnow defaults to "Confirmar" instead of "Eliminar". hiddenActionsonFindChatWidget: ids of actions to hide from the menu, SDK or host-added. Empty by default, so everything shows; when nothing is left, the "+" button is not rendered.composerLabelsonFindChatWidget(new exported modelFindChatComposerLabels): override the field placeholder, the keyboard hint and whether it shows (showKeyboardHint: null = desktop only, true/false = always/never), and the status label while a turn is in flight.- Neutral surface palette (no seed tint) in light and dark mode; the brand
color is reserved for accents. The chat's text theme now follows the chat's
brightness rather than the host app's, so markdown in replies stays legible
when the chat and the host use opposite themes.
themeBuilderstill runs last and can override all of it. - Markdown in assistant replies: styled inline code and code blocks, block quotes, headings and links consistent with the chat typography.
- Turn errors (network, 429, …) render as an inline banner above the composer instead of a bare line of text.
- Action menu and confirmation restyled after Claude's popovers: rounded card with a hairline border and soft shadow, 40px items with a muted leading icon and rounded hover, "+" that turns into "×" while open, and a filled primary button in the confirmation step. Menu items and dialog buttons now inherit the host's font family.
- Fix: the composer text sits visually centered against the "+" and send
buttons.
InputDecoratorshifts the text down by (line height − font size) / 2 whenevertextAlignVerticalis set, on every renderer, so the composer no longer uses it: the field is centered structurally inside a box as tall as the buttons, andinputHeightnow bounds the field to the lines that fit instead of usingexpands. Verified by pixel measurement in Chrome. - Fix: in
floatinganddrawermodes, changingtheme,visualStyle, colors or any otherFindChatWidgetprop on a rebuild had no effect until the widget was remounted, because the panel'sOverlayEntrywas never marked for rebuild. - Example app: runs without a backend by default.
DemoChatBackendis an in-memoryhttp.Clientthat answers the webchat endpoints (config, history, messages, SSE stream) with scripted replies and keyword-driven scenarios (largo,markdown,lento,error,falla). A real backend is opt-in via--dart-define=FIND_CHAT_CONNECTION_ID/FIND_CHAT_BASE_URL.
0.4.0 #
- Action menu on the send button: long-pressing the send button opens a
floating menu of chat actions. Works in all three display modes and with
showHeader: false, so the actions stay reachable when the host app hides the header. The header's ↻ button is unchanged. - Ships one action, Eliminar conversación: asks for confirmation, then runs
clearConversation()— messages and the persisted conversation id are dropped on this device only, the backend conversation is untouched. Disabled while a turn is in flight. actionsBuilderonFindChatWidgetcomposes the menu. It receives the SDK defaults (already resolved against the current state) and returns the final list, so host apps can append, reorder, or drop entries; returning an empty list turns the menu off. New exported models:FindChatActionandFindChatActionConfirmation.- The menu and its confirmation render via
OverlayPortalinstead ofshowMenu/showDialog: infloating/drawermodes the panel is inserted above theNavigator's overlay entries, so route-based popups would be painted behind the chat.
0.3.0 #
- Per-turn
options: scope what a turn can consult (RAG documents, dataset rows, …) by passing an opaque map that travels as theoptionsfield ofPOST /messages. Available onFindChatWidget/FindChatController/FindChatClient.sendMessage(), plusFindChatController.setOptions()to re-scope an ongoing chat — useful when the host app navigates between screens (e.g. from one product to another). The SDK never inspects the map's contents: the app's backend builds it, so extending the contract server-side needs no SDK release. Omitted from the request body when null or empty.
0.2.2 #
- Add direct bubble color overrides for user and assistant messages.
- Add direct text color overrides for user and assistant message bubbles.
- Add
inputHeightto customize the bottom text field height. - Fix: vertically center the message input text.
0.2.1 #
- Add
showHeadertoFindChatWidgetand the presentation views, allowing host apps to hide the built-in chat panel header. - Add
themeBuilder, an advanced styling hook that receives the SDK-resolvedThemeDataand returns a modified copy. - Fix: the message input no longer draws an extra internal border when focused.
0.2.0 #
- Signed mode (
auth_mode: "signed"connections): newtokenProvidercallback onFindChatWidget/FindChatController/FindChatClientattaches the visitor JWT asAuthorization: Bearerto every request (including each SSE reconnection, so long streams survive token expiry). On 401 the client fetches a fresh token and retries once; a persistent 401 throws the newFindChatAuthException. - Multiple conversations per visitor (signed mode):
FindChatClient.listConversations()(GET /conversations),FindChatController.listConversations()/selectConversation()/startNewConversation(), and aconversationIdgetter for the active one. - Fix:
GET /historymessage ids are numeric in the backend response; parsing no longer assumes a string.
0.1.0 #
- Initial release:
FindChatClient(HTTP + SSE against the public webchat API),FindChatController,FindChatWidgetwith three display modes (embedded,floating,drawer), light/dark theming and visual style (classic/gptStyle) configurable via explicit override or inherited from connection config, andFindChatSessionStorefor visitor_id/conversation_id persistence.