fix: image and attachments
This commit is contained in:
parent
5a4c5eee83
commit
bb106eba01
@ -36,3 +36,10 @@ export type Assistant = {
|
|||||||
/** Represents the metadata of the object. */
|
/** Represents the metadata of the object. */
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CodeInterpreterTool {
|
||||||
|
/**
|
||||||
|
* The type of tool being defined: `code_interpreter`
|
||||||
|
*/
|
||||||
|
type: 'code_interpreter'
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { CodeInterpreterTool } from '../assistant'
|
||||||
import { ChatCompletionMessage, ChatCompletionRole } from '../inference'
|
import { ChatCompletionMessage, ChatCompletionRole } from '../inference'
|
||||||
import { ModelInfo } from '../model'
|
import { ModelInfo } from '../model'
|
||||||
import { Thread } from '../thread'
|
import { Thread } from '../thread'
|
||||||
@ -15,6 +16,10 @@ export type ThreadMessage = {
|
|||||||
thread_id: string
|
thread_id: string
|
||||||
/** The assistant id of this thread. **/
|
/** The assistant id of this thread. **/
|
||||||
assistant_id?: string
|
assistant_id?: string
|
||||||
|
/**
|
||||||
|
* A list of files attached to the message, and the tools they were added to.
|
||||||
|
*/
|
||||||
|
attachments?: Array<Attachment> | null
|
||||||
/** The role of the author of this message. **/
|
/** The role of the author of this message. **/
|
||||||
role: ChatCompletionRole
|
role: ChatCompletionRole
|
||||||
/** The content of this message. **/
|
/** The content of this message. **/
|
||||||
@ -52,6 +57,11 @@ export type MessageRequest = {
|
|||||||
*/
|
*/
|
||||||
assistantId?: string
|
assistantId?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of files attached to the message, and the tools they were added to.
|
||||||
|
*/
|
||||||
|
attachments: Array<Attachment> | null
|
||||||
|
|
||||||
/** Messages for constructing a chat completion request **/
|
/** Messages for constructing a chat completion request **/
|
||||||
messages?: ChatCompletionMessage[]
|
messages?: ChatCompletionMessage[]
|
||||||
|
|
||||||
@ -98,7 +108,6 @@ export enum ErrorCode {
|
|||||||
export enum ContentType {
|
export enum ContentType {
|
||||||
Text = 'text',
|
Text = 'text',
|
||||||
Image = 'image_url',
|
Image = 'image_url',
|
||||||
Pdf = 'pdf',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,8 +117,15 @@ export enum ContentType {
|
|||||||
export type ContentValue = {
|
export type ContentValue = {
|
||||||
value: string
|
value: string
|
||||||
annotations: string[]
|
annotations: string[]
|
||||||
name?: string
|
}
|
||||||
size?: number
|
|
||||||
|
/**
|
||||||
|
* The `ImageContentValue` type defines the shape of a content value object of image type
|
||||||
|
* @data_transfer_object
|
||||||
|
*/
|
||||||
|
export type ImageContentValue = {
|
||||||
|
detail?: string
|
||||||
|
url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,5 +134,37 @@ export type ContentValue = {
|
|||||||
*/
|
*/
|
||||||
export type ThreadContent = {
|
export type ThreadContent = {
|
||||||
type: ContentType
|
type: ContentType
|
||||||
text: ContentValue
|
text?: ContentValue
|
||||||
|
image_url?: ImageContentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Attachment {
|
||||||
|
/**
|
||||||
|
* The ID of the file to attach to the message.
|
||||||
|
*/
|
||||||
|
file_id?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tools to add this file to.
|
||||||
|
*/
|
||||||
|
tools?: Array<CodeInterpreterTool | Attachment.AssistantToolsFileSearchTypeOnly>
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Attachment {
|
||||||
|
export interface AssistantToolsFileSearchTypeOnly {
|
||||||
|
/**
|
||||||
|
* The type of tool being defined: `file_search`
|
||||||
|
*/
|
||||||
|
type: 'file_search'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On an incomplete message, details about why the message is incomplete.
|
||||||
|
*/
|
||||||
|
export interface IncompleteDetails {
|
||||||
|
/**
|
||||||
|
* The reason the message is incomplete.
|
||||||
|
*/
|
||||||
|
reason: 'content_filter' | 'max_tokens' | 'run_cancelled' | 'run_expired' | 'run_failed'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,7 +141,7 @@ export default class JanAssistantExtension extends AssistantExtension {
|
|||||||
top_k: 2,
|
top_k: 2,
|
||||||
chunk_size: 1024,
|
chunk_size: 1024,
|
||||||
chunk_overlap: 64,
|
chunk_overlap: 64,
|
||||||
retrieval_template: `Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
|
retrieval_template: `Use the following pieces of context to answer the question at the end.
|
||||||
----------------
|
----------------
|
||||||
CONTEXT: {CONTEXT}
|
CONTEXT: {CONTEXT}
|
||||||
----------------
|
----------------
|
||||||
|
|||||||
@ -9,13 +9,14 @@ export function toolRetrievalUpdateTextSplitter(
|
|||||||
retrieval.updateTextSplitter(chunkSize, chunkOverlap)
|
retrieval.updateTextSplitter(chunkSize, chunkOverlap)
|
||||||
}
|
}
|
||||||
export async function toolRetrievalIngestNewDocument(
|
export async function toolRetrievalIngestNewDocument(
|
||||||
|
thread: string,
|
||||||
file: string,
|
file: string,
|
||||||
model: string,
|
model: string,
|
||||||
engine: string,
|
engine: string,
|
||||||
useTimeWeighted: boolean
|
useTimeWeighted: boolean
|
||||||
) {
|
) {
|
||||||
const filePath = path.join(getJanDataFolderPath(), normalizeFilePath(file))
|
const threadPath = path.join(getJanDataFolderPath(), 'threads', thread)
|
||||||
const threadPath = path.dirname(filePath.replace('files', ''))
|
const filePath = path.join(getJanDataFolderPath(), 'files', file)
|
||||||
retrieval.updateEmbeddingEngine(model, engine)
|
retrieval.updateEmbeddingEngine(model, engine)
|
||||||
return retrieval
|
return retrieval
|
||||||
.ingestAgentKnowledge(filePath, `${threadPath}/memory`, useTimeWeighted)
|
.ingestAgentKnowledge(filePath, `${threadPath}/memory`, useTimeWeighted)
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export class RetrievalTool extends InferenceTool {
|
|||||||
await executeOnMain(
|
await executeOnMain(
|
||||||
NODE,
|
NODE,
|
||||||
'toolRetrievalIngestNewDocument',
|
'toolRetrievalIngestNewDocument',
|
||||||
|
data.thread?.id,
|
||||||
docFile,
|
docFile,
|
||||||
data.model?.id,
|
data.model?.id,
|
||||||
data.model?.engine,
|
data.model?.engine,
|
||||||
|
|||||||
@ -89,7 +89,9 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
|
|||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<AutoLink text={message.content[0].text.value} />
|
{message?.content[0]?.text?.value && (
|
||||||
|
<AutoLink text={message?.content[0]?.text?.value} />
|
||||||
|
)}
|
||||||
{defaultDesc()}
|
{defaultDesc()}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { FileInfo } from '@/types/file'
|
|||||||
|
|
||||||
export const editPromptAtom = atom<string>('')
|
export const editPromptAtom = atom<string>('')
|
||||||
export const currentPromptAtom = atom<string>('')
|
export const currentPromptAtom = atom<string>('')
|
||||||
export const fileUploadAtom = atom<FileInfo[]>([])
|
export const fileUploadAtom = atom<FileInfo | undefined>()
|
||||||
|
|
||||||
export const searchAtom = atom<string>('')
|
export const searchAtom = atom<string>('')
|
||||||
|
|
||||||
|
|||||||
@ -58,9 +58,11 @@ describe('Model.atom.ts', () => {
|
|||||||
setAtom.current({ id: '1' } as any)
|
setAtom.current({ id: '1' } as any)
|
||||||
})
|
})
|
||||||
expect(getAtom.current).toEqual([{ id: '1' }])
|
expect(getAtom.current).toEqual([{ id: '1' }])
|
||||||
|
act(() => {
|
||||||
reset.current([])
|
reset.current([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('removeDownloadingModelAtom', () => {
|
describe('removeDownloadingModelAtom', () => {
|
||||||
it('should remove downloading model', async () => {
|
it('should remove downloading model', async () => {
|
||||||
@ -83,9 +85,11 @@ describe('Model.atom.ts', () => {
|
|||||||
removeAtom.current('1')
|
removeAtom.current('1')
|
||||||
})
|
})
|
||||||
expect(getAtom.current).toEqual([])
|
expect(getAtom.current).toEqual([])
|
||||||
|
act(() => {
|
||||||
reset.current([])
|
reset.current([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('removeDownloadedModelAtom', () => {
|
describe('removeDownloadedModelAtom', () => {
|
||||||
it('should remove downloaded model', async () => {
|
it('should remove downloaded model', async () => {
|
||||||
@ -113,9 +117,11 @@ describe('Model.atom.ts', () => {
|
|||||||
removeAtom.current('1')
|
removeAtom.current('1')
|
||||||
})
|
})
|
||||||
expect(getAtom.current).toEqual([])
|
expect(getAtom.current).toEqual([])
|
||||||
|
act(() => {
|
||||||
reset.current([])
|
reset.current([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('importingModelAtom', () => {
|
describe('importingModelAtom', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { fileUploadAtom } from '@/containers/Providers/Jotai'
|
|||||||
import { toaster } from '@/containers/Toast'
|
import { toaster } from '@/containers/Toast'
|
||||||
|
|
||||||
import { isLocalEngine } from '@/utils/modelEngine'
|
import { isLocalEngine } from '@/utils/modelEngine'
|
||||||
|
|
||||||
import { useActiveModel } from './useActiveModel'
|
import { useActiveModel } from './useActiveModel'
|
||||||
import useRecommendedModel from './useRecommendedModel'
|
import useRecommendedModel from './useRecommendedModel'
|
||||||
|
|
||||||
@ -168,7 +169,7 @@ export const useCreateNewThread = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Delete the file upload state
|
// Delete the file upload state
|
||||||
setFileUpload([])
|
setFileUpload(undefined)
|
||||||
setActiveThread(createdThread)
|
setActiveThread(createdThread)
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
return toaster({
|
return toaster({
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* @jest-environment jsdom
|
||||||
|
*/
|
||||||
// useDropModelBinaries.test.ts
|
// useDropModelBinaries.test.ts
|
||||||
|
|
||||||
import { renderHook, act } from '@testing-library/react'
|
import { renderHook, act } from '@testing-library/react'
|
||||||
@ -18,6 +21,7 @@ jest.mock('jotai', () => ({
|
|||||||
jest.mock('uuid')
|
jest.mock('uuid')
|
||||||
jest.mock('@/utils/file')
|
jest.mock('@/utils/file')
|
||||||
jest.mock('@/containers/Toast')
|
jest.mock('@/containers/Toast')
|
||||||
|
jest.mock("@uppy/core")
|
||||||
|
|
||||||
describe('useDropModelBinaries', () => {
|
describe('useDropModelBinaries', () => {
|
||||||
const mockSetImportingModels = jest.fn()
|
const mockSetImportingModels = jest.fn()
|
||||||
|
|||||||
@ -134,11 +134,9 @@ export default function useSendChatMessage() {
|
|||||||
setCurrentPrompt('')
|
setCurrentPrompt('')
|
||||||
setEditPrompt('')
|
setEditPrompt('')
|
||||||
|
|
||||||
let base64Blob = fileUpload[0]
|
let base64Blob = fileUpload ? await getBase64(fileUpload.file) : undefined
|
||||||
? await getBase64(fileUpload[0].file)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
if (base64Blob && fileUpload[0]?.type === 'image') {
|
if (base64Blob && fileUpload?.type === 'image') {
|
||||||
// Compress image
|
// Compress image
|
||||||
base64Blob = await compressImage(base64Blob, 512)
|
base64Blob = await compressImage(base64Blob, 512)
|
||||||
}
|
}
|
||||||
@ -171,7 +169,7 @@ export default function useSendChatMessage() {
|
|||||||
).addSystemMessage(activeAssistantRef.current?.instructions)
|
).addSystemMessage(activeAssistantRef.current?.instructions)
|
||||||
|
|
||||||
if (!isResend) {
|
if (!isResend) {
|
||||||
requestBuilder.pushMessage(prompt, base64Blob, fileUpload[0]?.type)
|
requestBuilder.pushMessage(prompt, base64Blob, fileUpload)
|
||||||
|
|
||||||
// Build Thread Message to persist
|
// Build Thread Message to persist
|
||||||
const threadMessageBuilder = new ThreadMessageBuilder(
|
const threadMessageBuilder = new ThreadMessageBuilder(
|
||||||
@ -207,7 +205,7 @@ export default function useSendChatMessage() {
|
|||||||
selectedModelRef.current?.id ?? activeAssistantRef.current?.model.id
|
selectedModelRef.current?.id ?? activeAssistantRef.current?.model.id
|
||||||
|
|
||||||
if (base64Blob) {
|
if (base64Blob) {
|
||||||
setFileUpload([])
|
setFileUpload(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modelRef.current?.id !== modelId && modelId) {
|
if (modelRef.current?.id !== modelId && modelId) {
|
||||||
@ -222,9 +220,7 @@ export default function useSendChatMessage() {
|
|||||||
// Process message request with Assistants tools
|
// Process message request with Assistants tools
|
||||||
const request = await ToolManager.instance().process(
|
const request = await ToolManager.instance().process(
|
||||||
requestBuilder.build(),
|
requestBuilder.build(),
|
||||||
activeThreadRef.current.assistants?.flatMap(
|
activeAssistantRef?.current.tools ?? []
|
||||||
(assistant) => assistant.tools ?? []
|
|
||||||
) ?? []
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request for inference
|
// Request for inference
|
||||||
|
|||||||
@ -37,5 +37,5 @@ const config = {
|
|||||||
// module.exports = createJestConfig(config)
|
// module.exports = createJestConfig(config)
|
||||||
module.exports = async () => ({
|
module.exports = async () => ({
|
||||||
...(await createJestConfig(config)()),
|
...(await createJestConfig(config)()),
|
||||||
transformIgnorePatterns: ['/node_modules/(?!(layerr)/)'],
|
transformIgnorePatterns: ['/node_modules/(?!(layerr|nanoid|@uppy|preact)/)'],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -17,6 +17,9 @@
|
|||||||
"@janhq/core": "link:./core",
|
"@janhq/core": "link:./core",
|
||||||
"@janhq/joi": "link:./joi",
|
"@janhq/joi": "link:./joi",
|
||||||
"@tanstack/react-virtual": "^3.10.9",
|
"@tanstack/react-virtual": "^3.10.9",
|
||||||
|
"@uppy/core": "^4.3.0",
|
||||||
|
"@uppy/react": "^4.0.4",
|
||||||
|
"@uppy/xhr-upload": "^4.2.3",
|
||||||
"autoprefixer": "10.4.16",
|
"autoprefixer": "10.4.16",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"framer-motion": "^10.16.4",
|
"framer-motion": "^10.16.4",
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { useActiveModel } from '@/hooks/useActiveModel'
|
|||||||
|
|
||||||
import useSendChatMessage from '@/hooks/useSendChatMessage'
|
import useSendChatMessage from '@/hooks/useSendChatMessage'
|
||||||
|
|
||||||
|
import { uploader } from '@/utils/file'
|
||||||
import { isLocalEngine } from '@/utils/modelEngine'
|
import { isLocalEngine } from '@/utils/modelEngine'
|
||||||
|
|
||||||
import FileUploadPreview from '../FileUploadPreview'
|
import FileUploadPreview from '../FileUploadPreview'
|
||||||
@ -71,6 +72,7 @@ const ChatInput = () => {
|
|||||||
const activeAssistant = useAtomValue(activeAssistantAtom)
|
const activeAssistant = useAtomValue(activeAssistantAtom)
|
||||||
const { stopInference } = useActiveModel()
|
const { stopInference } = useActiveModel()
|
||||||
|
|
||||||
|
const upload = uploader()
|
||||||
const [activeTabThreadRightPanel, setActiveTabThreadRightPanel] = useAtom(
|
const [activeTabThreadRightPanel, setActiveTabThreadRightPanel] = useAtom(
|
||||||
activeTabThreadRightPanelAtom
|
activeTabThreadRightPanelAtom
|
||||||
)
|
)
|
||||||
@ -104,18 +106,26 @@ const ChatInput = () => {
|
|||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = event.target.files?.[0]
|
const file = event.target.files?.[0]
|
||||||
if (!file) return
|
if (!file) return
|
||||||
setFileUpload([{ file: file, type: 'pdf' }])
|
upload.addFile(file)
|
||||||
|
upload.upload().then((data) => {
|
||||||
|
setFileUpload({
|
||||||
|
file: file,
|
||||||
|
type: 'pdf',
|
||||||
|
id: data?.successful?.[0]?.response?.body?.id,
|
||||||
|
name: data?.successful?.[0]?.response?.body?.filename,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = event.target.files?.[0]
|
const file = event.target.files?.[0]
|
||||||
if (!file) return
|
if (!file) return
|
||||||
setFileUpload([{ file: file, type: 'image' }])
|
setFileUpload({ file: file, type: 'image' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderPreview = (fileUpload: any) => {
|
const renderPreview = (fileUpload: any) => {
|
||||||
if (fileUpload.length > 0) {
|
if (fileUpload) {
|
||||||
if (fileUpload[0].type === 'image') {
|
if (fileUpload.type === 'image') {
|
||||||
return <ImageUploadPreview file={fileUpload[0].file} />
|
return <ImageUploadPreview file={fileUpload[0].file} />
|
||||||
} else {
|
} else {
|
||||||
return <FileUploadPreview />
|
return <FileUploadPreview />
|
||||||
@ -132,7 +142,7 @@ const ChatInput = () => {
|
|||||||
'relative mb-1 max-h-[400px] resize-none rounded-lg border border-[hsla(var(--app-border))] p-3 pr-20',
|
'relative mb-1 max-h-[400px] resize-none rounded-lg border border-[hsla(var(--app-border))] p-3 pr-20',
|
||||||
'focus-within:outline-none focus-visible:outline-0 focus-visible:ring-1 focus-visible:ring-[hsla(var(--primary-bg))] focus-visible:ring-offset-0',
|
'focus-within:outline-none focus-visible:outline-0 focus-visible:ring-1 focus-visible:ring-[hsla(var(--primary-bg))] focus-visible:ring-offset-0',
|
||||||
'overflow-y-auto',
|
'overflow-y-auto',
|
||||||
fileUpload.length && 'rounded-t-none',
|
fileUpload && 'rounded-t-none',
|
||||||
experimentalFeature && 'pl-10',
|
experimentalFeature && 'pl-10',
|
||||||
activeSettingInputBox && 'pb-14 pr-16'
|
activeSettingInputBox && 'pb-14 pr-16'
|
||||||
)}
|
)}
|
||||||
@ -154,7 +164,7 @@ const ChatInput = () => {
|
|||||||
className="absolute left-3 top-2.5"
|
className="absolute left-3 top-2.5"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (
|
if (
|
||||||
fileUpload.length > 0 ||
|
!!fileUpload ||
|
||||||
(activeAssistant?.tools &&
|
(activeAssistant?.tools &&
|
||||||
!activeAssistant?.tools[0]?.enabled &&
|
!activeAssistant?.tools[0]?.enabled &&
|
||||||
!activeAssistant?.model.settings?.vision_model)
|
!activeAssistant?.model.settings?.vision_model)
|
||||||
@ -178,12 +188,12 @@ const ChatInput = () => {
|
|||||||
}
|
}
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
{fileUpload.length > 0 ||
|
{!!fileUpload ||
|
||||||
(activeAssistant?.tools &&
|
(activeAssistant?.tools &&
|
||||||
!activeAssistant?.tools[0]?.enabled &&
|
!activeAssistant?.tools[0]?.enabled &&
|
||||||
!activeAssistant?.model.settings?.vision_model && (
|
!activeAssistant?.model.settings?.vision_model && (
|
||||||
<>
|
<>
|
||||||
{fileUpload.length !== 0 && (
|
{!!fileUpload && (
|
||||||
<span>
|
<span>
|
||||||
Currently, we only support 1 attachment at the same
|
Currently, we only support 1 attachment at the same
|
||||||
time.
|
time.
|
||||||
|
|||||||
@ -15,21 +15,22 @@ const FileUploadPreview = () => {
|
|||||||
const setCurrentPrompt = useSetAtom(currentPromptAtom)
|
const setCurrentPrompt = useSetAtom(currentPromptAtom)
|
||||||
|
|
||||||
const onDeleteClick = () => {
|
const onDeleteClick = () => {
|
||||||
setFileUpload([])
|
setFileUpload(undefined)
|
||||||
setCurrentPrompt('')
|
setCurrentPrompt('')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col rounded-t-lg border border-b-0 border-[hsla(var(--app-border))] p-4">
|
<div className="flex flex-col rounded-t-lg border border-b-0 border-[hsla(var(--app-border))] p-4">
|
||||||
|
{!!fileUpload && (
|
||||||
<div className="bg-secondary relative inline-flex w-60 space-x-3 rounded-lg p-4">
|
<div className="bg-secondary relative inline-flex w-60 space-x-3 rounded-lg p-4">
|
||||||
<Icon type={fileUpload[0].type} />
|
<Icon type={fileUpload?.type} />
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<h6 className="line-clamp-1 w-3/4 truncate font-medium">
|
<h6 className="line-clamp-1 w-3/4 truncate font-medium">
|
||||||
{fileUpload[0].file.name.replaceAll(/[-._]/g, ' ')}
|
{fileUpload?.file.name.replaceAll(/[-._]/g, ' ')}
|
||||||
</h6>
|
</h6>
|
||||||
<p className="text-[hsla(var(--text-secondary)]">
|
<p className="text-[hsla(var(--text-secondary)]">
|
||||||
{toGibibytes(fileUpload[0].file.size)}
|
{toGibibytes(fileUpload?.file.size)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ const FileUploadPreview = () => {
|
|||||||
<XIcon size={14} className="text-background" />
|
<XIcon size={14} className="text-background" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const ImageUploadPreview: React.FC<Props> = ({ file }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onDeleteClick = () => {
|
const onDeleteClick = () => {
|
||||||
setFileUpload([])
|
setFileUpload(undefined)
|
||||||
setCurrentPrompt('')
|
setCurrentPrompt('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,15 +11,7 @@ import { openFileTitle } from '@/utils/titleUtils'
|
|||||||
|
|
||||||
import Icon from '../FileUploadPreview/Icon'
|
import Icon from '../FileUploadPreview/Icon'
|
||||||
|
|
||||||
const DocMessage = ({
|
const DocMessage = ({ id, name }: { id: string; name?: string }) => {
|
||||||
id,
|
|
||||||
name,
|
|
||||||
size,
|
|
||||||
}: {
|
|
||||||
id: string
|
|
||||||
name?: string
|
|
||||||
size?: number
|
|
||||||
}) => {
|
|
||||||
const { onViewFile, onViewFileContainer } = usePath()
|
const { onViewFile, onViewFileContainer } = usePath()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -44,9 +36,9 @@ const DocMessage = ({
|
|||||||
<h6 className="line-clamp-1 w-4/5 font-medium">
|
<h6 className="line-clamp-1 w-4/5 font-medium">
|
||||||
{name?.replaceAll(/[-._]/g, ' ')}
|
{name?.replaceAll(/[-._]/g, ' ')}
|
||||||
</h6>
|
</h6>
|
||||||
<p className="text-[hsla(var(--text-secondary)]">
|
{/* <p className="text-[hsla(var(--text-secondary)]">
|
||||||
{toGibibytes(Number(size))}
|
{toGibibytes(Number(size))}
|
||||||
</p>
|
</p> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { memo, useMemo } from 'react'
|
import { memo } from 'react'
|
||||||
|
|
||||||
import { ThreadContent } from '@janhq/core'
|
|
||||||
import { Tooltip } from '@janhq/joi'
|
import { Tooltip } from '@janhq/joi'
|
||||||
|
|
||||||
import { FolderOpenIcon } from 'lucide-react'
|
import { FolderOpenIcon } from 'lucide-react'
|
||||||
@ -11,21 +10,13 @@ import { openFileTitle } from '@/utils/titleUtils'
|
|||||||
|
|
||||||
import { RelativeImage } from '../TextMessage/RelativeImage'
|
import { RelativeImage } from '../TextMessage/RelativeImage'
|
||||||
|
|
||||||
const ImageMessage = ({ content }: { content: ThreadContent }) => {
|
const ImageMessage = ({ image }: { image: string }) => {
|
||||||
const { onViewFile, onViewFileContainer } = usePath()
|
const { onViewFile, onViewFileContainer } = usePath()
|
||||||
|
|
||||||
const annotation = useMemo(
|
|
||||||
() => content?.text?.annotations[0] ?? '',
|
|
||||||
[content]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group/image relative mb-2 inline-flex cursor-pointer overflow-hidden rounded-xl">
|
<div className="group/image relative mb-2 inline-flex cursor-pointer overflow-hidden rounded-xl">
|
||||||
<div className="left-0 top-0 z-20 h-full w-full group-hover/image:inline-block">
|
<div className="left-0 top-0 z-20 h-full w-full group-hover/image:inline-block">
|
||||||
<RelativeImage
|
<RelativeImage src={image} onClick={() => onViewFile(image)} />
|
||||||
src={annotation}
|
|
||||||
onClick={() => onViewFile(annotation)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
trigger={
|
trigger={
|
||||||
|
|||||||
@ -33,14 +33,19 @@ const MessageContainer: React.FC<
|
|||||||
const tokenSpeed = useAtomValue(tokenSpeedAtom)
|
const tokenSpeed = useAtomValue(tokenSpeedAtom)
|
||||||
|
|
||||||
const text = useMemo(
|
const text = useMemo(
|
||||||
() => props.content[0]?.text?.value ?? '',
|
() =>
|
||||||
|
props.content.find((e) => e.type === ContentType.Text)?.text?.value ?? '',
|
||||||
[props.content]
|
[props.content]
|
||||||
)
|
)
|
||||||
const messageType = useMemo(
|
|
||||||
() => props.content[0]?.type ?? '',
|
const image = useMemo(
|
||||||
|
() =>
|
||||||
|
props.content.find((e) => e.type === ContentType.Image)?.image_url?.url,
|
||||||
[props.content]
|
[props.content]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const attachedFile = useMemo(() => 'attachments' in props, [props])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative mx-auto max-w-[700px] p-4">
|
<div className="group relative mx-auto max-w-[700px] p-4">
|
||||||
<div
|
<div
|
||||||
@ -108,16 +113,8 @@ const MessageContainer: React.FC<
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{messageType === ContentType.Image && (
|
{image && <ImageMessage image={image} />}
|
||||||
<ImageMessage content={props.content[0]} />
|
{attachedFile && <DocMessage id={props.id} name={props.id} />}
|
||||||
)}
|
|
||||||
{messageType === ContentType.Pdf && (
|
|
||||||
<DocMessage
|
|
||||||
id={props.id}
|
|
||||||
name={props.content[0]?.text?.name}
|
|
||||||
size={props.content[0]?.text?.size}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{editMessage === props.id ? (
|
{editMessage === props.id ? (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -22,6 +22,8 @@ import { reloadModelAtom } from '@/hooks/useSendChatMessage'
|
|||||||
|
|
||||||
import ChatBody from '@/screens/Thread/ThreadCenterPanel/ChatBody'
|
import ChatBody from '@/screens/Thread/ThreadCenterPanel/ChatBody'
|
||||||
|
|
||||||
|
import { uploader } from '@/utils/file'
|
||||||
|
|
||||||
import ChatInput from './ChatInput'
|
import ChatInput from './ChatInput'
|
||||||
import RequestDownloadModel from './RequestDownloadModel'
|
import RequestDownloadModel from './RequestDownloadModel'
|
||||||
|
|
||||||
@ -57,7 +59,7 @@ const ThreadCenterPanel = () => {
|
|||||||
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
const experimentalFeature = useAtomValue(experimentalFeatureEnabledAtom)
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
const activeAssistant = useAtomValue(activeAssistantAtom)
|
const activeAssistant = useAtomValue(activeAssistantAtom)
|
||||||
|
const upload = uploader()
|
||||||
const acceptedFormat: Accept = activeAssistant?.model.settings?.vision_model
|
const acceptedFormat: Accept = activeAssistant?.model.settings?.vision_model
|
||||||
? {
|
? {
|
||||||
'application/pdf': ['.pdf'],
|
'application/pdf': ['.pdf'],
|
||||||
@ -93,7 +95,7 @@ const ThreadCenterPanel = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDragLeave: () => setDragOver(false),
|
onDragLeave: () => setDragOver(false),
|
||||||
onDrop: (files, rejectFiles) => {
|
onDrop: async (files, rejectFiles) => {
|
||||||
// Retrieval file drag and drop is experimental feature
|
// Retrieval file drag and drop is experimental feature
|
||||||
if (!experimentalFeature) return
|
if (!experimentalFeature) return
|
||||||
if (
|
if (
|
||||||
@ -106,7 +108,19 @@ const ThreadCenterPanel = () => {
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
const imageType = files[0]?.type.includes('image')
|
const imageType = files[0]?.type.includes('image')
|
||||||
setFileUpload([{ file: files[0], type: imageType ? 'image' : 'pdf' }])
|
if (imageType) {
|
||||||
|
setFileUpload({ file: files[0], type: 'image' })
|
||||||
|
} else {
|
||||||
|
upload.addFile(files[0])
|
||||||
|
upload.upload().then((data) => {
|
||||||
|
setFileUpload({
|
||||||
|
file: files[0],
|
||||||
|
type: imageType ? 'image' : 'pdf',
|
||||||
|
id: data?.successful?.[0]?.response?.body?.id,
|
||||||
|
name: data?.successful?.[0]?.response?.body?.filename,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
setDragOver(false)
|
setDragOver(false)
|
||||||
},
|
},
|
||||||
onDropRejected: (e) => {
|
onDropRejected: (e) => {
|
||||||
|
|||||||
2
web/types/file.d.ts
vendored
2
web/types/file.d.ts
vendored
@ -3,4 +3,6 @@ export type FileType = 'image' | 'pdf'
|
|||||||
export type FileInfo = {
|
export type FileInfo = {
|
||||||
file: File
|
file: File
|
||||||
type: FileType
|
type: FileType
|
||||||
|
id?: string
|
||||||
|
name?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { baseName } from '@janhq/core'
|
import { baseName } from '@janhq/core'
|
||||||
|
import Uppy from '@uppy/core'
|
||||||
|
import XHR from '@uppy/xhr-upload'
|
||||||
|
|
||||||
export type FilePathWithSize = {
|
export type FilePathWithSize = {
|
||||||
path: string
|
path: string
|
||||||
@ -27,3 +29,17 @@ export const getFileInfoFromFile = async (
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const uploader = () => {
|
||||||
|
const uppy = new Uppy().use(XHR, {
|
||||||
|
endpoint: 'http://127.0.0.1:39291/v1/files',
|
||||||
|
method: 'POST',
|
||||||
|
fieldName: 'file',
|
||||||
|
formData: true,
|
||||||
|
limit: 1,
|
||||||
|
})
|
||||||
|
uppy.setMeta({
|
||||||
|
purpose: 'assistants',
|
||||||
|
})
|
||||||
|
return uppy
|
||||||
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { ulid } from 'ulidx'
|
|||||||
|
|
||||||
import { Stack } from '@/utils/Stack'
|
import { Stack } from '@/utils/Stack'
|
||||||
|
|
||||||
import { FileType } from '@/types/file'
|
import { FileInfo, FileType } from '@/types/file'
|
||||||
|
|
||||||
export class MessageRequestBuilder {
|
export class MessageRequestBuilder {
|
||||||
msgId: string
|
msgId: string
|
||||||
@ -38,7 +38,7 @@ export class MessageRequestBuilder {
|
|||||||
.filter((e) => e.status !== MessageStatus.Error)
|
.filter((e) => e.status !== MessageStatus.Error)
|
||||||
.map<ChatCompletionMessage>((msg) => ({
|
.map<ChatCompletionMessage>((msg) => ({
|
||||||
role: msg.role,
|
role: msg.role,
|
||||||
content: msg.content[0]?.text.value ?? '.',
|
content: msg.content[0]?.text?.value ?? '.',
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,11 +46,11 @@ export class MessageRequestBuilder {
|
|||||||
pushMessage(
|
pushMessage(
|
||||||
message: string,
|
message: string,
|
||||||
base64Blob: string | undefined,
|
base64Blob: string | undefined,
|
||||||
fileContentType: FileType
|
fileInfo?: FileInfo
|
||||||
) {
|
) {
|
||||||
if (base64Blob && fileContentType === 'pdf')
|
if (base64Blob && fileInfo?.type === 'pdf')
|
||||||
return this.addDocMessage(message)
|
return this.addDocMessage(message, fileInfo?.name)
|
||||||
else if (base64Blob && fileContentType === 'image') {
|
else if (base64Blob && fileInfo?.type === 'image') {
|
||||||
return this.addImageMessage(message, base64Blob)
|
return this.addImageMessage(message, base64Blob)
|
||||||
}
|
}
|
||||||
this.messages = [
|
this.messages = [
|
||||||
@ -77,7 +77,7 @@ export class MessageRequestBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Chainable
|
// Chainable
|
||||||
addDocMessage(prompt: string) {
|
addDocMessage(prompt: string, name?: string) {
|
||||||
const message: ChatCompletionMessage = {
|
const message: ChatCompletionMessage = {
|
||||||
role: ChatCompletionRole.User,
|
role: ChatCompletionRole.User,
|
||||||
content: [
|
content: [
|
||||||
@ -88,7 +88,7 @@ export class MessageRequestBuilder {
|
|||||||
{
|
{
|
||||||
type: ChatCompletionMessageContentType.Doc,
|
type: ChatCompletionMessageContentType.Doc,
|
||||||
doc_url: {
|
doc_url: {
|
||||||
url: `threads/${this.thread.id}/files/${this.msgId}.pdf`,
|
url: name ?? `${this.msgId}.pdf`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] as ChatCompletionMessageContent,
|
] as ChatCompletionMessageContent,
|
||||||
@ -163,6 +163,7 @@ export class MessageRequestBuilder {
|
|||||||
return {
|
return {
|
||||||
id: this.msgId,
|
id: this.msgId,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
|
attachments: [],
|
||||||
threadId: this.thread.id,
|
threadId: this.thread.id,
|
||||||
messages: this.normalizeMessages(this.messages),
|
messages: this.normalizeMessages(this.messages),
|
||||||
model: this.model,
|
model: this.model,
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
|
import {
|
||||||
import { ChatCompletionRole, MessageStatus } from '@janhq/core'
|
ChatCompletionRole,
|
||||||
|
MessageRequestType,
|
||||||
|
MessageStatus,
|
||||||
|
} from '@janhq/core'
|
||||||
|
|
||||||
import { ThreadMessageBuilder } from './threadMessageBuilder'
|
import { ThreadMessageBuilder } from './threadMessageBuilder'
|
||||||
import { MessageRequestBuilder } from './messageRequestBuilder'
|
import { MessageRequestBuilder } from './messageRequestBuilder'
|
||||||
|
|
||||||
import { ContentType } from '@janhq/core';
|
import { ContentType } from '@janhq/core'
|
||||||
describe('ThreadMessageBuilder', () => {
|
describe('ThreadMessageBuilder', () => {
|
||||||
it('testBuildMethod', () => {
|
it('testBuildMethod', () => {
|
||||||
const msgRequest = new MessageRequestBuilder(
|
const msgRequest = new MessageRequestBuilder(
|
||||||
'type',
|
MessageRequestType.Thread,
|
||||||
{ model: 'model' },
|
{ model: 'model' } as any,
|
||||||
{ id: 'thread-id' },
|
{ id: 'thread-id' } as any,
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
const builder = new ThreadMessageBuilder(msgRequest)
|
const builder = new ThreadMessageBuilder(msgRequest)
|
||||||
@ -29,14 +32,14 @@ import { ContentType } from '@janhq/core';
|
|||||||
|
|
||||||
it('testPushMessageWithPromptOnly', () => {
|
it('testPushMessageWithPromptOnly', () => {
|
||||||
const msgRequest = new MessageRequestBuilder(
|
const msgRequest = new MessageRequestBuilder(
|
||||||
'type',
|
MessageRequestType.Thread,
|
||||||
{ model: 'model' },
|
{ model: 'model' } as any,
|
||||||
{ id: 'thread-id' },
|
{ id: 'thread-id' } as any,
|
||||||
[]
|
[]
|
||||||
);
|
)
|
||||||
const builder = new ThreadMessageBuilder(msgRequest);
|
const builder = new ThreadMessageBuilder(msgRequest)
|
||||||
const prompt = 'test prompt';
|
const prompt = 'test prompt'
|
||||||
builder.pushMessage(prompt, undefined, []);
|
builder.pushMessage(prompt, undefined, undefined)
|
||||||
expect(builder.content).toEqual([
|
expect(builder.content).toEqual([
|
||||||
{
|
{
|
||||||
type: ContentType.Text,
|
type: ContentType.Text,
|
||||||
@ -45,56 +48,53 @@ import { ContentType } from '@janhq/core';
|
|||||||
annotations: [],
|
annotations: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
])
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
||||||
it('testPushMessageWithPdf', () => {
|
it('testPushMessageWithPdf', () => {
|
||||||
const msgRequest = new MessageRequestBuilder(
|
const msgRequest = new MessageRequestBuilder(
|
||||||
'type',
|
MessageRequestType.Thread,
|
||||||
{ model: 'model' },
|
{ model: 'model' } as any,
|
||||||
{ id: 'thread-id' },
|
{ id: 'thread-id' } as any,
|
||||||
[]
|
[]
|
||||||
);
|
)
|
||||||
const builder = new ThreadMessageBuilder(msgRequest);
|
const builder = new ThreadMessageBuilder(msgRequest)
|
||||||
const prompt = 'test prompt';
|
const prompt = 'test prompt'
|
||||||
const base64 = 'test base64';
|
const base64 = 'test base64'
|
||||||
const fileUpload = [{ type: 'pdf', file: { name: 'test.pdf', size: 1000 } }];
|
const fileUpload = [
|
||||||
builder.pushMessage(prompt, base64, fileUpload);
|
{ type: 'pdf', file: { name: 'test.pdf', size: 1000 } },
|
||||||
|
] as any
|
||||||
|
builder.pushMessage(prompt, base64, fileUpload)
|
||||||
expect(builder.content).toEqual([
|
expect(builder.content).toEqual([
|
||||||
{
|
{
|
||||||
type: ContentType.Pdf,
|
type: ContentType.Text,
|
||||||
text: {
|
text: {
|
||||||
value: prompt,
|
value: prompt,
|
||||||
annotations: [base64],
|
annotations: [],
|
||||||
name: fileUpload[0].file.name,
|
|
||||||
size: fileUpload[0].file.size,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
])
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
||||||
it('testPushMessageWithImage', () => {
|
it('testPushMessageWithImage', () => {
|
||||||
const msgRequest = new MessageRequestBuilder(
|
const msgRequest = new MessageRequestBuilder(
|
||||||
'type',
|
MessageRequestType.Thread,
|
||||||
{ model: 'model' },
|
{ model: 'model' } as any,
|
||||||
{ id: 'thread-id' },
|
{ id: 'thread-id' } as any,
|
||||||
[]
|
[]
|
||||||
);
|
)
|
||||||
const builder = new ThreadMessageBuilder(msgRequest);
|
const builder = new ThreadMessageBuilder(msgRequest)
|
||||||
const prompt = 'test prompt';
|
const prompt = 'test prompt'
|
||||||
const base64 = 'test base64';
|
const base64 = 'test base64'
|
||||||
const fileUpload = [{ type: 'image', file: { name: 'test.jpg', size: 1000 } }];
|
const fileUpload = [{ type: 'image', file: { name: 'test.jpg', size: 1000 } }]
|
||||||
builder.pushMessage(prompt, base64, fileUpload);
|
builder.pushMessage(prompt, base64, fileUpload as any)
|
||||||
expect(builder.content).toEqual([
|
expect(builder.content).toEqual([
|
||||||
{
|
{
|
||||||
type: ContentType.Image,
|
type: ContentType.Text,
|
||||||
text: {
|
text: {
|
||||||
value: prompt,
|
value: prompt,
|
||||||
annotations: [base64],
|
annotations: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
])
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Attachment,
|
||||||
ChatCompletionRole,
|
ChatCompletionRole,
|
||||||
ContentType,
|
ContentType,
|
||||||
MessageStatus,
|
MessageStatus,
|
||||||
@ -14,6 +15,7 @@ export class ThreadMessageBuilder {
|
|||||||
messageRequest: MessageRequestBuilder
|
messageRequest: MessageRequestBuilder
|
||||||
|
|
||||||
content: ThreadContent[] = []
|
content: ThreadContent[] = []
|
||||||
|
attachments: Attachment[] = []
|
||||||
|
|
||||||
constructor(messageRequest: MessageRequestBuilder) {
|
constructor(messageRequest: MessageRequestBuilder) {
|
||||||
this.messageRequest = messageRequest
|
this.messageRequest = messageRequest
|
||||||
@ -24,6 +26,7 @@ export class ThreadMessageBuilder {
|
|||||||
return {
|
return {
|
||||||
id: this.messageRequest.msgId,
|
id: this.messageRequest.msgId,
|
||||||
thread_id: this.messageRequest.thread.id,
|
thread_id: this.messageRequest.thread.id,
|
||||||
|
attachments: this.attachments,
|
||||||
role: ChatCompletionRole.User,
|
role: ChatCompletionRole.User,
|
||||||
status: MessageStatus.Ready,
|
status: MessageStatus.Ready,
|
||||||
created: timestamp,
|
created: timestamp,
|
||||||
@ -36,31 +39,9 @@ export class ThreadMessageBuilder {
|
|||||||
pushMessage(
|
pushMessage(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
base64: string | undefined,
|
base64: string | undefined,
|
||||||
fileUpload: FileInfo[]
|
fileUpload?: FileInfo
|
||||||
) {
|
) {
|
||||||
if (base64 && fileUpload[0]?.type === 'image') {
|
if (prompt) {
|
||||||
this.content.push({
|
|
||||||
type: ContentType.Image,
|
|
||||||
text: {
|
|
||||||
value: prompt,
|
|
||||||
annotations: [base64],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (base64 && fileUpload[0]?.type === 'pdf') {
|
|
||||||
this.content.push({
|
|
||||||
type: ContentType.Pdf,
|
|
||||||
text: {
|
|
||||||
value: prompt,
|
|
||||||
annotations: [base64],
|
|
||||||
name: fileUpload[0].file.name,
|
|
||||||
size: fileUpload[0].file.size,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prompt && !base64) {
|
|
||||||
this.content.push({
|
this.content.push({
|
||||||
type: ContentType.Text,
|
type: ContentType.Text,
|
||||||
text: {
|
text: {
|
||||||
@ -69,6 +50,26 @@ export class ThreadMessageBuilder {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (base64 && fileUpload?.type === 'image') {
|
||||||
|
this.content.push({
|
||||||
|
type: ContentType.Image,
|
||||||
|
image_url: {
|
||||||
|
url: base64,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base64 && fileUpload?.type === 'pdf') {
|
||||||
|
this.attachments.push({
|
||||||
|
file_id: fileUpload.id,
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
type: 'file_search',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user