Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
48ec5fa
WIP OneChat UI
zacharyparikh Jun 4, 2025
9d80f2c
ConversationsService list - return array instead of object
zacharyparikh Jun 6, 2025
ab154d2
Adapt Chat Components to Onechat
zacharyparikh Jun 9, 2025
4be24c4
WIP Add OneChat to Serverless Navigation Tree
zacharyparikh Jun 9, 2025
7ae836f
Remove unused conversations_panel components
zacharyparikh Jun 9, 2025
2bb4f99
Remove unused chat_conversation_progress components
zacharyparikh Jun 9, 2025
d0453c9
[jedr] fix use_kibana hook to include internal services
Jun 10, 2025
5936550
Fix conversation routing
zacharyparikh Jun 10, 2025
b1c22d5
Handle conversation events
zacharyparikh Jun 10, 2025
58df7b1
Remove duplicate OneChat link in navigation tree
zacharyparikh Jun 10, 2025
6c5016f
Add new conversation button
zacharyparikh Jun 11, 2025
f08e338
WIP Use persisted conversation rounds
zacharyparikh Jun 11, 2025
0515651
[jedr] hacky way to show tool calls with result! Please refactor
Jun 11, 2025
0445f30
Revert demo only changes
zacharyparikh Jun 13, 2025
af28709
Modify Onechat Link in Navigation Tree
zacharyparikh Jun 16, 2025
093140b
Fix warnings
zacharyparikh Jun 16, 2025
6c10bf8
Optimistically update conversation round for streamed assistant messa…
zacharyparikh Jun 18, 2025
3f5ba3e
Update query data for new conversation id
zacharyparikh Jun 18, 2025
384c1ff
Merge branch 'main' into onechat/simple-ui
zacharyparikh Jun 20, 2025
7d857a1
Merge remote-tracking branch 'upstream/main' into onechat/simple-ui
zacharyparikh Jun 23, 2025
8e5bc8b
Add feature flag to disable OneChat page
zacharyparikh Jun 23, 2025
4b3a35d
[CI] Auto-commit changed files from 'ts-node .buildkite/pipeline-reso…
kibanamachine Jun 23, 2025
ae6c28b
[CI] Auto-commit changed files from 'node scripts/telemetry_check'
kibanamachine Jun 23, 2025
b0115e4
Merge remote-tracking branch 'upstream/main' into onechat/simple-ui
zacharyparikh Jun 24, 2025
2b80594
Add instructions to enable Chat UI to README
zacharyparikh Jun 24, 2025
d4cbb4e
Addressing Pierre feedback
zacharyparikh Jun 24, 2025
90a5193
Remove unused query keys
zacharyparikh Jun 24, 2025
37f117b
[CI] Auto-commit changed files from 'ts-node .buildkite/pipeline-reso…
kibanamachine Jun 24, 2025
5cfd707
Fix newConversationLabel property access in chat_header.tsx
zacharyparikh Jun 25, 2025
1a7a368
Fix i18n ids in server/plugin.ts
zacharyparikh Jun 25, 2025
1f2b5ec
Register onechat:chat:enabled ui setting
zacharyparikh Jun 25, 2025
8fb41d1
Remove "onechat" segment from public URL path
zacharyparikh Jun 25, 2025
d4c6b88
Merge remote-tracking branch 'upstream/main' into onechat/simple-ui
zacharyparikh Jun 25, 2025
d144a5a
Align plugin setup with #224330
zacharyparikh Jun 25, 2025
fed1912
[CI] Auto-commit changed files from 'ts-node .buildkite/pipeline-reso…
kibanamachine Jun 25, 2025
4a4ab48
Merge remote-tracking branch 'upstream/main' into onechat/simple-ui
zacharyparikh Jun 26, 2025
60213aa
Remove initial message context
zacharyparikh Jun 26, 2025
3b09815
organize imports mount.tsx
zacharyparikh Jun 26, 2025
0aa5890
Remove unused onechat:chat:enabled feature flag
zacharyparikh Jun 26, 2025
a5d6af1
Use "conversations" naming rather than "chat"
zacharyparikh Jun 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
*/

