Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: chat ui3 #116

Merged
merged 2 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 8 additions & 8 deletions components/app/header/Header.less
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

&__dropdown-item.@{left}__dropdown-item.@{left}__dropdown-item {
&:hover {
background-color: rgba(67, 136, 221, 0.40);
background-color: var(--sq-primary-card-hover);
}
}
}
Expand All @@ -46,10 +46,10 @@
color: #fff;

&:hover {
color: var(--sq-blue600);
color: var(--sq-primary-link-hover);
}
&--active {
color: var(--sq-blue600);
color: var(--sq-primary-link-hover);
}
}
}
Expand All @@ -71,7 +71,7 @@
border-radius: 0;
&--dark {
&:hover {
background-color: rgba(67, 136, 221, 0.40);
background-color: var(--sq-primary-card-hover);
}
}
}
Expand Down Expand Up @@ -116,11 +116,11 @@

&:hover {
text-decoration: underline;
color: var(--sq-blue600);
color: var(--sq-primary-link-hover);
}

&--active {
color: var(--sq-blue600);
color: var(--sq-primary-link-hover);
}
}

Expand Down Expand Up @@ -262,11 +262,11 @@
color: #fff;
&:hover {
text-decoration: underline;
color: var(--sq-blue600);
color: var(--sq-primary);
}

&--active {
color: var(--sq-blue600);
color: var(--sq-primary);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion components/app/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const LeftHeader = ({ leftElement, dropdownLinks, isMobile }: LeftHeaderProps) =
<div className={clsx(bem(), theme === 'dark' ? bem({ dark: 'dark' }) : '')} id="leftHeader">
<Dropdown
label={dropdownLinks.label}
LeftLabelIcon={<img src="https://static.subquery.network/design/images/appIcon.svg" alt="SubQuery Apps" />}
LeftLabelIcon={<img src="https://static.subquery.network/design/images/app-icon.svg" alt="SubQuery Apps" />}
menuitem={dropdownLinks.links.map((label, key) => ({
key,
label: <MenuWithDesc title={label.label} description={label.description} width={isMobile ? '100%' : 366} />,
Expand Down
19 changes: 13 additions & 6 deletions components/common/chatUi/chatUi.less
Original file line number Diff line number Diff line change
Expand Up @@ -178,19 +178,26 @@
align-items: center;
gap: 12px;
color: rgb(209, 213, 219);

.subql-markdown-preview {
p {
margin-top: 0;
}
}
}

&__assistant {
background: rgb(68,70,84);
position: relative;
&--lastOne&--loading {
.subql-chat-conversation-message__item-span {
&::after {
content: "▍";
align-self: flex-end;
margin-left: 8px;
animation: pulseCursor 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;

.subql-markdown-preview {
&::after {
content: "▍";
align-self: flex-end;
margin-left: 8px;
animation: pulseCursor 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
}
}
}
Expand Down
34 changes: 23 additions & 11 deletions components/common/chatUi/chatUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Typography } from '../typography';
import { v4 as uuidv4 } from 'uuid';
import { cloneDeep, isString } from 'lodash-es';
import Address from '../address';
import Markdown from '../markdown/Markdown';

const indexerName: { [key in string]: string } = {
'0xd0af1919af890cfdd8d12be5cf1b1421224fc29a': 'Mainnet Operator',
Expand All @@ -31,6 +32,7 @@ export interface ChatUiProps {
placeholder?: React.ReactNode;
width?: number;
height?: number;
model?: string;
}

export type AiMessageType = 'text' | 'image_url';
Expand Down Expand Up @@ -127,7 +129,9 @@ export const ConversationMessage = forwardRef<
bem('item'),
bem(message.role, {
[answerStatus]:
index === property.messages.length - 1 && message.role === 'assistant' ? true : undefined,
index === property.messages.length - 1 && message.role === 'assistant' && !message?.content?.length
? true
: undefined,
lastOne: index === property.messages.length - 1 && message.role === 'assistant' ? true : undefined,
}),
)}
Expand All @@ -142,7 +146,9 @@ export const ConversationMessage = forwardRef<
></RiRobot2Line>
)}
{/* TODO: support array */}
<span className={clsx(bem('item-span'))}>{isString(message.content) ? message.content : ''}</span>
<div className={clsx(bem('item-span'))}>
{isString(message.content) ? <Markdown.Preview>{message.content}</Markdown.Preview> : ''}
</div>
</div>
);
})}
Expand All @@ -155,23 +161,24 @@ ConversationMessage.displayName = 'ConversationMessage';
// maybe later support custom workspace name
const workspaceName = 'subql-chat-workspace';

