@gravity-ui/aikit 1.15.1 → 1.15.2-beta.12d894e43bcc0b7b911e62db16634e4e418ff48a.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/build/cjs/components/organisms/ThinkingMessage/index.d.ts +13 -1
  2. package/build/cjs/components/organisms/ThinkingMessage/index.js +7 -2
  3. package/build/cjs/components/organisms/ThinkingMessage/index.js.map +1 -1
  4. package/build/cjs/components/pages/AIStudioChat/AIStudioChat.d.ts +11 -0
  5. package/build/cjs/components/pages/AIStudioChat/AIStudioChat.js +216 -0
  6. package/build/cjs/components/pages/AIStudioChat/AIStudioChat.js.map +1 -0
  7. package/build/cjs/components/pages/AIStudioChat/index.d.ts +2 -0
  8. package/build/cjs/components/pages/AIStudioChat/index.js +6 -0
  9. package/build/cjs/components/pages/AIStudioChat/index.js.map +1 -0
  10. package/build/cjs/components/pages/AIStudioChat/types.d.ts +24 -0
  11. package/build/cjs/components/pages/AIStudioChat/types.js +3 -0
  12. package/build/cjs/components/pages/AIStudioChat/types.js.map +1 -0
  13. package/build/cjs/components/pages/index.d.ts +1 -0
  14. package/build/cjs/components/pages/index.js +1 -0
  15. package/build/cjs/components/pages/index.js.map +1 -1
  16. package/build/cjs/package.json +1 -1
  17. package/build/esm/components/organisms/ThinkingMessage/index.d.ts +13 -1
  18. package/build/esm/components/organisms/ThinkingMessage/index.js +7 -2
  19. package/build/esm/components/organisms/ThinkingMessage/index.js.map +1 -1
  20. package/build/esm/components/pages/AIStudioChat/AIStudioChat.d.ts +11 -0
  21. package/build/esm/components/pages/AIStudioChat/AIStudioChat.js +213 -0
  22. package/build/esm/components/pages/AIStudioChat/AIStudioChat.js.map +1 -0
  23. package/build/esm/components/pages/AIStudioChat/index.d.ts +2 -0
  24. package/build/esm/components/pages/AIStudioChat/index.js +2 -0
  25. package/build/esm/components/pages/AIStudioChat/index.js.map +1 -0
  26. package/build/esm/components/pages/AIStudioChat/types.d.ts +24 -0
  27. package/build/esm/components/pages/AIStudioChat/types.js +2 -0
  28. package/build/esm/components/pages/AIStudioChat/types.js.map +1 -0
  29. package/build/esm/components/pages/index.d.ts +1 -0
  30. package/build/esm/components/pages/index.js +1 -0
  31. package/build/esm/components/pages/index.js.map +1 -1
  32. package/build/esm/package.json +1 -1
  33. package/package.json +2 -1
@@ -1,3 +1,4 @@
1
+ import { OptionsType } from '@diplodoc/transform/lib/typings';
1
2
  import { DOMProps, QAProps } from '@gravity-ui/uikit';
2
3
  import type { ThinkingMessageContentData } from "../../../types/messages.js";
3
4
  import "./ThinkingMessage.css";
@@ -5,7 +6,18 @@ import "./ThinkingMessage.css";
5
6
  * Props for the ThinkingMessage component.
6
7
  * Combines DOM props, QA props, and thinking message data.
7
8
  */
8
- export type ThinkingMessageProps = DOMProps & QAProps & ThinkingMessageContentData;
9
+ export type ThinkingMessageProps = DOMProps & QAProps & ThinkingMessageContentData & {
10
+ /**
11
+ * How thinking content strings are rendered.
12
+ * @default 'plain'
13
+ */
14
+ format?: 'plain' | 'markdown';
15
+ /**
16
+ * Options for @diplodoc/transform when `format` is `'markdown'`.
17
+ * Shallow-merged with the component defaults (`disableCommonAnchors: true`).
18
+ */
19
+ transformOptions?: OptionsType;
20
+ };
9
21
  /**
10
22
  * ThinkingMessage component displays AI model's internal reasoning process.
11
23
  * Shows a collapsible block with thinking content and a copy button.
@@ -9,9 +9,13 @@ const uikit_1 = require("@gravity-ui/uikit");
9
9
  const cn_1 = require("../../../utils/cn.js");
10
10
  const ActionButton_1 = require("../../atoms/ActionButton/index.js");
11
11
  const Loader_1 = require("../../atoms/Loader/index.js");
12
+ const MarkdownRenderer_1 = require("../../atoms/MarkdownRenderer/index.js");
12
13
  const useThinkingMessage_1 = require("./useThinkingMessage.js");
13
14
  require("./ThinkingMessage.css");
14
15
  const b = (0, cn_1.block)('thinking-message');
16
+ const defaultTransformOptions = {
17
+ disableCommonAnchors: true,
18
+ };
15
19
  /**
16
20
  * ThinkingMessage component displays AI model's internal reasoning process.
17
21
  * Shows a collapsible block with thinking content and a copy button.
@@ -21,9 +25,10 @@ const b = (0, cn_1.block)('thinking-message');
21
25
  * @returns Rendered thinking message component
22
26
  */
23
27
  const ThinkingMessage = (props) => {
24
- const { className, qa, style } = props, data = tslib_1.__rest(props, ["className", "qa", "style"]);
28
+ const { className, qa, style, format = 'plain', transformOptions } = props, data = tslib_1.__rest(props, ["className", "qa", "style", "format", "transformOptions"]);
29
+ const markdownTransformOptions = Object.assign(Object.assign({}, defaultTransformOptions), transformOptions);
25
30
  const { isExpanded, toggleExpanded, buttonTitle, content, showLoader, handleCopy, showCopyButton, } = (0, useThinkingMessage_1.useThinkingMessage)(data);
26
- return ((0, jsx_runtime_1.jsxs)("div", { className: b(null, className), "data-qa": qa, style: style, children: [(0, jsx_runtime_1.jsxs)("div", { className: b('buttons'), children: [(0, jsx_runtime_1.jsxs)(uikit_1.Button, { size: "s", onClick: toggleExpanded, children: [buttonTitle, (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: isExpanded ? icons_1.ChevronUp : icons_1.ChevronDown })] }), showLoader ? ((0, jsx_runtime_1.jsx)(Loader_1.Loader, { view: "loading", size: "xs" })) : (showCopyButton && ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { size: "s", onClick: handleCopy, children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Copy, size: 16 }) })))] }), isExpanded && ((0, jsx_runtime_1.jsx)("div", { className: b('container'), children: content.map((item, index) => ((0, jsx_runtime_1.jsx)(uikit_1.Text, { className: b('content'), children: item }, index))) }))] }));
31
+ return ((0, jsx_runtime_1.jsxs)("div", { className: b(null, className), "data-qa": qa, style: style, children: [(0, jsx_runtime_1.jsxs)("div", { className: b('buttons'), children: [(0, jsx_runtime_1.jsxs)(uikit_1.Button, { size: "s", onClick: toggleExpanded, children: [buttonTitle, (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: isExpanded ? icons_1.ChevronUp : icons_1.ChevronDown })] }), showLoader ? ((0, jsx_runtime_1.jsx)(Loader_1.Loader, { view: "loading", size: "xs" })) : (showCopyButton && ((0, jsx_runtime_1.jsx)(ActionButton_1.ActionButton, { size: "s", onClick: handleCopy, children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Copy, size: 16 }) })))] }), isExpanded && ((0, jsx_runtime_1.jsx)("div", { className: b('container'), children: content.map((item, index) => format === 'markdown' ? ((0, jsx_runtime_1.jsx)(MarkdownRenderer_1.MarkdownRenderer, { className: b('content'), content: item, transformOptions: markdownTransformOptions }, index)) : ((0, jsx_runtime_1.jsx)(uikit_1.Text, { className: b('content'), children: item }, index))) }))] }));
27
32
  };
28
33
  exports.ThinkingMessage = ThinkingMessage;
