no message
This commit is contained in:
parent
ff13a53022
commit
cd29687ce0
24
package.json
24
package.json
@ -27,15 +27,20 @@
|
|||||||
"@solid-primitives/scheduled": "^1.3.2",
|
"@solid-primitives/scheduled": "^1.3.2",
|
||||||
"@solid-primitives/scroll": "^2.0.14",
|
"@solid-primitives/scroll": "^2.0.14",
|
||||||
"@unocss/reset": "^0.50.6",
|
"@unocss/reset": "^0.50.6",
|
||||||
"@zag-js/dialog": "^0.9.2",
|
|
||||||
"@zag-js/menu": "^0.9.2",
|
"@zag-js/checkbox": "^0.10.3",
|
||||||
"@zag-js/select": "^0.9.2",
|
"@zag-js/dialog": "^0.10.3",
|
||||||
"@zag-js/slider": "^0.9.2",
|
"@zag-js/menu": "^0.10.3",
|
||||||
"@zag-js/solid": "^0.9.2",
|
"@zag-js/select": "^0.10.3",
|
||||||
"@zag-js/switch": "^0.9.2",
|
"@zag-js/slider": "^0.10.3",
|
||||||
"@zag-js/toast": "^0.9.2",
|
"@zag-js/solid": "^0.10.3",
|
||||||
"@zag-js/toggle": "^0.9.2",
|
"@zag-js/switch": "^0.10.3",
|
||||||
"@zag-js/tooltip": "^0.9.2",
|
"@zag-js/tabs": "^0.10.3",
|
||||||
|
"@zag-js/toast": "^0.10.3",
|
||||||
|
"@zag-js/toggle": "^0.10.3",
|
||||||
|
"@zag-js/tooltip": "^0.10.3",
|
||||||
|
|
||||||
|
|
||||||
"astro": "^2.2.0",
|
"astro": "^2.2.0",
|
||||||
"bumpp": "^9.1.0",
|
"bumpp": "^9.1.0",
|
||||||
"destr": "^1.2.2",
|
"destr": "^1.2.2",
|
||||||
@ -69,6 +74,7 @@
|
|||||||
"eslint-plugin-astro": "^0.24.0",
|
"eslint-plugin-astro": "^0.24.0",
|
||||||
"lint-staged": "^13.2.2",
|
"lint-staged": "^13.2.2",
|
||||||
"punycode": "^2.3.0",
|
"punycode": "^2.3.0",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
"simple-git-hooks": "^2.8.1",
|
"simple-git-hooks": "^2.8.1",
|
||||||
"unocss": "^0.50.6",
|
"unocss": "^0.50.6",
|
||||||
"vite-plugin-pwa": "^0.14.7"
|
"vite-plugin-pwa": "^0.14.7"
|
||||||
|
@ -2,12 +2,16 @@ import {
|
|||||||
showConversationEditModal,
|
showConversationEditModal,
|
||||||
showConversationSidebar,
|
showConversationSidebar,
|
||||||
showEmojiPickerModal,
|
showEmojiPickerModal,
|
||||||
|
showSelectMessageModal,
|
||||||
showSettingsSidebar,
|
showSettingsSidebar,
|
||||||
|
showShareModal,
|
||||||
} from '@/stores/ui'
|
} from '@/stores/ui'
|
||||||
import ConversationSidebar from './conversations/ConversationSidebar'
|
import ConversationSidebar from './conversations/ConversationSidebar'
|
||||||
import SettingsSidebar from './settings/SettingsSidebar'
|
import SettingsSidebar from './settings/SettingsSidebar'
|
||||||
import ConversationEditModal from './conversations/ConversationEditModal'
|
import ConversationEditModal from './conversations/ConversationEditModal'
|
||||||
import EmojiPickerModal from './ui/EmojiPickerModal'
|
import EmojiPickerModal from './ui/EmojiPickerModal'
|
||||||
|
import ShareModal from './ui/ShareModal'
|
||||||
|
import SelectMessageModal from './ui/SelectMessageModal'
|
||||||
import Modal from './ui/Modal'
|
import Modal from './ui/Modal'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
@ -33,6 +37,16 @@ export default () => {
|
|||||||
<EmojiPickerModal />
|
<EmojiPickerModal />
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<Modal bindValue={showShareModal} direction="bottom" closeBtnClass="hidden">
|
||||||
|
<div class="max-h-[70vh] w-full">
|
||||||
|
<ShareModal />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
<Modal bindValue={showSelectMessageModal} direction="bottom" closeBtnClass="top-4 right-4" closeCallback={() => { showShareModal.set(true) }}>
|
||||||
|
<div class="max-h-[70vh] w-full">
|
||||||
|
<SelectMessageModal />
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ export default () => {
|
|||||||
<>
|
<>
|
||||||
{ $currentConversationId() && (
|
{ $currentConversationId() && (
|
||||||
<div class="fcc p-2 rounded-md text-xl hv-foreground" onClick={() => { handleShareMessage(true) }} >
|
<div class="fcc p-2 rounded-md text-xl hv-foreground" onClick={() => { handleShareMessage(true) }} >
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-3 h-3">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:1rem;height:1rem" class="w-3 h-3">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
23
src/components/header/ConversationMessageSettingButton.tsx
Normal file
23
src/components/header/ConversationMessageSettingButton.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useStore } from '@nanostores/solid'
|
||||||
|
import { showConversationEditModal } from '@/stores/ui'
|
||||||
|
import { currentConversationId } from '@/stores/conversation'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
// Retrieve the current conversation ID from the store
|
||||||
|
const $currentConversationId = useStore(currentConversationId)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Render the following code if the current conversation ID exists */}
|
||||||
|
{$currentConversationId() && (
|
||||||
|
<div
|
||||||
|
class="fcc p-2 rounded-md text-xl hv-foreground"
|
||||||
|
onClick={() => { showConversationEditModal.set(true) }}
|
||||||
|
>
|
||||||
|
{/* Render the carbon settings adjust icon */}
|
||||||
|
<div i-carbon-settings-adjust />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
29
src/components/header/ConversationMessageShareButton.tsx
Normal file
29
src/components/header/ConversationMessageShareButton.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { useStore } from '@nanostores/solid'
|
||||||
|
import { currentConversationId } from '@/stores/conversation'
|
||||||
|
import { showShareModal } from '@/stores/ui'
|
||||||
|
import { getMessagesByConversationId, updateMessage } from '@/stores/messages'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const $currentConversationId = useStore(currentConversationId)
|
||||||
|
const handleShareContext = () => {
|
||||||
|
const messages = getMessagesByConversationId($currentConversationId())
|
||||||
|
messages.forEach((message) => {
|
||||||
|
updateMessage($currentConversationId(), message.id, { isSelected: true },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
showShareModal.set(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{$currentConversationId() && (
|
||||||
|
<div
|
||||||
|
class="fcc p-2 rounded-md text-xl hv-foreground"
|
||||||
|
onClick={() => { handleShareContext() }}
|
||||||
|
>
|
||||||
|
<div i-carbon-export />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -4,6 +4,8 @@ import { useLargeScreen } from '@/hooks'
|
|||||||
import ConversationHeaderInfo from './ConversationHeaderInfo'
|
import ConversationHeaderInfo from './ConversationHeaderInfo'
|
||||||
import ConversationMessageClearButton from './ConversationMessageClearButton'
|
import ConversationMessageClearButton from './ConversationMessageClearButton'
|
||||||
import ConversationHeaderShare from './ConversationHeaderShare'
|
import ConversationHeaderShare from './ConversationHeaderShare'
|
||||||
|
import ConversationMessageShareButton from './ConversationMessageShareButton'
|
||||||
|
import ConversationMessageSettingButton from './ConversationMessageSettingButton'
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@ -26,7 +28,9 @@ export default () => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="fi gap-1 overflow-hidden">
|
<div class="fi gap-1 overflow-hidden">
|
||||||
<ConversationHeaderShare />
|
<ConversationHeaderShare />
|
||||||
|
<ConversationMessageSettingButton />
|
||||||
<ConversationMessageClearButton />
|
<ConversationMessageClearButton />
|
||||||
|
{/*<ConversationMessageShareButton />*/}
|
||||||
<div
|
<div
|
||||||
class="fcc p-2 rounded-md text-xl hv-foreground lg:hidden"
|
class="fcc p-2 rounded-md text-xl hv-foreground lg:hidden"
|
||||||
onClick={() => showSettingsSidebar.set(true)}
|
onClick={() => showSettingsSidebar.set(true)}
|
||||||
|
63
src/components/ui/SelectMessageModal.tsx
Normal file
63
src/components/ui/SelectMessageModal.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { useStore } from '@nanostores/solid'
|
||||||
|
import { For, createSignal } from 'solid-js'
|
||||||
|
import { useI18n } from '@/hooks'
|
||||||
|
import { currentConversationId } from '@/stores/conversation'
|
||||||
|
import { getMessagesByConversationId, updateMessage } from '@/stores/messages'
|
||||||
|
import { showSelectMessageModal, showShareModal } from '@/stores/ui'
|
||||||
|
import { Checkbox } from '../ui/base'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const $currentConversationId = useStore(currentConversationId)
|
||||||
|
const messages = getMessagesByConversationId($currentConversationId())
|
||||||
|
const [checkAll, setCheckAll] = createSignal(messages.every(item => item.isSelected))
|
||||||
|
const [selectedMessages, setSelectedMessages] = createSignal(messages)
|
||||||
|
|
||||||
|
const handleToggleMessages = (id: string) => {
|
||||||
|
messages.forEach((item) => {
|
||||||
|
if (item.id === id)
|
||||||
|
item.isSelected = !item.isSelected
|
||||||
|
})
|
||||||
|
setSelectedMessages(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectAll = () => {
|
||||||
|
messages.forEach((item) => {
|
||||||
|
item.isSelected = !checkAll()
|
||||||
|
})
|
||||||
|
setSelectedMessages(messages)
|
||||||
|
setCheckAll(!checkAll())
|
||||||
|
console.log(selectedMessages(), checkAll())
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveContext = () => {
|
||||||
|
messages.forEach((item) => {
|
||||||
|
updateMessage($currentConversationId(), item.id, { isSelected: item.isSelected })
|
||||||
|
})
|
||||||
|
showSelectMessageModal.set(false)
|
||||||
|
showShareModal.set(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="fi px-6 py-4 border-base b-b-1">
|
||||||
|
<div class="text-base">{t('conversations.share.messages.title')}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col p-6 h-100 overflow-auto relative">
|
||||||
|
{/* <div class="border border-base b-b-0 last:b-b-1 p-4 hv-base">
|
||||||
|
<Checkbox setValue={() => handleSelectAll()} initValue={checkAll()} label={`${t('conversations.share.messages.selectAll')}`} />
|
||||||
|
</div> */}
|
||||||
|
<For each={selectedMessages()}>
|
||||||
|
{(item) => {
|
||||||
|
return (
|
||||||
|
<div class="border border-base b-b-0 last:b-b-1 p-4 hv-base">
|
||||||
|
<Checkbox setValue={() => handleToggleMessages(item.id)} initValue={item.isSelected} label={`${item.role}: ${item.content}`} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
<div class="fcc px-2 py-2 bg-darker border border-base hv-base hover:border-base-100" onClick={() => handleSaveContext()}>{t('settings.save')}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
120
src/components/ui/ShareModal.tsx
Normal file
120
src/components/ui/ShareModal.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { useStore } from '@nanostores/solid'
|
||||||
|
import { For, Show, createSignal } from 'solid-js'
|
||||||
|
import html2canvas from 'html2canvas'
|
||||||
|
import { useClipboardCopy, useI18n } from '@/hooks'
|
||||||
|
import { currentConversationId } from '@/stores/conversation'
|
||||||
|
import { getMessagesByConversationId } from '@/stores/messages'
|
||||||
|
import { showSelectMessageModal, showShareModal } from '@/stores/ui'
|
||||||
|
import { Tabs } from '../ui/base'
|
||||||
|
import type { TabItem } from './base/Tabs'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const $currentConversationId = useStore(currentConversationId)
|
||||||
|
const messages = getMessagesByConversationId($currentConversationId()).filter(item => item.isSelected)
|
||||||
|
const [imageUrl, setImageUrl] = createSignal('')
|
||||||
|
const [imageBuffer, setImageBuffer] = createSignal<Blob>()
|
||||||
|
const [loading, setLoading] = createSignal(false)
|
||||||
|
|
||||||
|
const [copied, copy] = useClipboardCopy(messages.map(item => `${item.role}: ${item.content}`).join('\n'))
|
||||||
|
|
||||||
|
const copyImage = () => {
|
||||||
|
const [,copy] = useClipboardCopy(imageBuffer()!)
|
||||||
|
copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLoadImage = async() => {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
const messageWrapper = document.getElementById('message_list_wrapper') as HTMLDivElement
|
||||||
|
messageWrapper.style.display = 'block'
|
||||||
|
const canvas = await html2canvas(messageWrapper, {
|
||||||
|
useCORS: true,
|
||||||
|
})
|
||||||
|
messageWrapper.style.display = 'none'
|
||||||
|
canvas.toBlob((res) => {
|
||||||
|
if (res) {
|
||||||
|
setImageBuffer(res)
|
||||||
|
const url = URL.createObjectURL(res)
|
||||||
|
setLoading(false)
|
||||||
|
setImageUrl(url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs: TabItem[] = [
|
||||||
|
{
|
||||||
|
value: 'context',
|
||||||
|
label: t('conversations.share.tabs.context'),
|
||||||
|
content: <div class="flex flex-col gap-2">
|
||||||
|
{messages.length
|
||||||
|
? (
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="emerald-light-button mt-0 cursor-pointer mb-2" onClick={() => copy()}>{copied() ? t('copyed') : t('conversations.share.copy')}</div>
|
||||||
|
<For each={messages}>
|
||||||
|
{item => (
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<div class="font-bold w-20 text-left">{item.role}:</div>
|
||||||
|
<div class="text-left flex-1 whitespace-normal overflow-auto">{item.content}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: <div class="text-center text-sm">{t('empty')}</div>}
|
||||||
|
</div>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'image',
|
||||||
|
label: t('conversations.share.tabs.image'),
|
||||||
|
content: <div class="flex flex-col gap-2">
|
||||||
|
{messages.length
|
||||||
|
? (
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="inline-block text-left">
|
||||||
|
<Show when={imageUrl().length}>
|
||||||
|
<div class="button inline-block mt-0 cursor-pointer mb-2" onClick={() => { copyImage() }}>{t('conversations.share.image.copy')}</div>
|
||||||
|
<div class="button inline-block mt-0 cursor-pointer mb-2 ml-2" onClick={() => { window.open(imageUrl()) }}>{t('conversations.share.image.open')}</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={!imageUrl().length}>
|
||||||
|
<div class="emerald-light-button inline-block mt-0 cursor-pointer mb-2" onClick={() => handleLoadImage()}>{loading() ? t('conversations.share.image.loading') : t('conversations.share.image.btn')}</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<Show when={imageUrl().length}>
|
||||||
|
<img src={imageUrl()} alt="" />
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: <div class="text-center text-sm">{t('empty')}</div>}
|
||||||
|
</div>,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="fi justify-between border-base b-b-1 px-6 py-4">
|
||||||
|
<div class="text-base">{t('conversations.share.title')}</div>
|
||||||
|
{/* TODO */}
|
||||||
|
{/* <button class="button mt-0">{t('conversations.share.link.create')}</button> */}
|
||||||
|
</div>
|
||||||
|
<div class="fcc flex-col space-y-2 p-6">
|
||||||
|
<div
|
||||||
|
class="border w-full border-base fi justify-between box-border p-4 rounded-md hv-base"
|
||||||
|
onclick={() => {
|
||||||
|
showSelectMessageModal.set(true)
|
||||||
|
showShareModal.set(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="text-xs">{t('conversations.share.messages.selected')}</span>
|
||||||
|
<span class="text-xs op-60">{messages.length ? `${messages.length} Messages` : t('conversations.share.messages.title')}</span>
|
||||||
|
</div>
|
||||||
|
<Tabs tabs={tabs} sticky tabClass="bg-base-100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
34
src/components/ui/base/Checkbox.tsx
Normal file
34
src/components/ui/base/Checkbox.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import * as checkbox from '@zag-js/checkbox'
|
||||||
|
import { normalizeProps, useMachine } from '@zag-js/solid'
|
||||||
|
import { createMemo, createUniqueId } from 'solid-js'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initValue?: boolean
|
||||||
|
label: string
|
||||||
|
setValue: (v?: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Checkbox = (props: Props) => {
|
||||||
|
const [state, send] = useMachine(checkbox.machine({
|
||||||
|
id: createUniqueId(),
|
||||||
|
checked: props.initValue ?? false,
|
||||||
|
onChange(detail) {
|
||||||
|
props.setValue(detail.checked as boolean)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const api = createMemo(() => checkbox.connect(state, send, normalizeProps))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label {...api().rootProps}>
|
||||||
|
<div class="fi justify-revert cursor-pointer text-sm">
|
||||||
|
<input {...api().inputProps} />
|
||||||
|
{api().isChecked ? <div class="i-carbon:checkbox-checked text-xl" /> : <div class="i-carbon:checkbox text-xl" />}
|
||||||
|
<div {...api().labelProps} class="ml-2 truncate flex-1">
|
||||||
|
{props.label}
|
||||||
|
</div>
|
||||||
|
<div {...api().controlProps} />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
44
src/components/ui/base/Tabs.tsx
Normal file
44
src/components/ui/base/Tabs.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import * as tabs from '@zag-js/tabs'
|
||||||
|
import { normalizeProps, useMachine } from '@zag-js/solid'
|
||||||
|
import { For, createMemo, createUniqueId } from 'solid-js'
|
||||||
|
import type { JSX } from 'solid-js'
|
||||||
|
|
||||||
|
export interface TabItem {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
content: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
tabs: TabItem[]
|
||||||
|
initValue?: string
|
||||||
|
sticky?: boolean
|
||||||
|
tabClass?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tabs = (props: Props) => {
|
||||||
|
const [state, send] = useMachine(tabs.machine({ id: createUniqueId(), value: props.initValue ?? props.tabs[0].value }))
|
||||||
|
|
||||||
|
const api = createMemo(() => tabs.connect(state, send, normalizeProps))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...api().rootProps} class="w-full text-sm font-medium text-center">
|
||||||
|
<div {...api().tablistProps} class={`flex flex-wrap -mb-px border-b border-base ${props.sticky && 'sticky top-0 bottom-0 bg-white'} ${props.tabClass}`}>
|
||||||
|
<For each={props.tabs}>
|
||||||
|
{item => (
|
||||||
|
<button class={`inline-block p-4 border-b-2 border-transparent hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-300 cursor-pointer ${api().value === item.value && '!border-emerald-600 !text-emerald-600'}`} {...api().getTriggerProps({ value: item.value })}>
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
<For each={props.tabs}>
|
||||||
|
{item => (
|
||||||
|
<div class="w-full text-sm mt-4 border border-base p-4" {...api().getContentProps({ value: item.value })}>
|
||||||
|
{item.content}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -5,3 +5,5 @@ export * from './Select'
|
|||||||
export * from './Slider'
|
export * from './Slider'
|
||||||
export * from './Tooltip'
|
export * from './Tooltip'
|
||||||
export * from './Toggle'
|
export * from './Toggle'
|
||||||
|
export * from './Checkbox'
|
||||||
|
export * from './Tabs'
|
||||||
|
@ -24,17 +24,53 @@ export const en = {
|
|||||||
recent: 'Recents',
|
recent: 'Recents',
|
||||||
noRecent: 'No recents',
|
noRecent: 'No recents',
|
||||||
untitled: 'Untitled',
|
untitled: 'Untitled',
|
||||||
|
promopt: {
|
||||||
|
system: 'System Info',
|
||||||
|
desc: 'You are a helpful assistant, answer as concisely as possible...',
|
||||||
|
},
|
||||||
|
emoji: 'Search an emoji ~',
|
||||||
confirm: {
|
confirm: {
|
||||||
title: 'Delete all messages in this chat',
|
title: 'Delete all messages in this chat',
|
||||||
desc: 'This action cannot be undone.',
|
desc: 'This action cannot be undone.',
|
||||||
message: 'Delete this record',
|
message: 'Delete this record',
|
||||||
btn: 'confirm',
|
btn: 'confirm',
|
||||||
cancel: 'cancel',
|
cancel: 'cancel',
|
||||||
|
submit: 'submit',
|
||||||
|
},
|
||||||
|
share: {
|
||||||
|
title: 'Share Conversation',
|
||||||
|
link: {
|
||||||
|
title: 'Share with link',
|
||||||
|
copy: 'Copy Link',
|
||||||
|
create: 'Create Link',
|
||||||
|
},
|
||||||
|
save: 'Save',
|
||||||
|
copy: 'Copy Context',
|
||||||
|
messages: {
|
||||||
|
title: 'Select Message',
|
||||||
|
selected: 'Selected Messages',
|
||||||
|
selectAll: 'Select All',
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
context: 'Share Context',
|
||||||
|
image: 'Share Image',
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
btn: 'Generate Image',
|
||||||
|
open: 'Open in Tab',
|
||||||
|
loading: 'Generating...',
|
||||||
|
copy: 'Copy Image',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
docs: 'Docs',
|
||||||
|
github: 'Github',
|
||||||
|
scroll: 'Scroll to bottom',
|
||||||
|
empty: 'No data',
|
||||||
send: {
|
send: {
|
||||||
placeholder: 'Enter Something...',
|
placeholder: 'Enter Something...',
|
||||||
button: 'Send',
|
button: 'Send',
|
||||||
},
|
},
|
||||||
|
copyed: 'Copyed!',
|
||||||
},
|
},
|
||||||
} as language
|
} as language
|
||||||
|
@ -24,17 +24,53 @@ export const zhCN = {
|
|||||||
recent: '最近对话',
|
recent: '最近对话',
|
||||||
noRecent: '暂无最近对话',
|
noRecent: '暂无最近对话',
|
||||||
untitled: '未命名对话',
|
untitled: '未命名对话',
|
||||||
|
promopt: {
|
||||||
|
system: '系统信息',
|
||||||
|
desc: '你是个乐于助人的助手,回答尽量简洁...',
|
||||||
|
},
|
||||||
|
emoji: '搜索一个表情 ~',
|
||||||
confirm: {
|
confirm: {
|
||||||
title: '删除本会话的所有消息',
|
title: '删除本会话的所有消息',
|
||||||
desc: '这将删除本会话的所有消息,且不可恢复',
|
desc: '这将删除本会话的所有消息,且不可恢复',
|
||||||
message: '删除这条记录',
|
message: '删除这条记录',
|
||||||
btn: '确认',
|
btn: '确认',
|
||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
|
submit: '提交',
|
||||||
|
},
|
||||||
|
share: {
|
||||||
|
title: '分享对话',
|
||||||
|
link: {
|
||||||
|
title: '分享链接',
|
||||||
|
copy: '复制链接',
|
||||||
|
create: '创建链接',
|
||||||
|
},
|
||||||
|
save: '保存',
|
||||||
|
copy: '复制上下文',
|
||||||
|
messages: {
|
||||||
|
title: '选择消息',
|
||||||
|
selected: '已选择的消息',
|
||||||
|
selectAll: '全选',
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
context: '分享上下文',
|
||||||
|
image: '分享图片',
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
btn: '生成图片',
|
||||||
|
open: '新窗口打开',
|
||||||
|
loading: '生成中...',
|
||||||
|
copy: '复制图片',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
docs: '文档',
|
||||||
|
github: '源码',
|
||||||
|
scroll: '滚动到底部',
|
||||||
|
empty: '暂无数据',
|
||||||
send: {
|
send: {
|
||||||
placeholder: '输入内容...',
|
placeholder: '输入内容...',
|
||||||
button: '发送',
|
button: '发送',
|
||||||
},
|
},
|
||||||
|
copyed: '已拷贝!',
|
||||||
},
|
},
|
||||||
} as language
|
} as language
|
||||||
|
@ -29,11 +29,11 @@ const providerOpenAI = () => {
|
|||||||
description: '你可以随时切换支持服务商.',
|
description: '你可以随时切换支持服务商.',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'openai', label: 'openai' },
|
{ value: 'OpenAi', label: 'OpenAi' },
|
||||||
{ value: 'mbm-gpt', label: 'mbm-gpt' },
|
{ value: 'Mbmzone', label: 'Mbmzone' },
|
||||||
{ value: 'microsoft', label: 'microsoft' },
|
{ value: 'Azure', label: 'Azure' },
|
||||||
],
|
],
|
||||||
default: 'openai',
|
default: 'OpenAi',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'model',
|
key: 'model',
|
||||||
@ -42,6 +42,7 @@ const providerOpenAI = () => {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: [
|
options: [
|
||||||
{ value: 'gpt-3.5-turbo', label: 'gpt-3.5-turbo' },
|
{ value: 'gpt-3.5-turbo', label: 'gpt-3.5-turbo' },
|
||||||
|
{ value: 'gpt-3.5-turbo-16k', label: 'gpt-3.5-turbo-16k' },
|
||||||
{ value: 'gpt-4', label: 'gpt-4' },
|
{ value: 'gpt-4', label: 'gpt-4' },
|
||||||
{ value: 'gpt-4-0314', label: 'gpt-4-0314' },
|
{ value: 'gpt-4-0314', label: 'gpt-4-0314' },
|
||||||
{ value: 'gpt-4-32k', label: 'gpt-4-32k' },
|
{ value: 'gpt-4-32k', label: 'gpt-4-32k' },
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { action, map } from 'nanostores'
|
import { action, atom, map } from 'nanostores'
|
||||||
import { conversationMessagesMapData } from './tests/message.mock'
|
import { conversationMessagesMapData } from './tests/message.mock'
|
||||||
import { db } from './storage/message'
|
import { db } from './storage/message'
|
||||||
import { updateConversationById } from './conversation'
|
import { updateConversationById } from './conversation'
|
||||||
import type { MessageInstance } from '@/types/message'
|
import type { MessageInstance } from '@/types/message'
|
||||||
|
|
||||||
export const conversationMessagesMap = map<Record<string, MessageInstance[]>>({})
|
export const conversationMessagesMap = map<Record<string, MessageInstance[]>>({})
|
||||||
|
export const shareMessageIds = atom<string[]>([])
|
||||||
|
|
||||||
export const rebuildMessagesStore = async() => {
|
export const rebuildMessagesStore = async() => {
|
||||||
const data = await db.exportData() || {}
|
const data = await db.exportData() || {}
|
||||||
@ -100,4 +101,4 @@ export const spliceUpdateMessageByConversationId = action(
|
|||||||
lastUseTime: Date.now(),
|
lastUseTime: Date.now(),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
@ -6,6 +6,8 @@ export const showSettingsSidebar = atom(false)
|
|||||||
export const showConversationEditModal = atom(false)
|
export const showConversationEditModal = atom(false)
|
||||||
export const showEmojiPickerModal = atom(false)
|
export const showEmojiPickerModal = atom(false)
|
||||||
export const showConfirmModal = atom(false)
|
export const showConfirmModal = atom(false)
|
||||||
|
export const showShareModal = atom(false)
|
||||||
|
export const showSelectMessageModal = atom(false)
|
||||||
|
|
||||||
export const isSendBoxFocus = atom(false)
|
export const isSendBoxFocus = atom(false)
|
||||||
export const currentErrorMessage = atom<ErrorMessage | null>(null)
|
export const currentErrorMessage = atom<ErrorMessage | null>(null)
|
||||||
@ -18,4 +20,4 @@ export const scrollController = () => {
|
|||||||
scrollToBottom: () => elementList().forEach(element => element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' })),
|
scrollToBottom: () => elementList().forEach(element => element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' })),
|
||||||
instantToBottom: () => elementList().forEach(element => element.scrollTo({ top: element.scrollHeight })),
|
instantToBottom: () => elementList().forEach(element => element.scrollTo({ top: element.scrollHeight })),
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user