@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.
- package/build/cjs/components/organisms/ThinkingMessage/index.d.ts +13 -1
- package/build/cjs/components/organisms/ThinkingMessage/index.js +7 -2
- package/build/cjs/components/organisms/ThinkingMessage/index.js.map +1 -1
- package/build/cjs/components/pages/AIStudioChat/AIStudioChat.d.ts +11 -0
- package/build/cjs/components/pages/AIStudioChat/AIStudioChat.js +216 -0
- package/build/cjs/components/pages/AIStudioChat/AIStudioChat.js.map +1 -0
- package/build/cjs/components/pages/AIStudioChat/index.d.ts +2 -0
- package/build/cjs/components/pages/AIStudioChat/index.js +6 -0
- package/build/cjs/components/pages/AIStudioChat/index.js.map +1 -0
- package/build/cjs/components/pages/AIStudioChat/types.d.ts +24 -0
- package/build/cjs/components/pages/AIStudioChat/types.js +3 -0
- package/build/cjs/components/pages/AIStudioChat/types.js.map +1 -0
- package/build/cjs/components/pages/index.d.ts +1 -0
- package/build/cjs/components/pages/index.js +1 -0
- package/build/cjs/components/pages/index.js.map +1 -1
- package/build/cjs/package.json +1 -1
- package/build/esm/components/organisms/ThinkingMessage/index.d.ts +13 -1
- package/build/esm/components/organisms/ThinkingMessage/index.js +7 -2
- package/build/esm/components/organisms/ThinkingMessage/index.js.map +1 -1
- package/build/esm/components/pages/AIStudioChat/AIStudioChat.d.ts +11 -0
- package/build/esm/components/pages/AIStudioChat/AIStudioChat.js +213 -0
- package/build/esm/components/pages/AIStudioChat/AIStudioChat.js.map +1 -0
- package/build/esm/components/pages/AIStudioChat/index.d.ts +2 -0
- package/build/esm/components/pages/AIStudioChat/index.js +2 -0
- package/build/esm/components/pages/AIStudioChat/index.js.map +1 -0
- package/build/esm/components/pages/AIStudioChat/types.d.ts +24 -0
- package/build/esm/components/pages/AIStudioChat/types.js +2 -0
- package/build/esm/components/pages/AIStudioChat/types.js.map +1 -0
- package/build/esm/components/pages/index.d.ts +1 -0
- package/build/esm/components/pages/index.js +1 -0
- package/build/esm/components/pages/index.js.map +1 -1
- package/build/esm/package.json +1 -1
- 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;;;;;;
|
|
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,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 @@
|
|
|
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"]}
|
|
@@ -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"]}
|
package/build/cjs/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.15.
|
|
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;;;
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"]}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.15.
|
|
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.
|
|
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",
|