KvalityUI

ChatMessage

Сообщение пользователя или ответ агента: текст, шаги, план, блоки результатов, копирование и оценка.

Живой код — редактируйте
const assistantMessage = {
  id: "6f1c2a3b-4d5e-4f60-8a7b-9c0d1e2f3a4b",
  role: "assistant",
  parts: [
    {
      type: "tool-plan",
      toolCallId: "plan-1",
      state: "output-available",
      input: {
        steps: [
          { title: "Посчитать заявки по источникам", status: "done" },
          { title: "Построить динамику по дням", status: "done" },
          { title: "Сравнить с прошлой неделей", status: "done" }
        ]
      },
      output: { steps: 3, open: 0 }
    },
    {
      type: "tool-aggregate",
      toolCallId: "call-1",
      state: "output-available",
      input: {},
      output: { ref: "t1.a1", summary: "4 источника" }
    },
    {
      type: "tool-aggregate",
      toolCallId: "call-2",
      state: "output-available",
      input: {},
      output: { ref: "t1.a2", summary: "7 дней" }
    },
    {
      type: "text",
      text: "За неделю пришло **128 заявок**. Две трети дал Яндекс Директ — 84 заявки при конверсии 4,6%. Органика просела на 6%, соцсети выросли почти вдвое, но выборка маленькая."
    },
    {
      type: "tool-show",
      toolCallId: "call-3",
      state: "output-available",
      input: { ref: "t1.a1", view: "bars", layout: "half" },
      output: { shown: true }
    },
    {
      type: "tool-show",
      toolCallId: "call-4",
      state: "output-available",
      input: { ref: "t1.a2", view: "trend", layout: "half" },
      output: { shown: true }
    },
    {
      type: "tool-suggest",
      toolCallId: "call-5",
      state: "output-available",
      input: {
        questions: [
          "Почему просела органика?",
          "Какие кампании Директа лучше всех?",
          "Где теряются заявки?"
        ]
      },
      output: {}
    }
  ]
};

const resultsByRef = {
  "t1.a1": {
    kind: "table",
    title: "Заявки по источникам",
    data: {
      period: { from: "2026-10-13", to: "2026-10-19", days: 7 },
      caveats: [],
      columns: [
        { key: "source", label: "Источник", role: "dimension", unit: "text" },
        { key: "visits", label: "Визиты", role: "metric", unit: "count" },
        { key: "submissions", label: "Заявки", role: "metric", unit: "count" },
        { key: "conversion", label: "Конверсия", role: "metric", unit: "percent" }
      ],
      rows: [
        { source: "Яндекс Директ", visits: 1840, submissions: 84, conversion: 4.6 },
        { source: "Органика", visits: 920, submissions: 31, conversion: 3.4 },
        { source: "Соцсети", visits: 410, submissions: 9, conversion: 2.2 },
        { source: "Прямые", visits: 290, submissions: 4, conversion: 1.4 }
      ],
      totals: { source: "Всего", visits: 3460, submissions: 128, conversion: 3.7 }
    }
  },
  "t1.a2": {
    kind: "table",
    title: "Визиты и заявки по дням",
    data: {
      period: { from: "2026-10-13", to: "2026-10-19", days: 7 },
      caveats: [],
      columns: [
        { key: "day", label: "День", role: "dimension", unit: "text" },
        { key: "visits", label: "Визиты", role: "metric", unit: "count" },
        { key: "submissions", label: "Заявки", role: "metric", unit: "count" }
      ],
      rows: [
        { day: "2026-10-13", visits: 420, submissions: 14 },
        { day: "2026-10-14", visits: 510, submissions: 19 },
        { day: "2026-10-15", visits: 480, submissions: 17 },
        { day: "2026-10-16", visits: 560, submissions: 23 },
        { day: "2026-10-17", visits: 530, submissions: 21 },
        { day: "2026-10-18", visits: 390, submissions: 15 },
        { day: "2026-10-19", visits: 570, submissions: 19 }
      ]
    }
  }
};

render(
  <ChatMessage
    message={assistantMessage}
    streaming={false}
    renderResult={(input) => (
      <ResultCard input={input} result={resultsByRef[input.ref]} />
    )}
    renderFeedback={() => (
      <MessageRating
        pending={false}
        dislikeOpen={false}
        onDislikeOpenChange={() => {}}
        onRate={() => {}}
      />
    )}
  />
);

Во время ответа

Пока агент работает, сообщение показывает текущий шаг и живой список действий.

Живой код — редактируйте
const streamingMessage = {
  id: "message-streaming",
  role: "assistant",
  parts: [
    {
      type: "reasoning",
      text: "Нужно посчитать заявки по источникам и сравнить с прошлой неделей.",
      state: "done"
    },
    {
      type: "tool-aggregate",
      toolCallId: "call-10",
      state: "output-available",
      input: {},
      output: { ref: "t2.a1", summary: "4 источника" }
    },
    {
      type: "tool-compare_segments",
      toolCallId: "call-11",
      state: "input-available",
      input: {}
    }
  ]
};

render(
  <ChatMessage message={streamingMessage} streaming renderResult={() => null} />
);

Сообщение пользователя

Живой код — редактируйте
const userMessage = {
  id: "message-user",
  role: "user",
  parts: [
    {
      type: "text",
      text: "Откуда пришли заявки за прошлую неделю и как менялась динамика?"
    }
  ]
};

render(
  <ChatMessage
    message={userMessage}
    streaming={false}
    renderResult={() => null}
  />
);

Пропсы

Prop

Type

On this page