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

enhance(console): support llm chat page #3016

Merged
merged 1 commit into from
Nov 24, 2023
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
61 changes: 0 additions & 61 deletions console/client/QuestionAnswering.tsx

This file was deleted.

6 changes: 3 additions & 3 deletions console/client/ServingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import axios from 'axios'
import { useQuery } from 'react-query'
import { Select } from '@starwhale/ui'
import _ from 'lodash'
import QuestionAnswering from './QuestionAnswering'
import { IApiSchema, InferenceType, ISpecSchema } from './schemas'
import LLMChat from './pages/llm/LLMChat'
import { IApiSchema, InferenceType, ISpecSchema } from './schemas/api'

const fetchSpec = async () => {
const { data } = await axios.get<ISpecSchema>('/api/spec')
Expand Down Expand Up @@ -44,7 +44,7 @@ export default function ServingPage() {
}}
/>
)}
{currentApi?.inference_type === InferenceType.QUESTION_ANSWERING && <QuestionAnswering api={currentApi} />}
{currentApi?.inference_type === InferenceType.LLM_CHAT && <LLMChat api={currentApi} />}
</div>
)
}
41 changes: 41 additions & 0 deletions console/client/components/ChatHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import { IBackEndMessage } from '../schemas/chat'
import { IconTypesT } from '@starwhale/ui/IconFont'
import { IconFont } from '@starwhale/ui'

export interface IChatHistoryProps {
history: IBackEndMessage[]
}

const Icon = ({ type }: { type: IconTypesT }) => (
<div style={{ paddingRight: '20px' }}>
<IconFont type={type} size={18} />
</div>
)

const ChatMessage = ({ role, content }: IBackEndMessage) => {
return (
<div className={`flex flex-col ${role === 'bot' ? 'items-start' : 'items-end'}`}>
<div
className={`flex items-center ${
role === 'bot' ? 'bg-neutral-200 text-neutral-900' : 'bg-blue-200'
} rounded-xl px-3 py-2 max-w-[80%]`}
style={{ overflowWrap: 'anywhere' }}
>
<Icon type={role === 'bot' ? 'blog' : 'user'} />
{content}
</div>
</div>
)
}
export const ChatHistory = ({ history }: IChatHistoryProps) => {
return (
<div className='mx-auto space-y-5 md:space-y-10 px-3 pt-5 md:pt-12 w-[100%]'>
<div className='flex flex-col space-y-5'>
{history.map((i) => (
<ChatMessage key={i.content} {...i} />
))}
</div>
</div>
)
}
30 changes: 30 additions & 0 deletions console/client/components/ChatInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, { useRef } from 'react'

export interface IChatInputProps {
onSubmit: (message: string) => void
}

export const ChatInput = ({ onSubmit }: IChatInputProps) => {
const textareaRef = useRef<HTMLTextAreaElement>(null)

const handleKeyUp = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
e.preventDefault()
onSubmit(textareaRef.current?.value ?? '')
textareaRef.current!.value = ''
}
}

return (
<div className='relative'>
<textarea
ref={textareaRef}
className='min-h-[44px] rounded-lg pl-4 pr-12 py-2 w-full focus:outline-none focus:ring-1 focus:ring-neutral-300 border-2 border-neutral-200'
style={{ resize: 'none' }}
placeholder='Type a message...'
rows={1}
onKeyUp={handleKeyUp}
/>
</div>
)
}
20 changes: 20 additions & 0 deletions console/client/components/MaxNewTokens.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { Slider } from './Slider'

interface IMaxLenProps {
onChange: (num: number) => void
}

export const MaxNewTokens = ({ onChange }: IMaxLenProps) => {
return (
<Slider
label='Max new tokens'
onChange={onChange}
description='The maximum number of tokens to generate. The higher this value, the longer the text that will be generated.'
defaultValue={256}
max={1024}
min={0}
step={1}
/>
)
}
55 changes: 55 additions & 0 deletions console/client/components/Slider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { useCallback, useState } from 'react'

export interface ISliderProps {
label: string
description?: string
defaultValue?: number
step?: number
max?: number
min?: number
onChange: (value: number) => void
}

export const Slider = ({
label,
description,
defaultValue = 0,
step = 0.1,
max = 1,
min = 0,
onChange,
}: ISliderProps) => {
const [value, setValue] = useState(defaultValue)
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = parseFloat(event.target.value)
setValue(newValue)
onChange(newValue)
}

const formatValue = useCallback(
(v: number) => {
const stepStr = step.toString()
const stepDecimal = stepStr.split('.')[1]
const stepDecimalLength = stepDecimal ? stepDecimal.length : 0
return Number.isInteger(v) ? v : v.toFixed(stepDecimalLength)
},
[step]
)

return (
<div className='flex flex-col w-100% mb-8'>
<label className='mb-2 text-left text-neutral-700 dark:text-neutral-400'>{label}</label>
<span className='text-[12px] text-black/50 dark:text-white/50 text-sm'>{description}</span>
<span className='mt-2 mb-1 text-center text-neutral-900 dark:text-neutral-100'>{formatValue(value)}</span>
<input
className='cursor-pointer'
type='range'
min={min}
max={max}
step={step}
value={value}
onChange={handleChange}
/>
</div>
)
}
17 changes: 17 additions & 0 deletions console/client/components/Temperature.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import { ISliderProps, Slider } from './Slider'

interface ITemperatureProps {
onChangeTemperature: (temperature: number) => void
}

export const TemperatureSlider = ({ onChangeTemperature }: ITemperatureProps) => {
const props: ISliderProps = {
label: 'Temperature',
description:
'Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.',
defaultValue: 0.5,
onChange: onChangeTemperature,
}
return <Slider {...props} />
}
20 changes: 20 additions & 0 deletions console/client/components/TopK.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { Slider } from './Slider'

interface ITopKProps {
onChange: (num: number) => void
}

export const TopK = ({ onChange }: ITopKProps) => {
return (
<Slider
label='Top K'
onChange={onChange}
description='The top-k parameter limits the model’s predictions to the top k most probable tokens at each step of generation'
defaultValue={1}
max={100}
min={0}
step={1}
/>
)
}
20 changes: 20 additions & 0 deletions console/client/components/TopP.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { Slider } from './Slider'

interface ITopPProps {
onChange: (num: number) => void
}

export const TopP = ({ onChange }: ITopPProps) => {
return (
<Slider
label='Top P'
onChange={onChange}
description='The top-p parameter limits the model’s predictions such that the cumulative probability of the tokens generated is always less than p'
defaultValue={0.8}
max={1}
min={0}
step={0.01}
/>
)
}
2 changes: 2 additions & 0 deletions console/client/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import ReactDOM from 'react-dom/client'
import React from 'react'
import App from './App'
import '@starwhale/ui/Markdown/markdown.css'
import '@unocss/reset/tailwind.css'
import 'virtual:uno.css'

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
Expand Down
Loading
Loading