29
34
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/organisms/ThinkingMessage/index.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;AAEb,6CAA+D;AAC/D,6CAAwE;AAGxE,6CAAwC;AACxC,oEAAsD;AACtD,wDAA0C;AAE1C,gEAAwD;AAExD,iCAAgC;AAEhC,MAAM,CAAC,GAAG,IAAA,UAAK,EAAC,kBAAkB,CAAC,CAAC;AAQpC;;;;;;;GAOG;AACI,MAAM,eAAe,GAAG,CAAC,KAA2B,EAAE,EAAE;IAC3D,MAAM,EAAC,SAAS,EAAE,EAAE,EAAE,KAAK,KAAa,KAAK,EAAb,IAAI,kBAAI,KAAK,EAAvC,4BAA+B,CAAQ,CAAC;IAE9C,MAAM,EACF,UAAU,EACV,cAAc,EACd,WAAW,EACX,OAAO,EACP,UAAU,EACV,UAAU,EACV,cAAc,GACjB,GAAG,IAAA,uCAAkB,EAAC,IAAI,CAAC,CAAC;IAE7B,OAAO,CACH,iCAAK,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,aAAW,EAAE,EAAE,KAAK,EAAE,KAAK,aACzD,iCAAK,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,aACxB,wBAAC,cAAM,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,cAAc,aACnC,WAAW,EACZ,uBAAC,YAAI,IAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,iBAAS,CAAC,CAAC,CAAC,mBAAW,GAAI,IAC/C,EACR,UAAU,CAAC,CAAC,CAAC,CACV,uBAAC,eAAM,IAAC,IAAI,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,GAAG,CACtC,CAAC,CAAC,CAAC,CACA,cAAc,IAAI,CACd,uBAAC,2BAAY,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,UAAU,YACtC,uBAAC,YAAI,IAAC,IAAI,EAAE,YAAI,EAAE,IAAI,EAAE,EAAE,GAAI,GACnB,CAClB,CACJ,IACC,EACL,UAAU,IAAI,CACX,gCAAK,SAAS,EAAE,CAAC,CAAC,WAAW,CAAC,YACzB,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAC1B,uBAAC,YAAI,IAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,YACxB,IAAI,IAD2B,KAAK,CAElC,CACV,CAAC,GACA,CACT,IACC,CACT,CAAC;AACN,CAAC,CAAC;AAzCW,QAAA,eAAe,mBAyC1B","sourcesContent":["'use client';\n\nimport {ChevronDown, ChevronUp, Copy} from '@gravity-ui/icons';\nimport {Button, DOMProps, Icon, QAProps, Text} from '@gravity-ui/uikit';\n\nimport type {ThinkingMessageContentData} from '../../../types/messages';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\nimport {Loader} from '../../atoms/Loader';\n\nimport {useThinkingMessage} from './useThinkingMessage';\n\nimport './ThinkingMessage.scss';\n\nconst b = block('thinking-message');\n\n/**\n * Props for the ThinkingMessage component.\n * Combines DOM props, QA props, and thinking message data.\n */\nexport type ThinkingMessageProps = DOMProps & QAProps & ThinkingMessageContentData;\n\n/**\n * ThinkingMessage component displays AI model's internal reasoning process.\n * Shows a collapsible block with thinking content and a copy button.\n * Displays a loader when the thinking process is still in progress.\n *\n * @param props - Component props\n * @returns Rendered thinking message component\n */\nexport const ThinkingMessage = (props: ThinkingMessageProps) => {\n const {className, qa, style, ...data} = props;\n\n const {\n isExpanded,\n toggleExpanded,\n buttonTitle,\n content,\n showLoader,\n handleCopy,\n showCopyButton,\n } = useThinkingMessage(data);\n\n return (\n <div className={b(null, className)} data-qa={qa} style={style}>\n <div className={b('buttons')}>\n <Button size=\"s\" onClick={toggleExpanded}>\n {buttonTitle}\n <Icon data={isExpanded ? ChevronUp : ChevronDown} />\n </Button>\n {showLoader ? (\n <Loader view=\"loading\" size=\"xs\" />\n ) : (\n showCopyButton && (\n <ActionButton size=\"s\" onClick={handleCopy}>\n <Icon data={Copy} size={16} />\n </ActionButton>\n )\n )}\n </div>\n {isExpanded && (\n <div className={b('container')}>\n {content.map((item, index) => (\n <Text className={b('content')} key={index}>\n {item}\n </Text>\n ))}\n </div>\n )}\n </div>\n );\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/organisms/ThinkingMessage/index.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;;;AAGb,6CAA+D;AAC/D,6CAAwE;AAGxE,6CAAwC;AACxC,oEAAsD;AACtD,wDAA0C;AAC1C,4EAA8D;AAE9D,gEAAwD;AAExD,iCAAgC;AAEhC,MAAM,CAAC,GAAG,IAAA,UAAK,EAAC,kBAAkB,CAAC,CAAC;AAqBpC,MAAM,uBAAuB,GAAgB;IACzC,oBAAoB,EAAE,IAAI;CAC7B,CAAC;AAEF;;;;;;;GAOG;AACI,MAAM,eAAe,GAAG,CAAC,KAA2B,EAAE,EAAE;IAC3D,MAAM,EAAC,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,gBAAgB,KAAa,KAAK,EAAb,IAAI,kBAAI,KAAK,EAA3E,0DAAmE,CAAQ,CAAC;IAElF,MAAM,wBAAwB,mCACvB,uBAAuB,GACvB,gBAAgB,CACtB,CAAC;IAEF,MAAM,EACF,UAAU,EACV,cAAc,EACd,WAAW,EACX,OAAO,EACP,UAAU,EACV,UAAU,EACV,cAAc,GACjB,GAAG,IAAA,uCAAkB,EAAC,IAAI,CAAC,CAAC;IAE7B,OAAO,CACH,iCAAK,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,aAAW,EAAE,EAAE,KAAK,EAAE,KAAK,aACzD,iCAAK,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,aACxB,wBAAC,cAAM,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,cAAc,aACnC,WAAW,EACZ,uBAAC,YAAI,IAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,iBAAS,CAAC,CAAC,CAAC,mBAAW,GAAI,IAC/C,EACR,UAAU,CAAC,CAAC,CAAC,CACV,uBAAC,eAAM,IAAC,IAAI,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,GAAG,CACtC,CAAC,CAAC,CAAC,CACA,cAAc,IAAI,CACd,uBAAC,2BAAY,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,UAAU,YACtC,uBAAC,YAAI,IAAC,IAAI,EAAE,YAAI,EAAE,IAAI,EAAE,EAAE,GAAI,GACnB,CAClB,CACJ,IACC,EACL,UAAU,IAAI,CACX,gCAAK,SAAS,EAAE,CAAC,CAAC,WAAW,CAAC,YACzB,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACzB,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,CACpB,uBAAC,mCAAgB,IACb,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,EAEvB,OAAO,EAAE,IAAI,EACb,gBAAgB,EAAE,wBAAwB,IAFrC,KAAK,CAGZ,CACL,CAAC,CAAC,CAAC,CACA,uBAAC,YAAI,IAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,YACxB,IAAI,IAD2B,KAAK,CAElC,CACV,CACJ,GACC,CACT,IACC,CACT,CAAC;AACN,CAAC,CAAC;AAvDW,QAAA,eAAe,mBAuD1B","sourcesContent":["'use client';\n\nimport {OptionsType} from '@diplodoc/transform/lib/typings';\nimport {ChevronDown, ChevronUp, Copy} from '@gravity-ui/icons';\nimport {Button, DOMProps, Icon, QAProps, Text} from '@gravity-ui/uikit';\n\nimport type {ThinkingMessageContentData} from '../../../types/messages';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\nimport {Loader} from '../../atoms/Loader';\nimport {MarkdownRenderer} from '../../atoms/MarkdownRenderer';\n\nimport {useThinkingMessage} from './useThinkingMessage';\n\nimport './ThinkingMessage.scss';\n\nconst b = block('thinking-message');\n\n/**\n * Props for the ThinkingMessage component.\n * Combines DOM props, QA props, and thinking message data.\n */\nexport type ThinkingMessageProps = DOMProps &\n QAProps &\n ThinkingMessageContentData & {\n /**\n * How thinking content strings are rendered.\n * @default 'plain'\n */\n format?: 'plain' | 'markdown';\n /**\n * Options for @diplodoc/transform when `format` is `'markdown'`.\n * Shallow-merged with the component defaults (`disableCommonAnchors: true`).\n */\n transformOptions?: OptionsType;\n };\n\nconst defaultTransformOptions: OptionsType = {\n disableCommonAnchors: true,\n};\n\n/**\n * ThinkingMessage component displays AI model's internal reasoning process.\n * Shows a collapsible block with thinking content and a copy button.\n * Displays a loader when the thinking process is still in progress.\n *\n * @param props - Component props\n * @returns Rendered thinking message component\n */\nexport const ThinkingMessage = (props: ThinkingMessageProps) => {\n const {className, qa, style, format = 'plain', transformOptions, ...data} = props;\n\n const markdownTransformOptions: OptionsType = {\n ...defaultTransformOptions,\n ...transformOptions,\n };\n\n const {\n isExpanded,\n toggleExpanded,\n buttonTitle,\n content,\n showLoader,\n handleCopy,\n showCopyButton,\n } = useThinkingMessage(data);\n\n return (\n <div className={b(null, className)} data-qa={qa} style={style}>\n <div className={b('buttons')}>\n <Button size=\"s\" onClick={toggleExpanded}>\n {buttonTitle}\n <Icon data={isExpanded ? ChevronUp : ChevronDown} />\n </Button>\n {showLoader ? (\n <Loader view=\"loading\" size=\"xs\" />\n ) : (\n showCopyButton && (\n <ActionButton size=\"s\" onClick={handleCopy}>\n <Icon data={Copy} size={16} />\n </ActionButton>\n )\n )}\n </div>\n {isExpanded && (\n <div className={b('container')}>\n {content.map((item, index) =>\n format === 'markdown' ? (\n <MarkdownRenderer\n className={b('content')}\n key={index}\n content={item}\n transformOptions={markdownTransformOptions}\n />\n ) : (\n <Text className={b('content')} key={index}>\n {item}\n </Text>\n ),\n )}\n </div>\n )}\n </div>\n );\n};\n"]}
@@ -0,0 +1,11 @@
1
+ import type { AIStudioChatProps } from "./types.js";
2
+ /**
3
+ * AIStudioChat - a ready-to-use chat component with built-in OpenAI streaming support.
4
+ *
5
+ * Wraps ChatContainer and handles all state internally: streaming, message history,
6
+ * multi-chat management, and cancellation. Requires only an `apiUrl` to start working.
7
+ *
8
+ * @param props - component props
9
+ * @returns React component
10
+ */
11
+ export declare function AIStudioChat(props: AIStudioChatProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AIStudioChat = AIStudioChat;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const openai_1 = require("../../../adapters/openai/index.js");
8
+ const ChatContainer_1 = require("../ChatContainer/index.js");
9
+ /**
10
+ * Extracts plain text content from any TChatMessage variant.
11
+ */
12
+ function getMessageTextContent(message) {
13
+ var _a;
14
+ if (message.role === 'user') {
15
+ return typeof message.content === 'string' ? message.content : '';
16
+ }
17
+ const { content } = message;
18
+ if (typeof content === 'string') {
19
+ return content;
20
+ }
21
+ if (Array.isArray(content)) {
22
+ return content
23
+ .map((item) => {
24
+ var _a;
25
+ if (typeof item === 'string')
26
+ return item;
27
+ if (item.type === 'text' && ((_a = item.data) === null || _a === void 0 ? void 0 : _a.text))
28
+ return item.data.text;
29
+ return '';
30
+ })
31
+ .join('\n');
32
+ }
33
+ if (content && typeof content === 'object' && 'type' in content) {
34
+ if (content.type === 'text' && ((_a = content.data) === null || _a === void 0 ? void 0 : _a.text)) {
35
+ return content.data.text;
36
+ }
37
+ }
38
+ return '';
39
+ }
40
+ /**
41
+ * Derives a human-readable chat name from the first user message.
42
+ */
43
+ function deriveChatName(content) {
44
+ const trimmed = content.trim();
45
+ return trimmed.length > 40 ? `${trimmed.slice(0, 40)}...` : trimmed || 'New chat';
46
+ }
47
+ /**
48
+ * AIStudioChat - a ready-to-use chat component with built-in OpenAI streaming support.
49
+ *
50
+ * Wraps ChatContainer and handles all state internally: streaming, message history,
51
+ * multi-chat management, and cancellation. Requires only an `apiUrl` to start working.
52
+ *
53
+ * @param props - component props
54
+ * @returns React component
55
+ */
56
+ function AIStudioChat(props) {
57
+ var _a, _b;
58
+ const { apiUrl, initialMessages = [], showHistory = false, showNewChat = showHistory, systemPrompt, requestInit } = props, chatContainerProps = tslib_1.__rest(props, ["apiUrl", "initialMessages", "showHistory", "showNewChat", "systemPrompt", "requestInit"]);
59
+ // Current chat messages
60
+ const [messages, setMessages] = (0, react_1.useState)(initialMessages);
61
+ // Multi-chat state (used when showHistory=true)
62
+ const [chats, setChats] = (0, react_1.useState)([]);
63
+ const [activeChat, setActiveChat] = (0, react_1.useState)(null);
64
+ // Per-chat message store (ref to avoid unnecessary re-renders on switch)
65
+ const chatMessagesRef = (0, react_1.useRef)({});
66
+ // Always-current reference to activeChat for use inside async callbacks
67
+ const activeChatRef = (0, react_1.useRef)(null);
68
+ activeChatRef.current = activeChat;
69
+ // Streaming state
70
+ const [controller, setController] = (0, react_1.useState)(null);
71
+ const [isFetching, setIsFetching] = (0, react_1.useState)(false);
72
+ const [streamResponse, setStreamResponse] = (0, react_1.useState)(null);
73
+ const [streamOptions, setStreamOptions] = (0, react_1.useState)(null);
74
+ const handleStreamEnd = (0, react_1.useCallback)((finalMessages) => {
75
+ const committed = finalMessages.filter((msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '');
76
+ setMessages(committed);
77
+ setStreamResponse(null);
78
+ setStreamOptions(null);
79
+ // Persist messages and update the last message preview in history
80
+ const chat = activeChatRef.current;
81
+ if (chat) {
82
+ chatMessagesRef.current[chat.id] = committed;
83
+ const lastMsg = committed[committed.length - 1];
84
+ if (lastMsg) {
85
+ setChats((prev) => prev.map((c) => c.id === chat.id ? Object.assign(Object.assign({}, c), { lastMessage: getMessageTextContent(lastMsg) }) : c));
86
+ }
87
+ }
88
+ }, []);
89
+ const streamResult = (0, openai_1.useOpenAIStreamAdapter)(streamResponse, {
90
+ initialMessages: (_a = streamOptions === null || streamOptions === void 0 ? void 0 : streamOptions.initialMessages) !== null && _a !== void 0 ? _a : [],
91
+ assistantMessageId: (_b = streamOptions === null || streamOptions === void 0 ? void 0 : streamOptions.assistantMessageId) !== null && _b !== void 0 ? _b : 'assistant-idle',
92
+ onStreamEnd: handleStreamEnd,
93
+ });
94
+ const hasResponse = Boolean(streamResponse);
95
+ const displayMessages = hasResponse && streamResult.messages.length > 0 ? streamResult.messages : messages;
96
+ const status = (0, react_1.useMemo)(() => {
97
+ if (!hasResponse) {
98
+ return isFetching ? 'submitted' : 'ready';
99
+ }
100
+ if (streamResult.status === 'streaming')
101
+ return 'streaming';
102
+ if (streamResult.status === 'error')
103
+ return 'error';
104
+ return 'ready';
105
+ }, [hasResponse, isFetching, streamResult.status]);
106
+ const handleSendMessage = (0, react_1.useCallback)(async (data) => {
107
+ // Auto-create a chat when history is enabled and no chat is active yet
108
+ if (showHistory && !activeChatRef.current) {
109
+ const newChat = {
110
+ id: Date.now().toString(),
111
+ name: deriveChatName(data.content),
112
+ createTime: new Date().toISOString(),
113
+ };
114
+ chatMessagesRef.current[newChat.id] = [];
115
+ activeChatRef.current = newChat;
116
+ setActiveChat(newChat);
117
+ setChats((prev) => [newChat, ...prev]);
118
+ }
119
+ const userMessage = {
120
+ id: Date.now().toString(),
121
+ role: 'user',
122
+ content: data.content,
123
+ };
124
+ const messagesWithUser = [...messages, userMessage];
125
+ const assistantMessageId = (Date.now() + 1).toString();
126
+ setMessages(messagesWithUser);
127
+ setIsFetching(true);
128
+ const abortController = new AbortController();
129
+ setController(abortController);
130
+ try {
131
+ const apiMessages = [
132
+ ...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []),
133
+ ...messages
134
+ .filter((msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '')
135
+ .map((msg) => ({
136
+ role: msg.role,
137
+ content: getMessageTextContent(msg),
138
+ })),
139
+ { role: 'user', content: data.content },
140
+ ];
141
+ const response = await fetch(apiUrl, Object.assign(Object.assign({}, requestInit), { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, requestInit === null || requestInit === void 0 ? void 0 : requestInit.headers), body: JSON.stringify({ messages: apiMessages }), signal: abortController.signal }));
142
+ if (!response.ok) {
143
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
144
+ }
145
+ setStreamResponse(response);
146
+ setStreamOptions({ initialMessages: messagesWithUser, assistantMessageId });
147
+ }
148
+ catch (error) {
149
+ if (error.name !== 'AbortError') {
150
+ const errorMessage = {
151
+ id: (Date.now() + 2).toString(),
152
+ role: 'assistant',
153
+ content: error.message,
154
+ };
155
+ setMessages((prev) => {
156
+ const filtered = prev.filter((msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '');
157
+ return [...filtered, errorMessage];
158
+ });
159
+ }
160
+ setStreamResponse(null);
161
+ setStreamOptions(null);
162
+ }
163
+ finally {
164
+ setIsFetching(false);
165
+ setController(null);
166
+ }
167
+ }, [messages, apiUrl, systemPrompt, requestInit, showHistory]);
168
+ const handleCancel = (0, react_1.useCallback)(async () => {
169
+ controller === null || controller === void 0 ? void 0 : controller.abort();
170
+ setStreamResponse(null);
171
+ setStreamOptions(null);
172
+ }, [controller]);
173
+ const handleRetry = (0, react_1.useCallback)(async () => {
174
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
175
+ if (!lastUserMsg)
176
+ return;
177
+ const lastUserIndex = messages.lastIndexOf(lastUserMsg);
178
+ setMessages(messages.slice(0, lastUserIndex));
179
+ await handleSendMessage({ content: lastUserMsg.content });
180
+ }, [messages, handleSendMessage]);
181
+ const handleCreateChat = (0, react_1.useCallback)(() => {
182
+ const current = activeChatRef.current;
183
+ if (current) {
184
+ chatMessagesRef.current[current.id] = messages;
185
+ }
186
+ const newChat = {
187
+ id: Date.now().toString(),
188
+ name: 'New chat',
189
+ createTime: new Date().toISOString(),
190
+ };
191
+ chatMessagesRef.current[newChat.id] = [];
192
+ setChats((prev) => [newChat, ...prev]);
193
+ setActiveChat(newChat);
194
+ setMessages([]);
195
+ }, [messages]);
196
+ const handleSelectChat = (0, react_1.useCallback)((chat) => {
197
+ var _a;
198
+ const current = activeChatRef.current;
199
+ if (current) {
200
+ chatMessagesRef.current[current.id] = messages;
201
+ }
202
+ setActiveChat(chat);
203
+ setMessages((_a = chatMessagesRef.current[chat.id]) !== null && _a !== void 0 ? _a : []);
204
+ }, [messages]);
205
+ const handleDeleteChat = (0, react_1.useCallback)((chat) => {
206
+ var _a;
207
+ delete chatMessagesRef.current[chat.id];
208
+ setChats((prev) => prev.filter((c) => c.id !== chat.id));
209
+ if (((_a = activeChatRef.current) === null || _a === void 0 ? void 0 : _a.id) === chat.id) {
210
+ setActiveChat(null);
211
+ setMessages([]);
212
+ }
213
+ }, []);
214
+ return ((0, jsx_runtime_1.jsx)(ChatContainer_1.ChatContainer, Object.assign({}, chatContainerProps, { messages: displayMessages, status: status, error: streamResult.error, onSendMessage: handleSendMessage, onCancel: handleCancel, onRetry: handleRetry, chats: showHistory ? chats : undefined, activeChat: showHistory ? activeChat : undefined, onSelectChat: showHistory ? handleSelectChat : undefined, onCreateChat: showHistory ? handleCreateChat : undefined, onDeleteChat: showHistory ? handleDeleteChat : undefined, showHistory: showHistory, showNewChat: showNewChat })));
215
+ }
216
+ //# sourceMappingURL=AIStudioChat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AIStudioChat.js","sourceRoot":"../../../../../src","sources":["components/pages/AIStudioChat/AIStudioChat.tsx"],"names":[],"mappings":"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","sourcesContent":["import {useCallback, useMemo, useRef, useState} from 'react';\n\nimport {useOpenAIStreamAdapter} from '../../../adapters/openai';\nimport type {\n ChatStatus,\n ChatType,\n TAssistantMessage,\n TChatMessage,\n TSubmitData,\n TUserMessage,\n} from '../../../types';\nimport {ChatContainer} from '../ChatContainer';\n\nimport type {AIStudioChatProps} from './types';\n\ntype ApiMessage = {\n role: 'user' | 'assistant' | 'system';\n content: string;\n};\n\ntype StreamOptions = {\n initialMessages: TChatMessage[];\n assistantMessageId: string;\n};\n\n/**\n * Extracts plain text content from any TChatMessage variant.\n */\nfunction getMessageTextContent(message: TChatMessage): string {\n if (message.role === 'user') {\n return typeof message.content === 'string' ? message.content : '';\n }\n\n const {content} = message as TAssistantMessage;\n\n if (typeof content === 'string') {\n return content;\n }\n\n if (Array.isArray(content)) {\n return content\n .map((item) => {\n if (typeof item === 'string') return item;\n if (item.type === 'text' && item.data?.text) return item.data.text;\n return '';\n })\n .join('\\n');\n }\n\n if (content && typeof content === 'object' && 'type' in content) {\n if (content.type === 'text' && content.data?.text) {\n return content.data.text;\n }\n }\n\n return '';\n}\n\n/**\n * Derives a human-readable chat name from the first user message.\n */\nfunction deriveChatName(content: string): string {\n const trimmed = content.trim();\n return trimmed.length > 40 ? `${trimmed.slice(0, 40)}...` : trimmed || 'New chat';\n}\n\n/**\n * AIStudioChat - a ready-to-use chat component with built-in OpenAI streaming support.\n *\n * Wraps ChatContainer and handles all state internally: streaming, message history,\n * multi-chat management, and cancellation. Requires only an `apiUrl` to start working.\n *\n * @param props - component props\n * @returns React component\n */\nexport function AIStudioChat(props: AIStudioChatProps) {\n const {\n apiUrl,\n initialMessages = [],\n showHistory = false,\n showNewChat = showHistory,\n systemPrompt,\n requestInit,\n ...chatContainerProps\n } = props;\n\n // Current chat messages\n const [messages, setMessages] = useState<TChatMessage[]>(initialMessages);\n\n // Multi-chat state (used when showHistory=true)\n const [chats, setChats] = useState<ChatType[]>([]);\n const [activeChat, setActiveChat] = useState<ChatType | null>(null);\n\n // Per-chat message store (ref to avoid unnecessary re-renders on switch)\n const chatMessagesRef = useRef<Record<string, TChatMessage[]>>({});\n\n // Always-current reference to activeChat for use inside async callbacks\n const activeChatRef = useRef<ChatType | null>(null);\n activeChatRef.current = activeChat;\n\n // Streaming state\n const [controller, setController] = useState<AbortController | null>(null);\n const [isFetching, setIsFetching] = useState(false);\n const [streamResponse, setStreamResponse] = useState<Response | null>(null);\n const [streamOptions, setStreamOptions] = useState<StreamOptions | null>(null);\n\n const handleStreamEnd = useCallback((finalMessages: TChatMessage[]) => {\n const committed = finalMessages.filter(\n (msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '',\n );\n\n setMessages(committed);\n setStreamResponse(null);\n setStreamOptions(null);\n\n // Persist messages and update the last message preview in history\n const chat = activeChatRef.current;\n if (chat) {\n chatMessagesRef.current[chat.id] = committed;\n\n const lastMsg = committed[committed.length - 1];\n if (lastMsg) {\n setChats((prev) =>\n prev.map((c) =>\n c.id === chat.id ? {...c, lastMessage: getMessageTextContent(lastMsg)} : c,\n ),\n );\n }\n }\n }, []);\n\n const streamResult = useOpenAIStreamAdapter(streamResponse, {\n initialMessages: streamOptions?.initialMessages ?? [],\n assistantMessageId: streamOptions?.assistantMessageId ?? 'assistant-idle',\n onStreamEnd: handleStreamEnd,\n });\n\n const hasResponse = Boolean(streamResponse);\n\n const displayMessages =\n hasResponse && streamResult.messages.length > 0 ? streamResult.messages : messages;\n\n const status = useMemo((): ChatStatus => {\n if (!hasResponse) {\n return isFetching ? 'submitted' : 'ready';\n }\n if (streamResult.status === 'streaming') return 'streaming';\n if (streamResult.status === 'error') return 'error';\n return 'ready';\n }, [hasResponse, isFetching, streamResult.status]);\n\n const handleSendMessage = useCallback(\n async (data: TSubmitData) => {\n // Auto-create a chat when history is enabled and no chat is active yet\n if (showHistory && !activeChatRef.current) {\n const newChat: ChatType = {\n id: Date.now().toString(),\n name: deriveChatName(data.content),\n createTime: new Date().toISOString(),\n };\n chatMessagesRef.current[newChat.id] = [];\n activeChatRef.current = newChat;\n setActiveChat(newChat);\n setChats((prev) => [newChat, ...prev]);\n }\n\n const userMessage: TUserMessage = {\n id: Date.now().toString(),\n role: 'user',\n content: data.content,\n };\n const messagesWithUser: TChatMessage[] = [...messages, userMessage];\n const assistantMessageId = (Date.now() + 1).toString();\n\n setMessages(messagesWithUser);\n setIsFetching(true);\n\n const abortController = new AbortController();\n setController(abortController);\n\n try {\n const apiMessages: ApiMessage[] = [\n ...(systemPrompt ? [{role: 'system' as const, content: systemPrompt}] : []),\n ...messages\n .filter(\n (msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '',\n )\n .map((msg) => ({\n role: msg.role as 'user' | 'assistant',\n content: getMessageTextContent(msg),\n })),\n {role: 'user' as const, content: data.content},\n ];\n\n const response = await fetch(apiUrl, {\n ...requestInit,\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n ...requestInit?.headers,\n },\n body: JSON.stringify({messages: apiMessages}),\n signal: abortController.signal,\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n setStreamResponse(response);\n setStreamOptions({initialMessages: messagesWithUser, assistantMessageId});\n } catch (error) {\n if ((error as Error).name !== 'AbortError') {\n const errorMessage: TAssistantMessage = {\n id: (Date.now() + 2).toString(),\n role: 'assistant',\n content: (error as Error).message,\n };\n setMessages((prev) => {\n const filtered = prev.filter(\n (msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '',\n );\n return [...filtered, errorMessage];\n });\n }\n setStreamResponse(null);\n setStreamOptions(null);\n } finally {\n setIsFetching(false);\n setController(null);\n }\n },\n [messages, apiUrl, systemPrompt, requestInit, showHistory],\n );\n\n const handleCancel = useCallback(async () => {\n controller?.abort();\n setStreamResponse(null);\n setStreamOptions(null);\n }, [controller]);\n\n const handleRetry = useCallback(async () => {\n const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');\n if (!lastUserMsg) return;\n\n const lastUserIndex = messages.lastIndexOf(lastUserMsg);\n setMessages(messages.slice(0, lastUserIndex));\n\n await handleSendMessage({content: lastUserMsg.content as string});\n }, [messages, handleSendMessage]);\n\n const handleCreateChat = useCallback(() => {\n const current = activeChatRef.current;\n if (current) {\n chatMessagesRef.current[current.id] = messages;\n }\n\n const newChat: ChatType = {\n id: Date.now().toString(),\n name: 'New chat',\n createTime: new Date().toISOString(),\n };\n chatMessagesRef.current[newChat.id] = [];\n setChats((prev) => [newChat, ...prev]);\n setActiveChat(newChat);\n setMessages([]);\n }, [messages]);\n\n const handleSelectChat = useCallback(\n (chat: ChatType) => {\n const current = activeChatRef.current;\n if (current) {\n chatMessagesRef.current[current.id] = messages;\n }\n\n setActiveChat(chat);\n setMessages(chatMessagesRef.current[chat.id] ?? []);\n },\n [messages],\n );\n\n const handleDeleteChat = useCallback((chat: ChatType) => {\n delete chatMessagesRef.current[chat.id];\n setChats((prev) => prev.filter((c) => c.id !== chat.id));\n\n if (activeChatRef.current?.id === chat.id) {\n setActiveChat(null);\n setMessages([]);\n }\n }, []);\n\n return (\n <ChatContainer\n {...chatContainerProps}\n messages={displayMessages}\n status={status}\n error={streamResult.error}\n onSendMessage={handleSendMessage}\n onCancel={handleCancel}\n onRetry={handleRetry}\n chats={showHistory ? chats : undefined}\n activeChat={showHistory ? activeChat : undefined}\n onSelectChat={showHistory ? handleSelectChat : undefined}\n onCreateChat={showHistory ? handleCreateChat : undefined}\n onDeleteChat={showHistory ? handleDeleteChat : undefined}\n showHistory={showHistory}\n showNewChat={showNewChat}\n />\n );\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export { AIStudioChat } from "./AIStudioChat.js";
2
+ export type { AIStudioChatProps } from "./types.js";
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AIStudioChat = void 0;
4
+ var AIStudioChat_1 = require("./AIStudioChat.js");
5
+ Object.defineProperty(exports, "AIStudioChat", { enumerable: true, get: function () { return AIStudioChat_1.AIStudioChat; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/pages/AIStudioChat/index.ts"],"names":[],"mappings":";;;AAAA,kDAA4C;AAApC,4GAAA,YAAY,OAAA","sourcesContent":["export {AIStudioChat} from './AIStudioChat';\nexport type {AIStudioChatProps} from './types';\n"]}
@@ -0,0 +1,24 @@
1
+ import type { TChatMessage } from "../../../types/index.js";
2
+ import type { ChatContainerProps } from "../ChatContainer/index.js";
3
+ /**
4
+ * Props managed internally by AIStudioChat that are omitted from ChatContainerProps
5
+ */
6
+ type ManagedChatContainerProps = 'messages' | 'status' | 'error' | 'onSendMessage' | 'onCancel' | 'onRetry' | 'chats' | 'activeChat' | 'onSelectChat' | 'onCreateChat' | 'onDeleteChat' | 'onDeleteAllChats' | 'showHistory' | 'showNewChat';
7
+ /**
8
+ * Props for AIStudioChat component
9
+ */
10
+ export interface AIStudioChatProps extends Omit<ChatContainerProps, ManagedChatContainerProps> {
11
+ /** URL of the OpenAI-compatible streaming API endpoint */
12
+ apiUrl: string;
13
+ /** Initial messages to pre-populate the chat (e.g., for restoring a session) */
14
+ initialMessages?: TChatMessage[];
15
+ /** Enable multi-chat history panel (default: false) */
16
+ showHistory?: boolean;
17
+ /** Show new chat button in header (defaults to the value of showHistory) */
18
+ showNewChat?: boolean;
19
+ /** System prompt sent with every request */
20
+ systemPrompt?: string;
21
+ /** Additional fetch options applied to every API request (e.g., auth headers) */
22
+ requestInit?: Omit<RequestInit, 'body' | 'method' | 'signal'>;
23
+ }
24
+ export {};
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"../../../../../src","sources":["components/pages/AIStudioChat/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {TChatMessage} from '../../../types';\nimport type {ChatContainerProps} from '../ChatContainer';\n\n/**\n * Props managed internally by AIStudioChat that are omitted from ChatContainerProps\n */\ntype ManagedChatContainerProps =\n | 'messages'\n | 'status'\n | 'error'\n | 'onSendMessage'\n | 'onCancel'\n | 'onRetry'\n | 'chats'\n | 'activeChat'\n | 'onSelectChat'\n | 'onCreateChat'\n | 'onDeleteChat'\n | 'onDeleteAllChats'\n | 'showHistory'\n | 'showNewChat';\n\n/**\n * Props for AIStudioChat component\n */\nexport interface AIStudioChatProps extends Omit<ChatContainerProps, ManagedChatContainerProps> {\n /** URL of the OpenAI-compatible streaming API endpoint */\n apiUrl: string;\n\n /** Initial messages to pre-populate the chat (e.g., for restoring a session) */\n initialMessages?: TChatMessage[];\n\n /** Enable multi-chat history panel (default: false) */\n showHistory?: boolean;\n\n /** Show new chat button in header (defaults to the value of showHistory) */\n showNewChat?: boolean;\n\n /** System prompt sent with every request */\n systemPrompt?: string;\n\n /** Additional fetch options applied to every API request (e.g., auth headers) */\n requestInit?: Omit<RequestInit, 'body' | 'method' | 'signal'>;\n}\n"]}
@@ -1 +1,2 @@
1
+ export * from "./AIStudioChat/index.js";
1
2
  export * from "./ChatContainer/index.js";
@@ -2,5 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  // Export all pages
5
+ tslib_1.__exportStar(require("./AIStudioChat/index.js"), exports);
5
6
  tslib_1.__exportStar(require("./ChatContainer/index.js"), exports);
6
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../../src","sources":["components/pages/index.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,mEAAgC","sourcesContent":["// Export all pages\nexport * from './ChatContainer';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../src","sources":["components/pages/index.ts"],"names":[],"mappings":";;;AAAA,mBAAmB;AACnB,kEAA+B;AAC/B,mEAAgC","sourcesContent":["// Export all pages\nexport * from './AIStudioChat';\nexport * from './ChatContainer';\n"]}
@@ -1 +1 @@
1
- {"version":"1.15.1","type":"commonjs","sideEffects":["*.css","*.scss"]}
1
+ {"version":"1.15.2-beta.12d894e43bcc0b7b911e62db16634e4e418ff48a.0","type":"commonjs","sideEffects":["*.css","*.scss"]}
@@ -1,3 +1,4 @@
1
+ import { OptionsType } from '@diplodoc/transform/lib/typings';
1
2
  import { DOMProps, QAProps } from '@gravity-ui/uikit';
2
3
  import type { ThinkingMessageContentData } from "../../../types/messages.js";
3
4
  import "./ThinkingMessage.css";
@@ -5,7 +6,18 @@ import "./ThinkingMessage.css";
5
6
  * Props for the ThinkingMessage component.
6
7
  * Combines DOM props, QA props, and thinking message data.
7
8
  */
8
- export type ThinkingMessageProps = DOMProps & QAProps & ThinkingMessageContentData;
9
+ export type ThinkingMessageProps = DOMProps & QAProps & ThinkingMessageContentData & {
10
+ /**
11
+ * How thinking content strings are rendered.
12
+ * @default 'plain'
13
+ */
14
+ format?: 'plain' | 'markdown';
15
+ /**
16
+ * Options for @diplodoc/transform when `format` is `'markdown'`.
17
+ * Shallow-merged with the component defaults (`disableCommonAnchors: true`).
18
+ */
19
+ transformOptions?: OptionsType;
20
+ };
9
21
  /**
10
22
  * ThinkingMessage component displays AI model's internal reasoning process.
11
23
  * Shows a collapsible block with thinking content and a copy button.
@@ -6,9 +6,13 @@ import { Button, Icon, Text } from '@gravity-ui/uikit';
6
6
  import { block } from "../../../utils/cn.js";
7
7
  import { ActionButton } from "../../atoms/ActionButton/index.js";
8
8
  import { Loader } from "../../atoms/Loader/index.js";
9
+ import { MarkdownRenderer } from "../../atoms/MarkdownRenderer/index.js";
9
10
  import { useThinkingMessage } from "./useThinkingMessage.js";
10
11
  import "./ThinkingMessage.css";
11
12
  const b = block('thinking-message');
13
+ const defaultTransformOptions = {
14
+ disableCommonAnchors: true,
15
+ };
12
16
  /**
13
17
  * ThinkingMessage component displays AI model's internal reasoning process.
14
18
  * Shows a collapsible block with thinking content and a copy button.
@@ -18,8 +22,9 @@ const b = block('thinking-message');
18
22
  * @returns Rendered thinking message component
19
23
  */
20
24
  export const ThinkingMessage = (props) => {
21
- const { className, qa, style } = props, data = __rest(props, ["className", "qa", "style"]);
25
+ const { className, qa, style, format = 'plain', transformOptions } = props, data = __rest(props, ["className", "qa", "style", "format", "transformOptions"]);
26
+ const markdownTransformOptions = Object.assign(Object.assign({}, defaultTransformOptions), transformOptions);
22
27
  const { isExpanded, toggleExpanded, buttonTitle, content, showLoader, handleCopy, showCopyButton, } = useThinkingMessage(data);
23
- return (_jsxs("div", { className: b(null, className), "data-qa": qa, style: style, children: [_jsxs("div", { className: b('buttons'), children: [_jsxs(Button, { size: "s", onClick: toggleExpanded, children: [buttonTitle, _jsx(Icon, { data: isExpanded ? ChevronUp : ChevronDown })] }), showLoader ? (_jsx(Loader, { view: "loading", size: "xs" })) : (showCopyButton && (_jsx(ActionButton, { size: "s", onClick: handleCopy, children: _jsx(Icon, { data: Copy, size: 16 }) })))] }), isExpanded && (_jsx("div", { className: b('container'), children: content.map((item, index) => (_jsx(Text, { className: b('content'), children: item }, index))) }))] }));
28
+ return (_jsxs("div", { className: b(null, className), "data-qa": qa, style: style, children: [_jsxs("div", { className: b('buttons'), children: [_jsxs(Button, { size: "s", onClick: toggleExpanded, children: [buttonTitle, _jsx(Icon, { data: isExpanded ? ChevronUp : ChevronDown })] }), showLoader ? (_jsx(Loader, { view: "loading", size: "xs" })) : (showCopyButton && (_jsx(ActionButton, { size: "s", onClick: handleCopy, children: _jsx(Icon, { data: Copy, size: 16 }) })))] }), isExpanded && (_jsx("div", { className: b('container'), children: content.map((item, index) => format === 'markdown' ? (_jsx(MarkdownRenderer, { className: b('content'), content: item, transformOptions: markdownTransformOptions }, index)) : (_jsx(Text, { className: b('content'), children: item }, index))) }))] }));
24
29
  };
25
30
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/organisms/ThinkingMessage/index.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;AAEb,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,IAAI,EAAC,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAC,MAAM,EAAY,IAAI,EAAW,IAAI,EAAC,MAAM,mBAAmB,CAAC;AAGxE,OAAO,EAAC,KAAK,EAAC,6BAA0B;AACxC,OAAO,EAAC,YAAY,EAAC,0CAAiC;AACtD,OAAO,EAAC,MAAM,EAAC,oCAA2B;AAE1C,OAAO,EAAC,kBAAkB,EAAC,gCAA6B;AAExD,+BAAgC;AAEhC,MAAM,CAAC,GAAG,KAAK,CAAC,kBAAkB,CAAC,CAAC;AAQpC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAA2B,EAAE,EAAE;IAC3D,MAAM,EAAC,SAAS,EAAE,EAAE,EAAE,KAAK,KAAa,KAAK,EAAb,IAAI,UAAI,KAAK,EAAvC,4BAA+B,CAAQ,CAAC;IAE9C,MAAM,EACF,UAAU,EACV,cAAc,EACd,WAAW,EACX,OAAO,EACP,UAAU,EACV,UAAU,EACV,cAAc,GACjB,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAE7B,OAAO,CACH,eAAK,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,aAAW,EAAE,EAAE,KAAK,EAAE,KAAK,aACzD,eAAK,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,aACxB,MAAC,MAAM,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,cAAc,aACnC,WAAW,EACZ,KAAC,IAAI,IAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,GAAI,IAC/C,EACR,UAAU,CAAC,CAAC,CAAC,CACV,KAAC,MAAM,IAAC,IAAI,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,GAAG,CACtC,CAAC,CAAC,CAAC,CACA,cAAc,IAAI,CACd,KAAC,YAAY,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,UAAU,YACtC,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAI,GACnB,CAClB,CACJ,IACC,EACL,UAAU,IAAI,CACX,cAAK,SAAS,EAAE,CAAC,CAAC,WAAW,CAAC,YACzB,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAC1B,KAAC,IAAI,IAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,YACxB,IAAI,IAD2B,KAAK,CAElC,CACV,CAAC,GACA,CACT,IACC,CACT,CAAC;AACN,CAAC,CAAC","sourcesContent":["'use client';\n\nimport {ChevronDown, ChevronUp, Copy} from '@gravity-ui/icons';\nimport {Button, DOMProps, Icon, QAProps, Text} from '@gravity-ui/uikit';\n\nimport type {ThinkingMessageContentData} from '../../../types/messages';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\nimport {Loader} from '../../atoms/Loader';\n\nimport {useThinkingMessage} from './useThinkingMessage';\n\nimport './ThinkingMessage.scss';\n\nconst b = block('thinking-message');\n\n/**\n * Props for the ThinkingMessage component.\n * Combines DOM props, QA props, and thinking message data.\n */\nexport type ThinkingMessageProps = DOMProps & QAProps & ThinkingMessageContentData;\n\n/**\n * ThinkingMessage component displays AI model's internal reasoning process.\n * Shows a collapsible block with thinking content and a copy button.\n * Displays a loader when the thinking process is still in progress.\n *\n * @param props - Component props\n * @returns Rendered thinking message component\n */\nexport const ThinkingMessage = (props: ThinkingMessageProps) => {\n const {className, qa, style, ...data} = props;\n\n const {\n isExpanded,\n toggleExpanded,\n buttonTitle,\n content,\n showLoader,\n handleCopy,\n showCopyButton,\n } = useThinkingMessage(data);\n\n return (\n <div className={b(null, className)} data-qa={qa} style={style}>\n <div className={b('buttons')}>\n <Button size=\"s\" onClick={toggleExpanded}>\n {buttonTitle}\n <Icon data={isExpanded ? ChevronUp : ChevronDown} />\n </Button>\n {showLoader ? (\n <Loader view=\"loading\" size=\"xs\" />\n ) : (\n showCopyButton && (\n <ActionButton size=\"s\" onClick={handleCopy}>\n <Icon data={Copy} size={16} />\n </ActionButton>\n )\n )}\n </div>\n {isExpanded && (\n <div className={b('container')}>\n {content.map((item, index) => (\n <Text className={b('content')} key={index}>\n {item}\n </Text>\n ))}\n </div>\n )}\n </div>\n );\n};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/organisms/ThinkingMessage/index.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;AAGb,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,IAAI,EAAC,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAC,MAAM,EAAY,IAAI,EAAW,IAAI,EAAC,MAAM,mBAAmB,CAAC;AAGxE,OAAO,EAAC,KAAK,EAAC,6BAA0B;AACxC,OAAO,EAAC,YAAY,EAAC,0CAAiC;AACtD,OAAO,EAAC,MAAM,EAAC,oCAA2B;AAC1C,OAAO,EAAC,gBAAgB,EAAC,8CAAqC;AAE9D,OAAO,EAAC,kBAAkB,EAAC,gCAA6B;AAExD,+BAAgC;AAEhC,MAAM,CAAC,GAAG,KAAK,CAAC,kBAAkB,CAAC,CAAC;AAqBpC,MAAM,uBAAuB,GAAgB;IACzC,oBAAoB,EAAE,IAAI;CAC7B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAA2B,EAAE,EAAE;IAC3D,MAAM,EAAC,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,gBAAgB,KAAa,KAAK,EAAb,IAAI,UAAI,KAAK,EAA3E,0DAAmE,CAAQ,CAAC;IAElF,MAAM,wBAAwB,mCACvB,uBAAuB,GACvB,gBAAgB,CACtB,CAAC;IAEF,MAAM,EACF,UAAU,EACV,cAAc,EACd,WAAW,EACX,OAAO,EACP,UAAU,EACV,UAAU,EACV,cAAc,GACjB,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAE7B,OAAO,CACH,eAAK,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,aAAW,EAAE,EAAE,KAAK,EAAE,KAAK,aACzD,eAAK,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,aACxB,MAAC,MAAM,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,cAAc,aACnC,WAAW,EACZ,KAAC,IAAI,IAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,GAAI,IAC/C,EACR,UAAU,CAAC,CAAC,CAAC,CACV,KAAC,MAAM,IAAC,IAAI,EAAC,SAAS,EAAC,IAAI,EAAC,IAAI,GAAG,CACtC,CAAC,CAAC,CAAC,CACA,cAAc,IAAI,CACd,KAAC,YAAY,IAAC,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,UAAU,YACtC,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAI,GACnB,CAClB,CACJ,IACC,EACL,UAAU,IAAI,CACX,cAAK,SAAS,EAAE,CAAC,CAAC,WAAW,CAAC,YACzB,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACzB,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,CACpB,KAAC,gBAAgB,IACb,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,EAEvB,OAAO,EAAE,IAAI,EACb,gBAAgB,EAAE,wBAAwB,IAFrC,KAAK,CAGZ,CACL,CAAC,CAAC,CAAC,CACA,KAAC,IAAI,IAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,YACxB,IAAI,IAD2B,KAAK,CAElC,CACV,CACJ,GACC,CACT,IACC,CACT,CAAC;AACN,CAAC,CAAC","sourcesContent":["'use client';\n\nimport {OptionsType} from '@diplodoc/transform/lib/typings';\nimport {ChevronDown, ChevronUp, Copy} from '@gravity-ui/icons';\nimport {Button, DOMProps, Icon, QAProps, Text} from '@gravity-ui/uikit';\n\nimport type {ThinkingMessageContentData} from '../../../types/messages';\nimport {block} from '../../../utils/cn';\nimport {ActionButton} from '../../atoms/ActionButton';\nimport {Loader} from '../../atoms/Loader';\nimport {MarkdownRenderer} from '../../atoms/MarkdownRenderer';\n\nimport {useThinkingMessage} from './useThinkingMessage';\n\nimport './ThinkingMessage.scss';\n\nconst b = block('thinking-message');\n\n/**\n * Props for the ThinkingMessage component.\n * Combines DOM props, QA props, and thinking message data.\n */\nexport type ThinkingMessageProps = DOMProps &\n QAProps &\n ThinkingMessageContentData & {\n /**\n * How thinking content strings are rendered.\n * @default 'plain'\n */\n format?: 'plain' | 'markdown';\n /**\n * Options for @diplodoc/transform when `format` is `'markdown'`.\n * Shallow-merged with the component defaults (`disableCommonAnchors: true`).\n */\n transformOptions?: OptionsType;\n };\n\nconst defaultTransformOptions: OptionsType = {\n disableCommonAnchors: true,\n};\n\n/**\n * ThinkingMessage component displays AI model's internal reasoning process.\n * Shows a collapsible block with thinking content and a copy button.\n * Displays a loader when the thinking process is still in progress.\n *\n * @param props - Component props\n * @returns Rendered thinking message component\n */\nexport const ThinkingMessage = (props: ThinkingMessageProps) => {\n const {className, qa, style, format = 'plain', transformOptions, ...data} = props;\n\n const markdownTransformOptions: OptionsType = {\n ...defaultTransformOptions,\n ...transformOptions,\n };\n\n const {\n isExpanded,\n toggleExpanded,\n buttonTitle,\n content,\n showLoader,\n handleCopy,\n showCopyButton,\n } = useThinkingMessage(data);\n\n return (\n <div className={b(null, className)} data-qa={qa} style={style}>\n <div className={b('buttons')}>\n <Button size=\"s\" onClick={toggleExpanded}>\n {buttonTitle}\n <Icon data={isExpanded ? ChevronUp : ChevronDown} />\n </Button>\n {showLoader ? (\n <Loader view=\"loading\" size=\"xs\" />\n ) : (\n showCopyButton && (\n <ActionButton size=\"s\" onClick={handleCopy}>\n <Icon data={Copy} size={16} />\n </ActionButton>\n )\n )}\n </div>\n {isExpanded && (\n <div className={b('container')}>\n {content.map((item, index) =>\n format === 'markdown' ? (\n <MarkdownRenderer\n className={b('content')}\n key={index}\n content={item}\n transformOptions={markdownTransformOptions}\n />\n ) : (\n <Text className={b('content')} key={index}>\n {item}\n </Text>\n ),\n )}\n </div>\n )}\n </div>\n );\n};\n"]}
@@ -0,0 +1,11 @@
1
+ import type { AIStudioChatProps } from "./types.js";
2
+ /**
3
+ * AIStudioChat - a ready-to-use chat component with built-in OpenAI streaming support.
4
+ *
5
+ * Wraps ChatContainer and handles all state internally: streaming, message history,
6
+ * multi-chat management, and cancellation. Requires only an `apiUrl` to start working.
7
+ *
8
+ * @param props - component props
9
+ * @returns React component
10
+ */
11
+ export declare function AIStudioChat(props: AIStudioChatProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,213 @@
1
+ import { __rest } from "tslib";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useCallback, useMemo, useRef, useState } from 'react';
4
+ import { useOpenAIStreamAdapter } from "../../../adapters/openai/index.js";
5
+ import { ChatContainer } from "../ChatContainer/index.js";
6
+ /**
7
+ * Extracts plain text content from any TChatMessage variant.
8
+ */
9
+ function getMessageTextContent(message) {
10
+ var _a;
11
+ if (message.role === 'user') {
12
+ return typeof message.content === 'string' ? message.content : '';
13
+ }
14
+ const { content } = message;
15
+ if (typeof content === 'string') {
16
+ return content;
17
+ }
18
+ if (Array.isArray(content)) {
19
+ return content
20
+ .map((item) => {
21
+ var _a;
22
+ if (typeof item === 'string')
23
+ return item;
24
+ if (item.type === 'text' && ((_a = item.data) === null || _a === void 0 ? void 0 : _a.text))
25
+ return item.data.text;
26
+ return '';
27
+ })
28
+ .join('\n');
29
+ }
30
+ if (content && typeof content === 'object' && 'type' in content) {
31
+ if (content.type === 'text' && ((_a = content.data) === null || _a === void 0 ? void 0 : _a.text)) {
32
+ return content.data.text;
33
+ }
34
+ }
35
+ return '';
36
+ }
37
+ /**
38
+ * Derives a human-readable chat name from the first user message.
39
+ */
40
+ function deriveChatName(content) {
41
+ const trimmed = content.trim();
42
+ return trimmed.length > 40 ? `${trimmed.slice(0, 40)}...` : trimmed || 'New chat';
43
+ }
44
+ /**
45
+ * AIStudioChat - a ready-to-use chat component with built-in OpenAI streaming support.
46
+ *
47
+ * Wraps ChatContainer and handles all state internally: streaming, message history,
48
+ * multi-chat management, and cancellation. Requires only an `apiUrl` to start working.
49
+ *
50
+ * @param props - component props
51
+ * @returns React component
52
+ */
53
+ export function AIStudioChat(props) {
54
+ var _a, _b;
55
+ const { apiUrl, initialMessages = [], showHistory = false, showNewChat = showHistory, systemPrompt, requestInit } = props, chatContainerProps = __rest(props, ["apiUrl", "initialMessages", "showHistory", "showNewChat", "systemPrompt", "requestInit"]);
56
+ // Current chat messages
57
+ const [messages, setMessages] = useState(initialMessages);
58
+ // Multi-chat state (used when showHistory=true)
59
+ const [chats, setChats] = useState([]);
60
+ const [activeChat, setActiveChat] = useState(null);
61
+ // Per-chat message store (ref to avoid unnecessary re-renders on switch)
62
+ const chatMessagesRef = useRef({});
63
+ // Always-current reference to activeChat for use inside async callbacks
64
+ const activeChatRef = useRef(null);
65
+ activeChatRef.current = activeChat;
66
+ // Streaming state
67
+ const [controller, setController] = useState(null);
68
+ const [isFetching, setIsFetching] = useState(false);
69
+ const [streamResponse, setStreamResponse] = useState(null);
70
+ const [streamOptions, setStreamOptions] = useState(null);
71
+ const handleStreamEnd = useCallback((finalMessages) => {
72
+ const committed = finalMessages.filter((msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '');
73
+ setMessages(committed);
74
+ setStreamResponse(null);
75
+ setStreamOptions(null);
76
+ // Persist messages and update the last message preview in history
77
+ const chat = activeChatRef.current;
78
+ if (chat) {
79
+ chatMessagesRef.current[chat.id] = committed;
80
+ const lastMsg = committed[committed.length - 1];
81
+ if (lastMsg) {
82
+ setChats((prev) => prev.map((c) => c.id === chat.id ? Object.assign(Object.assign({}, c), { lastMessage: getMessageTextContent(lastMsg) }) : c));
83
+ }
84
+ }
85
+ }, []);
86
+ const streamResult = useOpenAIStreamAdapter(streamResponse, {
87
+ initialMessages: (_a = streamOptions === null || streamOptions === void 0 ? void 0 : streamOptions.initialMessages) !== null && _a !== void 0 ? _a : [],
88
+ assistantMessageId: (_b = streamOptions === null || streamOptions === void 0 ? void 0 : streamOptions.assistantMessageId) !== null && _b !== void 0 ? _b : 'assistant-idle',
89
+ onStreamEnd: handleStreamEnd,
90
+ });
91
+ const hasResponse = Boolean(streamResponse);
92
+ const displayMessages = hasResponse && streamResult.messages.length > 0 ? streamResult.messages : messages;
93
+ const status = useMemo(() => {
94
+ if (!hasResponse) {
95
+ return isFetching ? 'submitted' : 'ready';
96
+ }
97
+ if (streamResult.status === 'streaming')
98
+ return 'streaming';
99
+ if (streamResult.status === 'error')
100
+ return 'error';
101
+ return 'ready';
102
+ }, [hasResponse, isFetching, streamResult.status]);
103
+ const handleSendMessage = useCallback(async (data) => {
104
+ // Auto-create a chat when history is enabled and no chat is active yet
105
+ if (showHistory && !activeChatRef.current) {
106
+ const newChat = {
107
+ id: Date.now().toString(),
108
+ name: deriveChatName(data.content),
109
+ createTime: new Date().toISOString(),
110
+ };
111
+ chatMessagesRef.current[newChat.id] = [];
112
+ activeChatRef.current = newChat;
113
+ setActiveChat(newChat);
114
+ setChats((prev) => [newChat, ...prev]);
115
+ }
116
+ const userMessage = {
117
+ id: Date.now().toString(),
118
+ role: 'user',
119
+ content: data.content,
120
+ };
121
+ const messagesWithUser = [...messages, userMessage];
122
+ const assistantMessageId = (Date.now() + 1).toString();
123
+ setMessages(messagesWithUser);
124
+ setIsFetching(true);
125
+ const abortController = new AbortController();
126
+ setController(abortController);
127
+ try {
128
+ const apiMessages = [
129
+ ...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []),
130
+ ...messages
131
+ .filter((msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '')
132
+ .map((msg) => ({
133
+ role: msg.role,
134
+ content: getMessageTextContent(msg),
135
+ })),
136
+ { role: 'user', content: data.content },
137
+ ];
138
+ const response = await fetch(apiUrl, Object.assign(Object.assign({}, requestInit), { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, requestInit === null || requestInit === void 0 ? void 0 : requestInit.headers), body: JSON.stringify({ messages: apiMessages }), signal: abortController.signal }));
139
+ if (!response.ok) {
140
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
141
+ }
142
+ setStreamResponse(response);
143
+ setStreamOptions({ initialMessages: messagesWithUser, assistantMessageId });
144
+ }
145
+ catch (error) {
146
+ if (error.name !== 'AbortError') {
147
+ const errorMessage = {
148
+ id: (Date.now() + 2).toString(),
149
+ role: 'assistant',
150
+ content: error.message,
151
+ };
152
+ setMessages((prev) => {
153
+ const filtered = prev.filter((msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '');
154
+ return [...filtered, errorMessage];
155
+ });
156
+ }
157
+ setStreamResponse(null);
158
+ setStreamOptions(null);
159
+ }
160
+ finally {
161
+ setIsFetching(false);
162
+ setController(null);
163
+ }
164
+ }, [messages, apiUrl, systemPrompt, requestInit, showHistory]);
165
+ const handleCancel = useCallback(async () => {
166
+ controller === null || controller === void 0 ? void 0 : controller.abort();
167
+ setStreamResponse(null);
168
+ setStreamOptions(null);
169
+ }, [controller]);
170
+ const handleRetry = useCallback(async () => {
171
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
172
+ if (!lastUserMsg)
173
+ return;
174
+ const lastUserIndex = messages.lastIndexOf(lastUserMsg);
175
+ setMessages(messages.slice(0, lastUserIndex));
176
+ await handleSendMessage({ content: lastUserMsg.content });
177
+ }, [messages, handleSendMessage]);
178
+ const handleCreateChat = useCallback(() => {
179
+ const current = activeChatRef.current;
180
+ if (current) {
181
+ chatMessagesRef.current[current.id] = messages;
182
+ }
183
+ const newChat = {
184
+ id: Date.now().toString(),
185
+ name: 'New chat',
186
+ createTime: new Date().toISOString(),
187
+ };
188
+ chatMessagesRef.current[newChat.id] = [];
189
+ setChats((prev) => [newChat, ...prev]);
190
+ setActiveChat(newChat);
191
+ setMessages([]);
192
+ }, [messages]);
193
+ const handleSelectChat = useCallback((chat) => {
194
+ var _a;
195
+ const current = activeChatRef.current;
196
+ if (current) {
197
+ chatMessagesRef.current[current.id] = messages;
198
+ }
199
+ setActiveChat(chat);
200
+ setMessages((_a = chatMessagesRef.current[chat.id]) !== null && _a !== void 0 ? _a : []);
201
+ }, [messages]);
202
+ const handleDeleteChat = useCallback((chat) => {
203
+ var _a;
204
+ delete chatMessagesRef.current[chat.id];
205
+ setChats((prev) => prev.filter((c) => c.id !== chat.id));
206
+ if (((_a = activeChatRef.current) === null || _a === void 0 ? void 0 : _a.id) === chat.id) {
207
+ setActiveChat(null);
208
+ setMessages([]);
209
+ }
210
+ }, []);
211
+ return (_jsx(ChatContainer, Object.assign({}, chatContainerProps, { messages: displayMessages, status: status, error: streamResult.error, onSendMessage: handleSendMessage, onCancel: handleCancel, onRetry: handleRetry, chats: showHistory ? chats : undefined, activeChat: showHistory ? activeChat : undefined, onSelectChat: showHistory ? handleSelectChat : undefined, onCreateChat: showHistory ? handleCreateChat : undefined, onDeleteChat: showHistory ? handleDeleteChat : undefined, showHistory: showHistory, showNewChat: showNewChat })));
212
+ }
213
+ //# sourceMappingURL=AIStudioChat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AIStudioChat.js","sourceRoot":"../../../../../src","sources":["components/pages/AIStudioChat/AIStudioChat.tsx"],"names":[],"mappings":"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","sourcesContent":["import {useCallback, useMemo, useRef, useState} from 'react';\n\nimport {useOpenAIStreamAdapter} from '../../../adapters/openai';\nimport type {\n ChatStatus,\n ChatType,\n TAssistantMessage,\n TChatMessage,\n TSubmitData,\n TUserMessage,\n} from '../../../types';\nimport {ChatContainer} from '../ChatContainer';\n\nimport type {AIStudioChatProps} from './types';\n\ntype ApiMessage = {\n role: 'user' | 'assistant' | 'system';\n content: string;\n};\n\ntype StreamOptions = {\n initialMessages: TChatMessage[];\n assistantMessageId: string;\n};\n\n/**\n * Extracts plain text content from any TChatMessage variant.\n */\nfunction getMessageTextContent(message: TChatMessage): string {\n if (message.role === 'user') {\n return typeof message.content === 'string' ? message.content : '';\n }\n\n const {content} = message as TAssistantMessage;\n\n if (typeof content === 'string') {\n return content;\n }\n\n if (Array.isArray(content)) {\n return content\n .map((item) => {\n if (typeof item === 'string') return item;\n if (item.type === 'text' && item.data?.text) return item.data.text;\n return '';\n })\n .join('\\n');\n }\n\n if (content && typeof content === 'object' && 'type' in content) {\n if (content.type === 'text' && content.data?.text) {\n return content.data.text;\n }\n }\n\n return '';\n}\n\n/**\n * Derives a human-readable chat name from the first user message.\n */\nfunction deriveChatName(content: string): string {\n const trimmed = content.trim();\n return trimmed.length > 40 ? `${trimmed.slice(0, 40)}...` : trimmed || 'New chat';\n}\n\n/**\n * AIStudioChat - a ready-to-use chat component with built-in OpenAI streaming support.\n *\n * Wraps ChatContainer and handles all state internally: streaming, message history,\n * multi-chat management, and cancellation. Requires only an `apiUrl` to start working.\n *\n * @param props - component props\n * @returns React component\n */\nexport function AIStudioChat(props: AIStudioChatProps) {\n const {\n apiUrl,\n initialMessages = [],\n showHistory = false,\n showNewChat = showHistory,\n systemPrompt,\n requestInit,\n ...chatContainerProps\n } = props;\n\n // Current chat messages\n const [messages, setMessages] = useState<TChatMessage[]>(initialMessages);\n\n // Multi-chat state (used when showHistory=true)\n const [chats, setChats] = useState<ChatType[]>([]);\n const [activeChat, setActiveChat] = useState<ChatType | null>(null);\n\n // Per-chat message store (ref to avoid unnecessary re-renders on switch)\n const chatMessagesRef = useRef<Record<string, TChatMessage[]>>({});\n\n // Always-current reference to activeChat for use inside async callbacks\n const activeChatRef = useRef<ChatType | null>(null);\n activeChatRef.current = activeChat;\n\n // Streaming state\n const [controller, setController] = useState<AbortController | null>(null);\n const [isFetching, setIsFetching] = useState(false);\n const [streamResponse, setStreamResponse] = useState<Response | null>(null);\n const [streamOptions, setStreamOptions] = useState<StreamOptions | null>(null);\n\n const handleStreamEnd = useCallback((finalMessages: TChatMessage[]) => {\n const committed = finalMessages.filter(\n (msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '',\n );\n\n setMessages(committed);\n setStreamResponse(null);\n setStreamOptions(null);\n\n // Persist messages and update the last message preview in history\n const chat = activeChatRef.current;\n if (chat) {\n chatMessagesRef.current[chat.id] = committed;\n\n const lastMsg = committed[committed.length - 1];\n if (lastMsg) {\n setChats((prev) =>\n prev.map((c) =>\n c.id === chat.id ? {...c, lastMessage: getMessageTextContent(lastMsg)} : c,\n ),\n );\n }\n }\n }, []);\n\n const streamResult = useOpenAIStreamAdapter(streamResponse, {\n initialMessages: streamOptions?.initialMessages ?? [],\n assistantMessageId: streamOptions?.assistantMessageId ?? 'assistant-idle',\n onStreamEnd: handleStreamEnd,\n });\n\n const hasResponse = Boolean(streamResponse);\n\n const displayMessages =\n hasResponse && streamResult.messages.length > 0 ? streamResult.messages : messages;\n\n const status = useMemo((): ChatStatus => {\n if (!hasResponse) {\n return isFetching ? 'submitted' : 'ready';\n }\n if (streamResult.status === 'streaming') return 'streaming';\n if (streamResult.status === 'error') return 'error';\n return 'ready';\n }, [hasResponse, isFetching, streamResult.status]);\n\n const handleSendMessage = useCallback(\n async (data: TSubmitData) => {\n // Auto-create a chat when history is enabled and no chat is active yet\n if (showHistory && !activeChatRef.current) {\n const newChat: ChatType = {\n id: Date.now().toString(),\n name: deriveChatName(data.content),\n createTime: new Date().toISOString(),\n };\n chatMessagesRef.current[newChat.id] = [];\n activeChatRef.current = newChat;\n setActiveChat(newChat);\n setChats((prev) => [newChat, ...prev]);\n }\n\n const userMessage: TUserMessage = {\n id: Date.now().toString(),\n role: 'user',\n content: data.content,\n };\n const messagesWithUser: TChatMessage[] = [...messages, userMessage];\n const assistantMessageId = (Date.now() + 1).toString();\n\n setMessages(messagesWithUser);\n setIsFetching(true);\n\n const abortController = new AbortController();\n setController(abortController);\n\n try {\n const apiMessages: ApiMessage[] = [\n ...(systemPrompt ? [{role: 'system' as const, content: systemPrompt}] : []),\n ...messages\n .filter(\n (msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '',\n )\n .map((msg) => ({\n role: msg.role as 'user' | 'assistant',\n content: getMessageTextContent(msg),\n })),\n {role: 'user' as const, content: data.content},\n ];\n\n const response = await fetch(apiUrl, {\n ...requestInit,\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n ...requestInit?.headers,\n },\n body: JSON.stringify({messages: apiMessages}),\n signal: abortController.signal,\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n setStreamResponse(response);\n setStreamOptions({initialMessages: messagesWithUser, assistantMessageId});\n } catch (error) {\n if ((error as Error).name !== 'AbortError') {\n const errorMessage: TAssistantMessage = {\n id: (Date.now() + 2).toString(),\n role: 'assistant',\n content: (error as Error).message,\n };\n setMessages((prev) => {\n const filtered = prev.filter(\n (msg) => msg.role !== 'assistant' || getMessageTextContent(msg) !== '',\n );\n return [...filtered, errorMessage];\n });\n }\n setStreamResponse(null);\n setStreamOptions(null);\n } finally {\n setIsFetching(false);\n setController(null);\n }\n },\n [messages, apiUrl, systemPrompt, requestInit, showHistory],\n );\n\n const handleCancel = useCallback(async () => {\n controller?.abort();\n setStreamResponse(null);\n setStreamOptions(null);\n }, [controller]);\n\n const handleRetry = useCallback(async () => {\n const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');\n if (!lastUserMsg) return;\n\n const lastUserIndex = messages.lastIndexOf(lastUserMsg);\n setMessages(messages.slice(0, lastUserIndex));\n\n await handleSendMessage({content: lastUserMsg.content as string});\n }, [messages, handleSendMessage]);\n\n const handleCreateChat = useCallback(() => {\n const current = activeChatRef.current;\n if (current) {\n chatMessagesRef.current[current.id] = messages;\n }\n\n const newChat: ChatType = {\n id: Date.now().toString(),\n name: 'New chat',\n createTime: new Date().toISOString(),\n };\n chatMessagesRef.current[newChat.id] = [];\n setChats((prev) => [newChat, ...prev]);\n setActiveChat(newChat);\n setMessages([]);\n }, [messages]);\n\n const handleSelectChat = useCallback(\n (chat: ChatType) => {\n const current = activeChatRef.current;\n if (current) {\n chatMessagesRef.current[current.id] = messages;\n }\n\n setActiveChat(chat);\n setMessages(chatMessagesRef.current[chat.id] ?? []);\n },\n [messages],\n );\n\n const handleDeleteChat = useCallback((chat: ChatType) => {\n delete chatMessagesRef.current[chat.id];\n setChats((prev) => prev.filter((c) => c.id !== chat.id));\n\n if (activeChatRef.current?.id === chat.id) {\n setActiveChat(null);\n setMessages([]);\n }\n }, []);\n\n return (\n <ChatContainer\n {...chatContainerProps}\n messages={displayMessages}\n status={status}\n error={streamResult.error}\n onSendMessage={handleSendMessage}\n onCancel={handleCancel}\n onRetry={handleRetry}\n chats={showHistory ? chats : undefined}\n activeChat={showHistory ? activeChat : undefined}\n onSelectChat={showHistory ? handleSelectChat : undefined}\n onCreateChat={showHistory ? handleCreateChat : undefined}\n onDeleteChat={showHistory ? handleDeleteChat : undefined}\n showHistory={showHistory}\n showNewChat={showNewChat}\n />\n );\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export { AIStudioChat } from "./AIStudioChat.js";
2
+ export type { AIStudioChatProps } from "./types.js";
@@ -0,0 +1,2 @@
1
+ export { AIStudioChat } from "./AIStudioChat.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../../src","sources":["components/pages/AIStudioChat/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,0BAAuB","sourcesContent":["export {AIStudioChat} from './AIStudioChat';\nexport type {AIStudioChatProps} from './types';\n"]}
@@ -0,0 +1,24 @@
1
+ import type { TChatMessage } from "../../../types/index.js";
2
+ import type { ChatContainerProps } from "../ChatContainer/index.js";
3
+ /**
4
+ * Props managed internally by AIStudioChat that are omitted from ChatContainerProps
5
+ */
6
+ type ManagedChatContainerProps = 'messages' | 'status' | 'error' | 'onSendMessage' | 'onCancel' | 'onRetry' | 'chats' | 'activeChat' | 'onSelectChat' | 'onCreateChat' | 'onDeleteChat' | 'onDeleteAllChats' | 'showHistory' | 'showNewChat';
7
+ /**
8
+ * Props for AIStudioChat component
9
+ */
10
+ export interface AIStudioChatProps extends Omit<ChatContainerProps, ManagedChatContainerProps> {
11
+ /** URL of the OpenAI-compatible streaming API endpoint */
12
+ apiUrl: string;
13
+ /** Initial messages to pre-populate the chat (e.g., for restoring a session) */
14
+ initialMessages?: TChatMessage[];
15
+ /** Enable multi-chat history panel (default: false) */
16
+ showHistory?: boolean;
17
+ /** Show new chat button in header (defaults to the value of showHistory) */
18
+ showNewChat?: boolean;
19
+ /** System prompt sent with every request */
20
+ systemPrompt?: string;
21
+ /** Additional fetch options applied to every API request (e.g., auth headers) */
22
+ requestInit?: Omit<RequestInit, 'body' | 'method' | 'signal'>;
23
+ }
24
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"../../../../../src","sources":["components/pages/AIStudioChat/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {TChatMessage} from '../../../types';\nimport type {ChatContainerProps} from '../ChatContainer';\n\n/**\n * Props managed internally by AIStudioChat that are omitted from ChatContainerProps\n */\ntype ManagedChatContainerProps =\n | 'messages'\n | 'status'\n | 'error'\n | 'onSendMessage'\n | 'onCancel'\n | 'onRetry'\n | 'chats'\n | 'activeChat'\n | 'onSelectChat'\n | 'onCreateChat'\n | 'onDeleteChat'\n | 'onDeleteAllChats'\n | 'showHistory'\n | 'showNewChat';\n\n/**\n * Props for AIStudioChat component\n */\nexport interface AIStudioChatProps extends Omit<ChatContainerProps, ManagedChatContainerProps> {\n /** URL of the OpenAI-compatible streaming API endpoint */\n apiUrl: string;\n\n /** Initial messages to pre-populate the chat (e.g., for restoring a session) */\n initialMessages?: TChatMessage[];\n\n /** Enable multi-chat history panel (default: false) */\n showHistory?: boolean;\n\n /** Show new chat button in header (defaults to the value of showHistory) */\n showNewChat?: boolean;\n\n /** System prompt sent with every request */\n systemPrompt?: string;\n\n /** Additional fetch options applied to every API request (e.g., auth headers) */\n requestInit?: Omit<RequestInit, 'body' | 'method' | 'signal'>;\n}\n"]}
@@ -1 +1,2 @@
1
+ export * from "./AIStudioChat/index.js";
1
2
  export * from "./ChatContainer/index.js";
@@ -1,3 +1,4 @@
1
1
  // Export all pages
2
+ export * from "./AIStudioChat/index.js";
2
3
  export * from "./ChatContainer/index.js";
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../../src","sources":["components/pages/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,yCAAgC","sourcesContent":["// Export all pages\nexport * from './ChatContainer';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"../../../../src","sources":["components/pages/index.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,wCAA+B;AAC/B,yCAAgC","sourcesContent":["// Export all pages\nexport * from './AIStudioChat';\nexport * from './ChatContainer';\n"]}
@@ -1 +1 @@
1
- {"version":"1.15.1","type":"module","sideEffects":["*.css","*.scss"]}
1
+ {"version":"1.15.2-beta.12d894e43bcc0b7b911e62db16634e4e418ff48a.0","type":"module","sideEffects":["*.css","*.scss"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/aikit",
3
- "version": "1.15.1",
3
+ "version": "1.15.2-beta.12d894e43bcc0b7b911e62db16634e4e418ff48a.0",
4
4
  "description": "Gravity UI base kit for building ai assistant chats",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -671,6 +671,7 @@
671
671
  "@types/jest": "^29.5.14",
672
672
  "@types/react": "^18.2.64",
673
673
  "@types/react-dom": "^18.2.21",
674
+ "@types/sanitize-html": "^2.16.1",
674
675
  "@types/uuid": "^11.0.0",
675
676
  "eslint": "^8.57.0",
676
677
  "eslint-plugin-storybook": "^9.0.5",