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

fix: attachment preview issue #4282

Merged
merged 3 commits into from
Dec 17, 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
4 changes: 2 additions & 2 deletions core/src/browser/extensions/engines/OAIEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
*/
override async inference(data: MessageRequest) {
if (!data.model?.id) {
events.emit(MessageEvent.OnMessageResponse, {

Check warning on line 59 in core/src/browser/extensions/engines/OAIEngine.ts

View workflow job for this annotation

GitHub Actions / coverage-check

59 line is not covered with tests
status: MessageStatus.Error,
content: [
{
Expand All @@ -68,7 +68,7 @@
},
],
})
return

Check warning on line 71 in core/src/browser/extensions/engines/OAIEngine.ts

View workflow job for this annotation

GitHub Actions / coverage-check

71 line is not covered with tests
}

const timestamp = Date.now()
Expand All @@ -80,8 +80,8 @@
role: ChatCompletionRole.Assistant,
content: [],
status: MessageStatus.Pending,
created: timestamp,
updated: timestamp,
created_at: timestamp,
completed_at: timestamp,
object: 'thread.message',
}

Expand All @@ -105,7 +105,7 @@
...model.parameters,
}
if (this.transformPayload) {
requestBody = this.transformPayload(requestBody)

Check warning on line 108 in core/src/browser/extensions/engines/OAIEngine.ts

View workflow job for this annotation

GitHub Actions / coverage-check

108 line is not covered with tests
}

requestInference(
Expand Down Expand Up @@ -133,9 +133,9 @@
},
error: async (err: any) => {
if (this.isCancelled || message.content.length) {
message.status = MessageStatus.Stopped
events.emit(MessageEvent.OnMessageUpdate, message)
return

Check warning on line 138 in core/src/browser/extensions/engines/OAIEngine.ts

View workflow job for this annotation

GitHub Actions / coverage-check

136-138 lines are not covered with tests
}
message.status = MessageStatus.Error
message.content[0] = {
Expand Down Expand Up @@ -163,6 +163,6 @@
* Headers for the inference request
*/
async headers(): Promise<HeadersInit> {
return {}

Check warning on line 166 in core/src/browser/extensions/engines/OAIEngine.ts

View workflow job for this annotation

GitHub Actions / coverage-check

166 line is not covered with tests
}
}
4 changes: 2 additions & 2 deletions core/src/node/api/restful/helper/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@
try {
return JSON.parse(result)
} catch (err) {
console.error(err)

Check warning on line 44 in core/src/node/api/restful/helper/builder.ts

View workflow job for this annotation

GitHub Actions / coverage-check

44 line is not covered with tests
}
})
.filter((e: any) => !!e)

return modelData
} catch (err) {
console.error(err)
return []

Check warning on line 52 in core/src/node/api/restful/helper/builder.ts

View workflow job for this annotation

GitHub Actions / coverage-check

51-52 lines are not covered with tests
}
}

Expand All @@ -57,7 +57,7 @@
if (existsSync(path)) {
return readFileSync(path, 'utf-8')
} else {
return undefined

Check warning on line 60 in core/src/node/api/restful/helper/builder.ts

View workflow job for this annotation

GitHub Actions / coverage-check

60 line is not covered with tests
}
}

Expand All @@ -84,8 +84,8 @@

const messageFilePath = join(threadDirPath, messageFile)
if (!existsSync(messageFilePath)) {
console.debug('message file not found')
return []

Check warning on line 88 in core/src/node/api/restful/helper/builder.ts

View workflow job for this annotation

GitHub Actions / coverage-check

87-88 lines are not covered with tests
}

const lines = readFileSync(messageFilePath, 'utf-8')
Expand All @@ -99,8 +99,8 @@
})
return messages
} catch (err) {
console.error(err)
return []

Check warning on line 103 in core/src/node/api/restful/helper/builder.ts

View workflow job for this annotation

GitHub Actions / coverage-check

102-103 lines are not covered with tests
}
}

