ruLog in to Senler

Inline Text Edits Through the Widget

SenlerWidget.createInlineTextEdit(config) returns a control object (controller) for “select text or a field → ask AI to change it → show a preview before applying.” The widget connects the request, a separate dialog, and the ready preview, while the site renders the button, loading state, comparison, and confirmation in its own style.

The controller does not change the field automatically. Application stays on the site because editors handle input events, undo/redo, autosave, and validation differently.

There is no separate inline-edit settings screen in the cabinet and no fixed appearance. Your site creates the buttons and comparison UI; Senler provides the controller and events described below.

Choose the Edit Scope First

  • scope: "selection" changes a selected fragment. A non-empty selectedText or getSelection() result is required.
  • scope: "field" proposes a replacement for the complete field and also works with an empty value.

Capture the current selection before calling AI because the browser may clear it after focus moves. The controller verifies that source text has not changed before returning a preview.

Create the Controller

const textarea = document.querySelector("#description");

const pendingStatuses = new Set(["sending", "message_sent", "answered"]);
const failureStatuses = new Set([
  "message_send_failed",
  "message_answer_failed",
  "preview_failed",
]);

const inlineEdit = SenlerWidget.createInlineTextEdit({
  fieldId: "product:123:description",
  fieldLabel: "Product description",
  getValue: () => textarea.value,
  getSelection: () => ({
    text: textarea.value.slice(textarea.selectionStart, textarea.selectionEnd),
  }),
  getContextItems: () => [
    {
      id: "product:123",
      kind: "product",
      role: "business_context",
      display: { label: "Product #123" },
      ref: {
        entity_type: "product",
        entity_id: "123",
        route: "/products/123",
      },
    },
  ],
  onStatusChange: ({ status, error }) => {
    renderInlineLoading(pendingStatuses.has(status));
    if (failureStatuses.has(status)) {
      renderInlineFallback(error?.message);
    }
  },
  onPreview: (preview) => {
    renderTextEditReview({
      before: preview.sourceText,
      after: preview.replacementText,
      changedFrom: preview.selectedText,
      changedTo: preview.selectedTextReplacement,
      onAccept: () => {
        textarea.value = preview.replacementText;
        textarea.dispatchEvent(new Event("input", { bubbles: true }));
      },
    });
  },
});

document.querySelector("#ai-improve").addEventListener("click", () => {
  inlineEdit.ask({ text: "Make this clearer", scope: "selection" });
});

document.querySelector("#ai-chat").addEventListener("click", () => {
  inlineEdit.openChat({ scope: "field" });
});

renderInlineLoading, renderInlineFallback, and renderTextEditReview stand for components in your site. Replace them with your own loading state, error message, and confirmation UI; the loader does not add these functions to window.

Supply Field Data

ParameterRequiredPurpose
fieldIdYesStable, non-empty field ID up to 88 characters. The controller adds internal prefixes, while the resulting context-item ID is limited to 120 characters.
fieldLabelYesNon-empty field name clear to the agent, up to 80 characters.
getValue()YesReturns the complete current text as a string.
onPreview(preview)YesDisplays the result and applies it only after user confirmation.
getSelection()For selection when ask does not pass textReturns a string or { text }.
contextItemsNoFixed additional-context array for this controller.
getContextItems()NoReturns a current array before every request. If both the function and contextItems are supplied, the function wins.
onStatusChange(detail)NoReceives status and applicable requestId, dialogId, preview, or error values.
onDialogIdChange(dialogId)NoReports the created background dialog ID.
onError(error)NoReceives a send, answer, or preview-validation error.

A widget message can contain no more than 12 context items. The controller adds three items for ask() and two when openChat() creates a dialog. For ask(), the controller's own items and persistent page items must therefore leave three free slots; after deduplication their combined count must not exceed 9.

You may also customize labels and the technical instruction through selectedTextLabel, fieldScopeLabel, taskLabel, taskSubtitle, and taskInstruction. selectedTextLabel, fieldScopeLabel, and taskLabel may contain up to 80 characters; taskSubtitle may contain up to 140. The defaults are normally sufficient. If you change taskInstruction, preserve the requirement to call senler.previewTextEdit with the preview fields listed below.

Every request sends the complete getValue() result, the selected fragment, and configured context items to the agent. Do not attach inline editing to passwords, payment data, or other fields whose content must not be sent to the agent.

If the site stores selection itself, pass it at call time: inlineEdit.ask({ text: "Fix typos", selectedText, scope: "selection" }).

Start an Edit or Open Chat

  • ask(text | { text, actionLabel?, selectedText?, scope? }) creates a new dialog and sends the request; onPreview runs when the edit is ready;
  • openChat({ selectedText?, scope? }) opens the regular widget with the same context and existing dialog;
  • getDialogId() returns the last known dialog ID or null;
  • destroy() removes controller handlers.

In the request object, text is the visitor instruction, actionLabel is its short label in technical context, selectedText is the captured selection, and scope chooses the selected fragment or the entire field. In the string form ask("Make this clearer"), the text is used as both the instruction and the label, with selection as the scope.

After the full loader runtime is ready, ask() returns a string requestId. When it is called while the bootstrap loader is still queuing commands, it returns undefined; the later-created ID is still supplied through onStatusChange. Do not base application logic only on the synchronous return value.

Each ask() starts a new dialog and automatically registers the internal senler.previewTextEdit action that creates the preview. If no dialog exists, openChat() creates an empty dialog with the context and focuses the input without sending anything.

Show and Apply the Preview

Preview contains:

  • fieldId — the field this result belongs to;
  • sourceText — the complete original field text;
  • replacementText — the complete proposed text;
  • selectedText — the original selected fragment;
  • selectedTextReplacement — the proposed fragment replacement;
  • summary — an optional short description of the change.

The controller does not write replacementText to the field. In onPreview, show the comparison, request confirmation, and only then update editor state, dispatch its input event, and save according to the site's rules.

For selection, the loader assembles a complete replacementText only when the source fragment can still be matched uniquely; matching tolerates whitespace differences. For field, the preview is rejected when the value changed after the request was sent. This prevents a late response from overwriting a newer edit.

Handle States

  • sending — the controller started sending;
  • message_sent — the message was accepted and the background dialog is known;
  • answered — the answer was generated, but preview may still be pending;
  • preview_readyonPreview received ready before/after data;
  • message_send_failed — the runtime message could not be sent;
  • message_answer_failed — the answer did not finish successfully;
  • preview_failed — the answer arrived, but an edit could not be built or parsed.

The site should show loading until preview_ready or a failure. onStatusChange receives all states, onError receives the error, and onDialogIdChange reports the created dialog ID. After an error, the user can retry or open the same dialog with openChat().

Lifecycle

Only one controller is active for a fieldId: creating another destroys the previous one. A controller tracks one active ask(), so wait for a preview or failure before starting another request for the same field. Call destroy() when the field is permanently removed from the page. After SenlerWidget.destroy(), create both the widget and controller again. Inline edits are unavailable in button_only mode.

Where to Find the Complete Contract

The Public API reference lists all SenlerWidget methods, runtime parameters, and result events. Use the event directly for a custom automatic-message flow; the field-edit controller already implements that connection and should not be duplicated manually.