chore: Update new model.json with multiple binaries feat: Add updates for handling multiple model binaries chore: jan can see Update Model.json (#1005) * add(mixtral): add model.json for mixtral * archived some models + update the model.json * add(model): add pandora 10.7b * fix(model): update description * fix(model): pump vers and change the featured model to trinity * fix(model): archive neuralchat * fix(model): decapriated all old models * fix(trinity): add cover image and change description * fix(trinity): update cover png * add(pandora): cover image * fix(pandora): cover image * add(mixtral): add model.json for mixtral * archived some models + update the model.json * add(model): add pandora 10.7b * fix(model): update description * fix(model): pump vers and change the featured model to trinity * fix(model): archive neuralchat * fix(model): decapriated all old models * fix(trinity): add cover image and change description * fix(trinity): update cover png * add(pandora): cover image * fix(pandora): cover image * chore: model desc nits * fix(models): adjust the size for solars and pandoras * add(mixtral): description --------- Co-authored-by: 0xSage <n@pragmatic.vc> chore: reformat model.json and use new template fix(Model): download/abort model (#1163) * fix(Model): download/abort model * fix: image preview Signed-off-by: James <james@jan.ai> --------- Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> Co-authored-by: Louis <louis@jan.ai> add preview and reduce time re-render all chat screen Signed-off-by: James <james@jan.ai> store files under thread_id/files Signed-off-by: James <james@jan.ai> fix: Update llava 1.5 size fix: Nitro extension path resolver feat: Add upload preview clearance chore: Update FileType to multiple targets fix: delete file preview once new thread created chore: Add langchain import support storing pdf file Signed-off-by: James <james@jan.ai> feat: add retrieval tool in node runtime fix: import module done Co-authored-by: Louis <louis-jan@users.noreply.github.com> feat: Add type assistant tool chore: Add tool_retrieval_enabled to InferenceEngine chore: Add AssistantTool to thread entity chore: refactor tool retrieval base class feat: Add handler for assistant with rag enabled chore: Update inferenceEngine type properly chore: Update inferenceEngine type properly fix: Update retrieval tool chore: main entry correction refactor: tsconfig files chore: Update ModelRuntimeParams type refactor: Remove unused files fix: wip chore: remove unused console.log for FileUploadPreview fix: Update mapping correctly for engine and proxyEngine feat: Add proxyEngine to type ModelInfo fix: WIP with test route fix: Add bundleDependencies to package.json chore: remove conversational history memory fix: refactor data passing reafactor: remove unused code fix: Update module chore: export import correction fix conflict Signed-off-by: James <james@jan.ai> fix: resolve after rebased fix: Update llava 1.5 model json feat: Add bakllava 1 model json refactor: node module export, ES syntax and langchain import fix: WIP fix: WIP fix: WIP fix: external module import fix: WIP Add UI attachment fot file upload Prepare Thumbnail UI image chore: rebase model folder to dev branch chore: remove multiple binaries related commits fix: remove multiple binaries related commits part 2 fix: Remove transformer.js related deps Fix truncate file name attachment remove unused code image preview attachment fix: remove multi binaries error chore: remove commented code for ModelArtifacts type Dropzone for drag and drop attachment Avoid conditional showing 0 using length fix symbol windows avoid undefined tools fix: add tool retrieval to true by default and disable the change chore: remove unused code fix: Enable nitro embedding by default fix: Update code WIP with nitro embedding chore: remove unused running function fix: assistant extension missing module feat: Retrieval ingest, query and reforward fix: Update hnswlib version conflict fix: Add tool settings fix: Update path to thread_id/memory fix: Add support for nitro embedding usage fix: RAG does not work with plain content message fix(Model): #1662 imported model does not use gpu (#1723) Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> feat: allow users to update retrieval settings chore: pass thread assistant settings to assistant extensions chore: eslint fix fix bug border right panel showing in thread while not have active thread Update setting layout retrieval assistant Renaming file settingcomponent change default value in core extention add fake loader generate response fix conditional fake loader remove unused import Proper error message on file typr fix: loading indicator fix: chunk size and overlap constraint conditional drag and drop when retrieval off fix: enable retrieval middleware as soon as its tool is enabled fix: configure embedding engine according to request fix: Retrieval false by default fix: engine json chore: migrate assistant disabled collapse panel when retrieval or children null chore: remove unused log chore: Bump nitro version to 0.2.14 for batch embedding chore: remove unused console.log
276 lines
10 KiB
TypeScript
276 lines
10 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react'
|
|
|
|
import {
|
|
ChatCompletionRole,
|
|
ContentType,
|
|
MessageStatus,
|
|
ThreadMessage,
|
|
} from '@janhq/core'
|
|
|
|
import {
|
|
Tooltip,
|
|
TooltipArrow,
|
|
TooltipContent,
|
|
TooltipPortal,
|
|
TooltipTrigger,
|
|
} from '@janhq/uikit'
|
|
import hljs from 'highlight.js'
|
|
|
|
import { useAtomValue } from 'jotai'
|
|
import { FolderOpenIcon } from 'lucide-react'
|
|
import { Marked, Renderer } from 'marked'
|
|
|
|
import { markedHighlight } from 'marked-highlight'
|
|
|
|
import { twMerge } from 'tailwind-merge'
|
|
|
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
|
|
|
import { useClipboard } from '@/hooks/useClipboard'
|
|
import { usePath } from '@/hooks/usePath'
|
|
|
|
import { toGibibytes } from '@/utils/converter'
|
|
import { displayDate } from '@/utils/datetime'
|
|
|
|
import Icon from '../FileUploadPreview/Icon'
|
|
import MessageToolbar from '../MessageToolbar'
|
|
|
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
|
|
|
const SimpleTextMessage: React.FC<ThreadMessage> = (props) => {
|
|
let text = ''
|
|
if (props.content && props.content.length > 0) {
|
|
text = props.content[0]?.text?.value ?? ''
|
|
}
|
|
const clipboard = useClipboard({ timeout: 1000 })
|
|
const { onViewFile } = usePath()
|
|
|
|
const marked: Marked = new Marked(
|
|
markedHighlight({
|
|
langPrefix: 'hljs',
|
|
highlight(code, lang) {
|
|
if (lang === undefined || lang === '') {
|
|
return hljs.highlightAuto(code).value
|
|
}
|
|
try {
|
|
return hljs.highlight(code, { language: lang }).value
|
|
} catch (err) {
|
|
return hljs.highlight(code, { language: 'javascript' }).value
|
|
}
|
|
},
|
|
}),
|
|
{
|
|
renderer: {
|
|
link: (href, title, text) => {
|
|
return Renderer.prototype.link
|
|
?.apply(this, [href, title, text])
|
|
.replace('<a', "<a target='_blank'")
|
|
},
|
|
code(code, lang, escaped) {
|
|
return `
|
|
<div class="relative code-block group/item">
|
|
<button class='text-xs copy-action hidden group-hover/item:block bg-gray-950 hover:bg-gray-950/90 text-gray-200 p-2 rounded-lg absolute top-6 right-2' >
|
|
${
|
|
clipboard.copied
|
|
? `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check pointer-events-none text-green-600"><path d="M20 6 9 17l-5-5"/></svg>`
|
|
: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy pointer-events-none text-gray-400"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`
|
|
}
|
|
</button>
|
|
<pre class="hljs">
|
|
<code class="language-${lang ?? ''}">${
|
|
escaped ? code : decodeURIComponent(code)
|
|
}</code>
|
|
</pre>
|
|
</div>
|
|
`
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
const parsedText = marked.parse(text)
|
|
const isUser = props.role === ChatCompletionRole.User
|
|
const isSystem = props.role === ChatCompletionRole.System
|
|
const [tokenCount, setTokenCount] = useState(0)
|
|
const [lastTimestamp, setLastTimestamp] = useState<number | undefined>()
|
|
const [tokenSpeed, setTokenSpeed] = useState(0)
|
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
|
|
|
const codeBlockCopyEvent = useRef((e: Event) => {
|
|
const target: HTMLElement = e.target as HTMLElement
|
|
if (typeof target.className !== 'string') return null
|
|
|
|
const isCopyActionClassName = target?.className.includes('copy-action')
|
|
|
|
if (isCopyActionClassName) {
|
|
const content = target?.parentNode?.querySelector('code')?.innerText ?? ''
|
|
clipboard.copy(content)
|
|
}
|
|
})
|
|
|
|
useEffect(() => {
|
|
document.addEventListener('click', codeBlockCopyEvent.current)
|
|
return () => {
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
document.removeEventListener('click', codeBlockCopyEvent.current)
|
|
}
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
if (props.status !== MessageStatus.Pending) {
|
|
return
|
|
}
|
|
const currentTimestamp = new Date().getTime() // Get current time in milliseconds
|
|
if (!lastTimestamp) {
|
|
// If this is the first update, just set the lastTimestamp and return
|
|
if (props.content[0]?.text?.value !== '')
|
|
setLastTimestamp(currentTimestamp)
|
|
return
|
|
}
|
|
|
|
const timeDiffInSeconds = (currentTimestamp - lastTimestamp) / 1000 // Time difference in seconds
|
|
const totalTokenCount = tokenCount + 1
|
|
const averageTokenSpeed = totalTokenCount / timeDiffInSeconds // Calculate average token speed
|
|
|
|
setTokenSpeed(averageTokenSpeed)
|
|
setTokenCount(totalTokenCount)
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [props.content])
|
|
|
|
return (
|
|
<div className="group relative mx-auto rounded-xl px-8">
|
|
<div
|
|
className={twMerge(
|
|
'mb-2 flex items-center justify-start gap-x-2',
|
|
!isUser && 'mt-2'
|
|
)}
|
|
>
|
|
{!isUser && !isSystem && <LogoMark width={28} />}
|
|
{isUser && (
|
|
<div className="flex h-8 w-8 items-center justify-center rounded-full border border-border">
|
|
<svg
|
|
width="12"
|
|
height="16"
|
|
viewBox="0 0 12 16"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<path
|
|
d="M6 0.497864C4.34315 0.497864 3 1.84101 3 3.49786C3 5.15472 4.34315 6.49786 6 6.49786C7.65685 6.49786 9 5.15472 9 3.49786C9 1.84101 7.65685 0.497864 6 0.497864ZM9.75 7.99786L2.24997 7.99787C1.00734 7.99787 0 9.00527 0 10.2479C0 11.922 0.688456 13.2633 1.81822 14.1701C2.93013 15.0625 4.42039 15.4979 6 15.4979C7.57961 15.4979 9.06987 15.0625 10.1818 14.1701C11.3115 13.2633 12 11.922 12 10.2479C12 9.00522 10.9926 7.99786 9.75 7.99786Z"
|
|
fill="#9CA3AF"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
)}
|
|
|
|
<div
|
|
className={twMerge(
|
|
'text-sm font-extrabold capitalize',
|
|
isUser && 'text-gray-500'
|
|
)}
|
|
>
|
|
{props.role}
|
|
</div>
|
|
<p className="text-xs font-medium text-gray-400">
|
|
{displayDate(props.created)}
|
|
</p>
|
|
<div
|
|
className={twMerge(
|
|
'absolute right-0 cursor-pointer transition-all',
|
|
messages[messages.length - 1]?.id === props.id && !isUser
|
|
? 'absolute -bottom-10 right-8'
|
|
: 'hidden group-hover:absolute group-hover:-top-2 group-hover:right-8 group-hover:flex'
|
|
)}
|
|
>
|
|
<MessageToolbar message={props} />
|
|
</div>
|
|
{messages[messages.length - 1]?.id === props.id &&
|
|
(props.status === MessageStatus.Pending || tokenSpeed > 0) && (
|
|
<p className="absolute right-8 text-xs font-medium text-foreground">
|
|
Token Speed: {Number(tokenSpeed).toFixed(2)}/s
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className={twMerge('w-full')}>
|
|
<>
|
|
{props.content[0]?.type === ContentType.Image && (
|
|
<div className="group/image relative mb-2 inline-flex overflow-hidden rounded-xl">
|
|
<img
|
|
className="aspect-auto h-[300px]"
|
|
alt={props.content[0]?.text.name}
|
|
src={props.content[0]?.text.annotations[0]}
|
|
/>
|
|
<div className="absolute left-0 top-0 z-20 hidden h-full w-full bg-black/20 group-hover/image:inline-block" />
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<div
|
|
className="absolute right-2 top-2 z-20 hidden h-8 w-8 cursor-pointer items-center justify-center rounded-md bg-background group-hover/image:flex"
|
|
onClick={() => onViewFile(`${props.id}.png`)}
|
|
>
|
|
<FolderOpenIcon size={20} />
|
|
</div>
|
|
</TooltipTrigger>
|
|
<TooltipPortal>
|
|
<TooltipContent side="top" className="max-w-[154px] px-3">
|
|
<span>Show in finder</span>
|
|
<TooltipArrow />
|
|
</TooltipContent>
|
|
</TooltipPortal>
|
|
</Tooltip>
|
|
</div>
|
|
)}
|
|
|
|
{props.content[0]?.type === ContentType.Pdf && (
|
|
<div className="group/file relative mb-2 inline-flex w-60 cursor-pointer gap-x-3 overflow-hidden rounded-lg bg-secondary p-4">
|
|
<div className="absolute left-0 top-0 z-20 hidden h-full w-full bg-black/20 backdrop-blur-sm group-hover/file:inline-block" />
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<div
|
|
className="absolute right-2 top-2 z-20 hidden h-8 w-8 cursor-pointer items-center justify-center rounded-md bg-background group-hover/file:flex"
|
|
onClick={() =>
|
|
onViewFile(`${props.id}.${props.content[0]?.type}`)
|
|
}
|
|
>
|
|
<FolderOpenIcon size={20} />
|
|
</div>
|
|
</TooltipTrigger>
|
|
<TooltipPortal>
|
|
<TooltipContent side="top" className="max-w-[154px] px-3">
|
|
<span>Show in finder</span>
|
|
<TooltipArrow />
|
|
</TooltipContent>
|
|
</TooltipPortal>
|
|
</Tooltip>
|
|
|
|
<Icon type={props.content[0].type} />
|
|
|
|
<div>
|
|
<h6 className="line-clamp-1 font-medium">
|
|
{props.content[0].text.name?.replaceAll(/[-._]/g, ' ')}
|
|
</h6>
|
|
<p className="text-muted-foreground">
|
|
{toGibibytes(Number(props.content[0].text.size))}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div
|
|
className={twMerge(
|
|
'message flex flex-grow flex-col gap-y-2 text-[15px] font-normal leading-relaxed',
|
|
isUser
|
|
? 'whitespace-pre-wrap break-words'
|
|
: 'rounded-xl bg-secondary p-4'
|
|
)}
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
dangerouslySetInnerHTML={{ __html: parsedText }}
|
|
/>
|
|
</>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default React.memo(SimpleTextMessage)
|