Expand Down Expand Up @@ -194,8 +194,8 @@
id: msgId,
thread_id: threadId,
status: MessageStatus.Ready,
created: createdAt,
updated: createdAt,
created_at: createdAt,
completed_at: createdAt,
object: 'thread.message',
role: message.role,
content: [
Expand Down
4 changes: 2 additions & 2 deletions core/src/types/message/messageEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export type ThreadMessage = {
/** The status of this message. **/
status: MessageStatus
/** The timestamp indicating when this message was created. Represented in Unix time. **/
created: number
created_at: number
/** The timestamp indicating when this message was updated. Represented in Unix time. **/
updated: number
completed_at: number
/** The additional metadata of this message. **/
metadata?: Record<string, unknown>

Expand Down
26 changes: 19 additions & 7 deletions web/hooks/usePath.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { openFileExplorer, joinPath, baseName } from '@janhq/core'
import { openFileExplorer, joinPath, baseName, fs } from '@janhq/core'

Check warning on line 1 in web/hooks/usePath.ts

View workflow job for this annotation

GitHub Actions / test-on-macos

'fs' is defined but never used

Check warning on line 1 in web/hooks/usePath.ts

View workflow job for this annotation

GitHub Actions / test-on-windows-pr

'fs' is defined but never used

Check warning on line 1 in web/hooks/usePath.ts

View workflow job for this annotation

GitHub Actions / test-on-ubuntu

'fs' is defined but never used

Check warning on line 1 in web/hooks/usePath.ts

View workflow job for this annotation

GitHub Actions / coverage-check

'fs' is defined but never used
import { useAtomValue } from 'jotai'

import { getFileInfo } from '@/utils/file'

import { janDataFolderPathAtom } from '@/helpers/atoms/AppConfig.atom'
import { activeAssistantAtom } from '@/helpers/atoms/Assistant.atom'
import { selectedModelAtom } from '@/helpers/atoms/Model.atom'
Expand Down Expand Up @@ -47,13 +49,23 @@
const onViewFile = async (id: string) => {
if (!activeThread) return

let filePath = undefined

id = await baseName(id)
filePath = await joinPath(['threads', `${activeThread.id}/files`, `${id}`])
if (!filePath) return
const fullPath = await joinPath([janDataFolderPath, filePath])
openFileExplorer(fullPath)

// New ID System
if (!id.startsWith('file-')) {
const threadFilePath = await joinPath([
janDataFolderPath,
'threads',
`${activeThread.id}/files`,
id,
])
openFileExplorer(threadFilePath)
} else {
id = id.split('.')[0]
const fileName = (await getFileInfo(id)).filename
const filesPath = await joinPath([janDataFolderPath, 'files', fileName])
openFileExplorer(filesPath)
}
}

const onViewFileContainer = async () => {
Expand Down
2 changes: 1 addition & 1 deletion web/hooks/useSendChatMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export default function useSendChatMessage() {
// Update thread state
const updatedThread: Thread = {
...activeThreadRef.current,
updated: newMessage.created,
updated: newMessage.created_at,
metadata: {
...activeThreadRef.current.metadata,
lastMessage: prompt,
Expand Down
2 changes: 1 addition & 1 deletion web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const ChatInput = () => {
const renderPreview = (fileUpload: any) => {
if (fileUpload) {
if (fileUpload.type === 'image') {
return <ImageUploadPreview file={fileUpload[0].file} />
return <ImageUploadPreview file={fileUpload.file} />
} else {
return <FileUploadPreview />
}
Expand Down
47 changes: 21 additions & 26 deletions web/screens/Thread/ThreadCenterPanel/TextMessage/DocMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,39 @@
import { memo } from 'react'

import { Tooltip } from '@janhq/joi'

import { FolderOpenIcon } from 'lucide-react'
import { memo, useEffect, useState } from 'react'

import { usePath } from '@/hooks/usePath'

import { toGibibytes } from '@/utils/converter'
import { openFileTitle } from '@/utils/titleUtils'
import { getFileInfo } from '@/utils/file'

import Icon from '../FileUploadPreview/Icon'

const DocMessage = ({ id, name }: { id: string; name?: string }) => {
const { onViewFile, onViewFileContainer } = usePath()
const DocMessage = ({ id }: { id: string }) => {
const { onViewFile } = usePath()
const [fileInfo, setFileInfo] = useState<
{ filename: string; id: string } | undefined
>()
useEffect(() => {
if (!fileInfo) {
getFileInfo(id).then((data) => {
setFileInfo(data)
})
}
}, [fileInfo, id])

return (
<div className="group/file bg-secondary relative mb-2 inline-flex w-60 cursor-pointer gap-x-3 overflow-hidden rounded-lg 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"
className="absolute left-0 top-0 z-20 hidden h-full w-full bg-black/20 opacity-50 group-hover/file:inline-block"
onClick={() => onViewFile(`${id}.pdf`)}
/>
<Tooltip
trigger={
<div
className="absolute right-2 top-2 z-20 hidden h-8 w-8 cursor-pointer items-center justify-center rounded-md bg-[hsla(var(--app-bg))] group-hover/file:flex"
onClick={onViewFileContainer}
>
<FolderOpenIcon size={20} />
</div>
}
content={<span>{openFileTitle()}</span>}
/>

<Icon type="pdf" />
<div className="w-full">
<h6 className="line-clamp-1 w-4/5 font-medium">
{name?.replaceAll(/[-._]/g, ' ')}
<h6 className="line-clamp-1 w-4/5 overflow-hidden font-medium">
{fileInfo?.filename}
</h6>
{/* <p className="text-[hsla(var(--text-secondary)]">
{toGibibytes(Number(size))}
</p> */}
<p className="text-[hsla(var(--text-secondary)] line-clamp-1 overflow-hidden truncate">
{fileInfo?.id ?? id}
</p>
</div>
</div>
)
Expand Down
27 changes: 2 additions & 25 deletions web/screens/Thread/ThreadCenterPanel/TextMessage/ImageMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,11 @@
import { memo } from 'react'

import { Tooltip } from '@janhq/joi'

import { FolderOpenIcon } from 'lucide-react'

import { usePath } from '@/hooks/usePath'

import { openFileTitle } from '@/utils/titleUtils'

import { RelativeImage } from '../TextMessage/RelativeImage'

const ImageMessage = ({ image }: { image: string }) => {
const { onViewFile, onViewFileContainer } = usePath()

return (
<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">
<RelativeImage src={image} onClick={() => onViewFile(image)} />
</div>
<Tooltip
trigger={
<div
className="absolute right-2 top-2 z-20 hidden h-8 w-8 cursor-pointer items-center justify-center rounded-md bg-[hsla(var(--app-bg))] group-hover/image:flex"
onClick={onViewFileContainer}
>
<FolderOpenIcon size={20} />
</div>
}
content={<span>{openFileTitle()}</span>}
/>
<div className="group/file relative mb-2 inline-flex overflow-hidden rounded-xl">
<RelativeImage src={image} />
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const RelativeImage = ({
onClick,
}: {
src: string
onClick: () => void
onClick?: () => void
}) => {
const [path, setPath] = useState<string>('')

Expand All @@ -17,9 +17,12 @@ export const RelativeImage = ({
})
}, [])
return (
<button onClick={onClick}>
<button
onClick={onClick}
className={onClick ? 'cursor-pointer' : 'cursor-default'}
>
<img
className="aspect-auto h-[300px] cursor-pointer"
className="aspect-auto h-[300px]"
alt={src}
src={src.includes('files/') ? `file://${path}/${src}` : src}
/>
Expand Down
6 changes: 4 additions & 2 deletions web/screens/Thread/ThreadCenterPanel/TextMessage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const MessageContainer: React.FC<
: (activeAssistant?.assistant_name ?? props.role)}
</div>
<p className="text-xs font-medium text-gray-400">
{props.created && displayDate(props.created ?? new Date())}
{props.created_at && displayDate(props.created_at ?? new Date())}
</p>
</div>

Expand Down Expand Up @@ -125,7 +125,9 @@ const MessageContainer: React.FC<
>
<>
{image && <ImageMessage image={image} />}
{attachedFile && <DocMessage id={props.id} name={props.id} />}
{attachedFile && (
<DocMessage id={props.attachments?.[0]?.file_id ?? props.id} />
)}

{editMessage === props.id ? (
<div>
Expand Down
39 changes: 20 additions & 19 deletions web/utils/datetime.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import { displayDate } from './datetime'
import { isToday } from './datetime'

import { displayDate } from './datetime';
import { isToday } from './datetime';

test('should return only time for today\'s timestamp', () => {
const today = new Date();
const timestamp = today.getTime();
const expectedTime = today.toLocaleTimeString(undefined, {
test("should return only time for today's timestamp", () => {
const today = new Date()
const timestamp = today.getTime()
const expectedTime = `${today.toLocaleDateString(undefined, {
day: '2-digit',
month: 'short',
year: 'numeric',
})}, ${today.toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true,
});
expect(displayDate(timestamp)).toBe(expectedTime);
});

})}`
expect(displayDate(timestamp / 1000)).toBe(expectedTime)
})

test('should return N/A for undefined timestamp', () => {
expect(displayDate()).toBe('N/A');
});

expect(displayDate()).toBe('N/A')
})

test('should return true for today\'s timestamp', () => {
const today = new Date();
const timestamp = today.setHours(0, 0, 0, 0);
expect(isToday(timestamp)).toBe(true);
});
test("should return true for today's timestamp", () => {
const today = new Date()
const timestamp = today.setHours(0, 0, 0, 0)
expect(isToday(timestamp)).toBe(true)
})
5 changes: 4 additions & 1 deletion web/utils/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ export const isToday = (timestamp: number) => {
export const displayDate = (timestamp?: string | number | Date) => {
if (!timestamp) return 'N/A'

const date = new Date(timestamp)
const date =
typeof timestamp === 'number'
? new Date(timestamp * 1000)
: new Date(timestamp)

let displayDate = `${date.toLocaleDateString(undefined, {
day: '2-digit',
Expand Down
9 changes: 9 additions & 0 deletions web/utils/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,12 @@ export const uploader = () => {
})
return uppy
}

/**
* Get the file information from the server.
*/
export const getFileInfo = (id: string) => {
return fetch(`${API_BASE_URL}/v1/files/${id}`)
.then((e) => e.json())
.catch(() => undefined)
}
4 changes: 2 additions & 2 deletions web/utils/threadMessageBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ describe('ThreadMessageBuilder', () => {
expect(result.thread_id).toBe(msgRequest.thread.id)
expect(result.role).toBe(ChatCompletionRole.User)
expect(result.status).toBe(MessageStatus.Ready)
expect(result.created).toBeDefined()
expect(result.updated).toBeDefined()
expect(result.created_at).toBeDefined()
expect(result.completed_at).toBeDefined()
expect(result.object).toBe('thread.message')
expect(result.content).toEqual([])
})
Expand Down
4 changes: 2 additions & 2 deletions web/utils/threadMessageBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export class ThreadMessageBuilder {
attachments: this.attachments,
role: ChatCompletionRole.User,
status: MessageStatus.Ready,
created: timestamp,
updated: timestamp,
created_at: timestamp,
completed_at: timestamp,
object: 'thread.message',
content: this.content,
}
Expand Down
Loading