import type { StructuredToolIdentifier } from '../tools/tools';
import type { SerializedAgentIdentifier } from '../agents';
import {
OneChatDefaultAgentId,
toSerializedAgentIdentifier,
type SerializedAgentIdentifier,
OneChatDefaultAgentProviderId,
} from '../agents';
import type { UserIdAndName } from '../base/users';

/**
Expand Down Expand Up @@ -64,6 +69,13 @@ export type ToolCallStep = ConversationRoundStepMixin<
ToolCallWithResult
>;

export const createToolCallStep = (toolCallWithResult: ToolCallWithResult): ToolCallStep => {
return {
type: ConversationRoundStepType.toolCall,
...toolCallWithResult,
};
};

export const isToolCallStep = (step: ConversationRoundStep): step is ToolCallStep => {
return step.type === ConversationRoundStepType.toolCall;
};
Expand Down Expand Up @@ -93,3 +105,19 @@ export interface Conversation {
updatedAt: string;
rounds: ConversationRound[];
}

export const createEmptyConversation = (): Conversation => {
const now = new Date().toISOString();
return {
id: 'new',
agentId: toSerializedAgentIdentifier({
agentId: OneChatDefaultAgentId,
providerId: OneChatDefaultAgentProviderId,
}),
user: { id: '', username: '' },
title: '',
createdAt: now,
updatedAt: now,
rounds: [],
};
};
8 changes: 8 additions & 0 deletions x-pack/platform/plugins/shared/onechat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,11 @@ Configure Claude Desktop by adding this to its configuration:
}
}
```

## Chat UI
To enable the Chat UI located at `/app/chat/`, add the following to your Kibana config:

```yaml
uiSettings.overrides:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for starting this!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem, it was Oren's suggestion

onechat:ui:enabled: true
```
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export type ConversationUpdateRequest = Pick<Conversation, 'id'> &
export interface ConversationListOptions {
agentId?: AgentIdentifier;
}

export interface ConversationGetOptions {
conversationId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useCallback, useRef, useEffect } from 'react';
import { css } from '@emotion/css';
import { EuiFlexItem, EuiPanel, useEuiTheme, euiScrollBarStyles } from '@elastic/eui';
import { useChat } from '../../hooks/use_chat';
import { useConversation } from '../../hooks/use_conversation';
import { useStickToBottom } from '../../hooks/use_stick_to_bottom';
import { ConversationInputForm } from './conversation_input_form';
import { ConversationRounds } from './conversation_rounds/conversation_rounds';
import { NewConversationPrompt } from './new_conversation_prompt';

const fullHeightClassName = css`
height: 100%;
`;

const conversationPanelClass = css`
min-height: 100%;
max-width: 850px;
Copy link
Copy Markdown
Member

@joemcelroy joemcelroy Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we try not to hardcode widths here. You should be able to reach the euiTheme via hook and get the spacing values.

max-width: calc(${euiTheme.size.xl} * 10);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Styling will likely be changing anyways in follow up

margin-left: auto;
margin-right: auto;
`;

const scrollContainerClassName = (scrollBarStyles: string) => css`
overflow-y: auto;
${scrollBarStyles}
`;

interface ConversationProps {
agentId: string;
conversationId: string | undefined;
}

export const Conversation: React.FC<ConversationProps> = ({ agentId, conversationId }) => {
const { conversation } = useConversation({ conversationId });
const { sendMessage } = useChat({
conversationId,
agentId,
});

const theme = useEuiTheme();
const scrollBarStyles = euiScrollBarStyles(theme);

const scrollContainerRef = useRef<HTMLDivElement | null>(null);

const { setStickToBottom } = useStickToBottom({
defaultState: true,
scrollContainer: scrollContainerRef.current,
});

useEffect(() => {
setStickToBottom(true);
}, [conversationId, setStickToBottom]);

const onSubmit = useCallback(
(message: string) => {
setStickToBottom(true);
sendMessage(message);
},
[sendMessage, setStickToBottom]
);

if (!conversationId && (!conversation || conversation.rounds.length === 0)) {
return <NewConversationPrompt onSubmit={onSubmit} />;
}

return (
<>
<EuiFlexItem grow className={scrollContainerClassName(scrollBarStyles)}>
<div ref={scrollContainerRef} className={fullHeightClassName}>
<EuiPanel hasBorder={false} hasShadow={false} className={conversationPanelClass}>
<ConversationRounds conversationRounds={conversation?.rounds ?? []} />
</EuiPanel>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ConversationInputForm disabled={!agentId} loading={false} onSubmit={onSubmit} />
</EuiFlexItem>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { css } from '@emotion/css';
import {
EuiTitle,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiSkeletonTitle,
useEuiTheme,
useEuiFontSize,
} from '@elastic/eui';
import { useConversation } from '../../hooks/use_conversation';
import { chatCommonLabels } from './i18n';

interface ConversationHeaderProps {
conversationId: string | undefined;
}

export const ConversationHeader: React.FC<ConversationHeaderProps> = ({ conversationId }) => {
const { conversation, isLoading: isConvLoading } = useConversation({ conversationId });

const { euiTheme } = useEuiTheme();

const containerClass = css`
padding: ${euiTheme.size.s} ${euiTheme.size.m};
border-bottom: solid ${euiTheme.border.width.thin} ${euiTheme.border.color};
`;

const conversationTitleClass = css`
font-weight: ${euiTheme.font.weight.semiBold};
font-size: ${useEuiFontSize('m').fontSize};
`;

return (
<EuiFlexItem grow={false}>
<EuiPanel
hasBorder={false}
hasShadow={false}
borderRadius="none"
color="subdued"
className={containerClass}
>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow>
<EuiTitle>
<EuiSkeletonTitle size="m" isLoading={conversationId !== undefined && isConvLoading}>
<h3 className={conversationTitleClass}>
{conversation?.title || chatCommonLabels.chat.conversations.newConversationLabel}
</h3>
</EuiSkeletonTitle>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useCallback, useState, KeyboardEvent } from 'react';
import { css } from '@emotion/css';
import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiTextArea,
keys,
useEuiTheme,
} from '@elastic/eui';
import { chatCommonLabels } from './i18n';

interface ConversationInputFormProps {
disabled: boolean;
loading: boolean;
onSubmit: (message: string) => void;
}

export const ConversationInputForm: React.FC<ConversationInputFormProps> = ({
disabled,
loading,
onSubmit,
}) => {
const [message, setMessage] = useState<string>('');
const { euiTheme } = useEuiTheme();

const handleSubmit = useCallback(() => {
if (loading || !message.trim()) {
return;
}

onSubmit(message);
setMessage('');
}, [message, loading, onSubmit]);

const handleChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
setMessage(event.currentTarget.value);
}, []);

const handleTextAreaKeyDown = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => {
if (!event.shiftKey && event.key === keys.ENTER) {
event.preventDefault();
handleSubmit();
}
},
[handleSubmit]
);

const topContainerClass = css`
padding-bottom: ${euiTheme.size.m};
`;

const inputFlexItemClass = css`
max-width: 900px;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here with hardcoded width

`;

return (
<EuiFlexGroup
gutterSize="s"
responsive={false}
alignItems="center"
justifyContent="center"
className={topContainerClass}
>
<EuiFlexItem className={inputFlexItemClass}>
<EuiTextArea
data-test-subj="onechatAppConversationInputFormTextArea"
fullWidth
rows={1}
value={message}
onChange={handleChange}
onKeyDown={handleTextAreaKeyDown}
placeholder={chatCommonLabels.userInputBox.placeholder}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label="Submit"
data-test-subj="onechatAppConversationInputFormSubmitButton"
iconType="kqlFunction"
display="fill"
size="m"
onClick={handleSubmit}
disabled={loading || disabled}
isLoading={loading}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
};
Loading