diff --git a/plugins/docuservix/pages/chat/ChatPage.tsx b/plugins/docuservix/pages/chat/ChatPage.tsx
index 3bd719b..9f9131c 100644
--- a/plugins/docuservix/pages/chat/ChatPage.tsx
+++ b/plugins/docuservix/pages/chat/ChatPage.tsx
@@ -1,10 +1,32 @@
import Layout from '@theme/Layout';
import { ReactNode } from 'react';
+import { IChat } from '@docuservix/models/chat';
+import { Chat } from '@docuservix/widgets/chat';
+
+const dialog: IChat = {
+ messages: [
+ {
+ role: 'user',
+ content: 'Can you show me some CSS animations? It can be simple tools like chatbots...',
+ },
+ {
+ role: 'assistant',
+ content: "Hello! I'm your AI assistant. How can I help you today?",
+ },
+ ],
+};
+
export function ChatPage(): ReactNode {
return (
- Hello world!
+
+
+
);
}
diff --git a/plugins/docuservix/widgets/chat/Chat.module.css b/plugins/docuservix/widgets/chat/Chat.module.css
new file mode 100644
index 0000000..1ff1edd
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Chat.module.css
@@ -0,0 +1,24 @@
+.Chat {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ background: var(--ifm-background-color);
+ border: 1px solid var(--ifm-color-emphasis-200);
+ border-radius: var(--ifm-global-radius);
+ overflow: hidden;
+}
+
+.Chat__statusMessage {
+ font-size: .65rem;
+ color: #666;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: .75rem 2.5rem;
+ background: #eee;
+}
+
+.Chat__statusMessage i {
+ margin-right: .25rem;
+ color: #667eea;
+}
diff --git a/plugins/docuservix/widgets/chat/Chat.tsx b/plugins/docuservix/widgets/chat/Chat.tsx
new file mode 100644
index 0000000..f6b8599
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Chat.tsx
@@ -0,0 +1,37 @@
+import block from 'bem-css-modules';
+import React, { ReactNode } from 'react';
+
+import { IChat } from '@docuservix/models/chat';
+
+import styles from './Chat.module.css';
+import { Header } from './Header';
+import { Input } from './Input';
+import { Messages } from './Messages';
+
+const b = block(styles, 'Chat');
+
+interface ChatProps {
+ dialog: IChat;
+ typing?: boolean;
+ statusMessage?: string;
+ onSend?: (text: string) => void;
+}
+
+export function Chat({ dialog, typing, statusMessage, onSend }: ChatProps): ReactNode {
+ const { messages } = dialog;
+
+ return (
+
+
+
+ {statusMessage &&
{statusMessage}
}
+
+
+ );
+}
diff --git a/plugins/docuservix/widgets/chat/Header.module.css b/plugins/docuservix/widgets/chat/Header.module.css
new file mode 100644
index 0000000..e48bbde
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Header.module.css
@@ -0,0 +1,32 @@
+.Header {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem 1.25rem;
+ border-bottom: 1px solid var(--ifm-color-emphasis-200);
+}
+
+.Header__avatar {
+ width: 50px;
+ height: 50px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-size: 1.5rem;
+ flex-shrink: 0;
+}
+
+.Header__info h3 {
+ margin: 0 0 0.25rem;
+ color: var(--ifm-font-color-base);
+ font-size: 1.125rem;
+}
+
+.Header__info p {
+ margin: 0;
+ color: var(--ifm-color-emphasis-600);
+ font-size: 0.85rem;
+}
diff --git a/plugins/docuservix/widgets/chat/Header.tsx b/plugins/docuservix/widgets/chat/Header.tsx
new file mode 100644
index 0000000..b438758
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Header.tsx
@@ -0,0 +1,21 @@
+import block from 'bem-css-modules';
+import React, { ReactNode } from 'react';
+
+import styles from './Header.module.css';
+import { RobotIcon } from './icons';
+
+const b = block(styles, 'Header');
+
+export function Header(): ReactNode {
+ return (
+
+
+
+
+
+
AI Assistant
+
Ready to help
+
+
+ );
+}
diff --git a/plugins/docuservix/widgets/chat/Input.module.css b/plugins/docuservix/widgets/chat/Input.module.css
new file mode 100644
index 0000000..2ab1f43
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Input.module.css
@@ -0,0 +1,58 @@
+.Input {
+ display: flex;
+ align-items: flex-end;
+ gap: 0.75rem;
+ padding: 1rem 1.25rem;
+ border-top: 1px solid var(--ifm-color-emphasis-200);
+}
+
+.Input__field {
+ flex: 1;
+ padding: 0.75rem 1rem;
+ border: 2px solid var(--ifm-color-emphasis-200);
+ border-radius: 1.5rem;
+ font-size: 1rem;
+ font-family: inherit;
+ color: var(--ifm-font-color-base);
+ background: var(--ifm-background-surface-color);
+ outline: none;
+ transition: border-color 0.3s;
+ resize: none;
+ min-height: 3.25rem;
+ max-height: 8rem;
+ field-sizing: content;
+}
+
+.Input__field:focus {
+ border-color: #667eea;
+}
+
+.Input__field:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.Input__send {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 3.25rem;
+ height: 3.25rem;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border: none;
+ border-radius: 50%;
+ color: white;
+ font-size: 1.125rem;
+ cursor: pointer;
+ flex-shrink: 0;
+ transition: transform 0.3s;
+}
+
+.Input__send:hover:not(:disabled) {
+ transform: scale(1.1);
+}
+
+.Input__send:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
diff --git a/plugins/docuservix/widgets/chat/Input.tsx b/plugins/docuservix/widgets/chat/Input.tsx
new file mode 100644
index 0000000..7e21347
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Input.tsx
@@ -0,0 +1,55 @@
+import block from 'bem-css-modules';
+import React, { ReactNode, useState } from 'react';
+
+import { PaperPlaneIcon } from './icons';
+import styles from './Input.module.css';
+
+const b = block(styles, 'Input');
+
+interface InputProps {
+ loading?: boolean;
+ onSend?: (text: string) => void;
+}
+
+export function Input({ loading, onSend }: InputProps): ReactNode {
+ const [input, setInput] = useState('');
+
+ const handleSend = () => {
+ const text = input.trim();
+
+ if (!text || loading) {
+ return;
+ }
+
+ setInput('');
+ onSend?.(text);
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSend();
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/plugins/docuservix/widgets/chat/Message.module.css b/plugins/docuservix/widgets/chat/Message.module.css
new file mode 100644
index 0000000..151a444
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Message.module.css
@@ -0,0 +1,41 @@
+.Message {
+ max-width: 85%;
+ animation: slideIn 0.3s ease-out;
+}
+
+@keyframes slideIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.Message_role_assistant {
+ align-self: flex-start;
+}
+
+.Message_role_user {
+ align-self: flex-end;
+}
+
+.Message__content {
+ padding: 0.75rem 1rem;
+ border-radius: 1.25rem;
+ line-height: 1.5;
+}
+
+.Message_role_assistant .Message__content {
+ background: var(--ifm-color-emphasis-100);
+ color: var(--ifm-font-color-base);
+ border-top-left-radius: 0.25rem;
+}
+
+.Message_role_user .Message__content {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: #fff;
+ border-bottom-right-radius: 0.25rem;
+}
+
+@media (min-width: 576px) {
+ .Message {
+ max-width: 75%;
+ }
+}
diff --git a/plugins/docuservix/widgets/chat/Message.tsx b/plugins/docuservix/widgets/chat/Message.tsx
new file mode 100644
index 0000000..9bdcd1a
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Message.tsx
@@ -0,0 +1,19 @@
+import block from 'bem-css-modules';
+import React, { ReactNode } from 'react';
+
+import styles from './Message.module.css';
+
+const b = block(styles, 'Message');
+
+interface MessageProps {
+ role: 'user' | 'assistant';
+ content: string;
+}
+
+export function Message({ role, content }: MessageProps): ReactNode {
+ return (
+
+ );
+}
diff --git a/plugins/docuservix/widgets/chat/Messages.module.css b/plugins/docuservix/widgets/chat/Messages.module.css
new file mode 100644
index 0000000..1a2ad88
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Messages.module.css
@@ -0,0 +1,65 @@
+.Messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 1rem 1.25rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+/* Typing indicator */
+.Messages__typing {
+ display: flex;
+ align-items: center;
+ align-self: flex-start;
+}
+
+.Messages__typingIndicator {
+ display: flex;
+ gap: 0.25rem;
+ padding: 0.75rem 1rem;
+ background: var(--ifm-color-emphasis-100);
+ border-radius: 1.25rem;
+ border-top-left-radius: 0.25rem;
+}
+
+.Messages__typingIndicator span {
+ width: 0.5rem;
+ height: 0.5rem;
+ background: var(--ifm-color-emphasis-500);
+ border-radius: 50%;
+ animation: bounce 1.4s infinite ease-in-out;
+}
+
+.Messages__typingIndicator span:nth-child(1) { animation-delay: -0.32s; }
+.Messages__typingIndicator span:nth-child(2) { animation-delay: -0.16s; }
+
+@keyframes bounce {
+ 0%, 80%, 100% { transform: scale(0); }
+ 40% { transform: scale(1); }
+}
+
+/* Scrollbar */
+.Messages::-webkit-scrollbar {
+ width: 6px;
+}
+
+.Messages::-webkit-scrollbar-track {
+ background: transparent;
+ border-radius: 3px;
+}
+
+.Messages::-webkit-scrollbar-thumb {
+ background: var(--ifm-color-emphasis-300);
+ border-radius: 3px;
+}
+
+.Messages::-webkit-scrollbar-thumb:hover {
+ background: var(--ifm-color-emphasis-400);
+}
+
+@media (min-width: 576px) {
+ .Messages {
+ padding: 1.5rem;
+ }
+}
diff --git a/plugins/docuservix/widgets/chat/Messages.tsx b/plugins/docuservix/widgets/chat/Messages.tsx
new file mode 100644
index 0000000..776b728
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/Messages.tsx
@@ -0,0 +1,38 @@
+import block from 'bem-css-modules';
+import React, { ReactNode } from 'react';
+
+import { IChatMessage } from '@docuservix/models/chat';
+
+import { Message } from './Message';
+import styles from './Messages.module.css';
+
+const b = block(styles, 'Messages');
+
+interface MessagesProps {
+ messages: IChatMessage[];
+ typing?: boolean;
+}
+
+export function Messages({ messages, typing }: MessagesProps): ReactNode {
+ return (
+
+ {messages.map((msg, i) => (
+
+ ))}
+
+ {typing && (
+
+ )}
+
+ );
+}
diff --git a/plugins/docuservix/widgets/chat/icons.tsx b/plugins/docuservix/widgets/chat/icons.tsx
new file mode 100644
index 0000000..b977f2e
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/icons.tsx
@@ -0,0 +1,30 @@
+import React, { ReactNode } from 'react';
+
+export function RobotIcon(): ReactNode {
+ return (
+
+ );
+}
+
+export function PaperPlaneIcon(): ReactNode {
+ return (
+
+ );
+}
diff --git a/plugins/docuservix/widgets/chat/index.ts b/plugins/docuservix/widgets/chat/index.ts
new file mode 100644
index 0000000..eeadfe5
--- /dev/null
+++ b/plugins/docuservix/widgets/chat/index.ts
@@ -0,0 +1 @@
+export { Chat } from './Chat';