export const chatWithStream = async (url: string, body: { messages: Message[] }) => {
export const chatWithStream = async (url: string, body: { messages: Message[]; model?: string }) => {
const { model = 'gemma2' } = body;
const res = await fetch(url, {
headers: {
accept: 'text/event-stream',
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify({
model: 'gemma2',
model,
messages: body.messages,
stream: true,
}),
});
return res;
};

export const ChatUi: FC<ChatUiProps> = ({ chatUrl, prompt, className, placeholder, width, height }) => {
export const ChatUi: FC<ChatUiProps> = ({ chatUrl, prompt, className, placeholder, width, height, model }) => {
const bem = useBem('subql-chat');
const [chats, setChats] = React.useState<ConversationItemProps['property'][]>([]);
const [currentChat, setCurrentChat] = useState<ConversationItemProps['property']>();
Expand Down Expand Up @@ -206,7 +213,7 @@ export const ChatUi: FC<ChatUiProps> = ({ chatUrl, prompt, className, placeholde
name: 'New Conversation',
chatUrl,
messages: [],
prompt: prompt || '',
prompt: '',
};

oldWorkspace.unshift(chat);
Expand Down Expand Up @@ -284,6 +291,7 @@ export const ChatUi: FC<ChatUiProps> = ({ chatUrl, prompt, className, placeholde
name: curChat.messages.length ? curChat.name : currentInput.slice(0, 40),
};
newChat.chatUrl = newChat.messages.length - 1 > 0 ? newChat.chatUrl : chatUrl;
newChat.prompt = newChat.prompt || prompt || '';

const newChats = cloneDeep(curChats).map((chat) => {
if (chat.id === curChat.id) {
Expand All @@ -305,10 +313,12 @@ export const ChatUi: FC<ChatUiProps> = ({ chatUrl, prompt, className, placeholde
};

await pushNewMsgToChat(newChat, robotAnswer, curChat, curChats);

// set user's message first, then get the response
const res = await chatWithStream(newChat.chatUrl, {
messages: prompt ? [{ role: 'system' as const, content: prompt }, ...newChat.messages] : newChat.messages,
messages: newChat.prompt
? [{ role: 'system' as const, content: newChat.prompt }, ...newChat.messages]
: newChat.messages,
model,
});

if (res.status === 200 && res.body) {
Expand All @@ -326,10 +336,9 @@ export const ChatUi: FC<ChatUiProps> = ({ chatUrl, prompt, className, placeholde

const parts = chunkValue.split('\n\n');
for (const part of parts) {
const partWithHandle = part.startsWith('data: ') ? part.slice(6, part.length).trim() : part;
if (!part.startsWith('data: ')) {
if (invalidJson) {
try {
invalidJson += partWithHandle;
invalidJson += part;
const parsed: { choices: { delta: { content: string } }[] } = JSON.parse(invalidJson);
robotAnswer.content += parsed?.choices?.[0]?.delta?.content;

Expand All @@ -341,6 +350,8 @@ export const ChatUi: FC<ChatUiProps> = ({ chatUrl, prompt, className, placeholde
continue;
}

const partWithHandle = part.startsWith('data: ') ? part.slice(6, part.length).trim() : part;

if (partWithHandle) {
try {
const parsed: { choices: { delta: { content: string } }[] } = JSON.parse(partWithHandle);
Expand All @@ -361,6 +372,7 @@ export const ChatUi: FC<ChatUiProps> = ({ chatUrl, prompt, className, placeholde

await pushNewMsgToChat(newChat, robotAnswer, curChat, curChats);
} catch (e) {
console.warn('Reach this code', invalidJson);
// to reach this code, it means the response is not valid or the code have something wrong.
}
}
Expand Down
6 changes: 3 additions & 3 deletions components/common/dropdown/Dropdown.less
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
}

&--active {
color: var(--sq-blue600);
color: var(--sq-primary);
.subql-typography {
color: var(--sq-blue600);
color: var(--sq-primary);
}

.subql-dropdown__icon {
color: var(--sq-blue600);
color: var(--sq-primary);
}
}

Expand Down
34 changes: 15 additions & 19 deletions components/styles.css
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
@font-face {
font-family: 'Inter';
src: local('Inter-Regular'), url('https://static.subquery.network/design/fonts/Inter-Regular.ttf') format('truetype');
font-family: 'PPNeue';
src: local('PPNeueMontreal-Bold'),
url('https://static.subquery.network/design/fonts/PPNeueMontreal-Bold.ttf') format('truetype');
font-display: swap;
font-weight: bold;
}

@font-face {
font-family: 'Inter-SemiBold';
src: local('Inter-SemiBold'),
url('https://static.subquery.network/design/fonts/Inter-SemiBold.ttf') format('truetype');
font-display: swap;
}

@font-face {
font-family: 'Inter-Bold';
src: local('Inter-Bold'), url('https://static.subquery.network/design/fonts/Inter-Bold.ttf') format('truetype');
font-display: swap;
}

@font-face {
font-family: 'Futura';
src: url('https://static.subquery.network/design/fonts/Futura-Medium.ttf') format('truetype');
font-family: 'PPNeue';
src: local('PPNeue-Regular'),
url('https://static.subquery.network/design/fonts/PPNeueMontreal-Regular.ttf') format('truetype');
font-display: swap;
}

:root {
--sq-primary: #e968dd;
--sq-primary-card-hover: #e968dd33;
--sq-primary-card-deephover: #e968dd66;
--sq-primary-link-hover: #d25ec7;

--sq-primary-pink: #ff4581;
--sq-primary-blue: #1677ff;
--sq-gradient: linear-gradient(96.26deg, var(--sq-primary-blue) 13.8%, var(--sq-primary-pink) 82.83%);
Expand All @@ -50,6 +45,7 @@
--sq-pink400: #ff6a9a;
--sq-pink500: #ff588e;
--sq-pink600: #ff4581;
--sq-pink700: #e968dd;

/* Gray */
--sq-gray100: #f9fafb;
Expand Down Expand Up @@ -98,8 +94,8 @@
--sq-border-radius: 8px;

/* Family */
--sq-font-family: 'Inter', Arial, sans-serif;
--sq-font-family-header: 'Futura', Arial, sans-serif;
--sq-font-family: 'PPNeue', Arial, sans-serif;
--sq-font-family-header: 'PPNeue', Arial, sans-serif;
}

body {
Expand Down
Loading