Skip to content

Commit

Permalink
Bot page (#514)
Browse files Browse the repository at this point in the history
* Fix URL path in handleChatHome function to open chat in new tab

* Refactor snapshot routing and add bot index view for enhanced snapshot management and export functionality.

* Refactor Header component to handle different types and pass 'typ' prop to handleHome function

* Refactor bot routes and move bot views to new directory

* Refactor: Move useCopyCode hook and fix indentation in bot and snapshot views

* Refactor: Update import paths to use '@/' alias for consistency
  • Loading branch information
swuecho committed Aug 4, 2024
1 parent 35d50e2 commit adcbc46
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 9 deletions.
File renamed without changes.
14 changes: 13 additions & 1 deletion web/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
path: '/bot',
name: 'Bot',
component: () => import('@/views/bot/index.vue'),
children: [
{
path: ':uuid?',
name: 'Bot',
component: () => import('@/views/bot/index.vue'),
},
],
},
{
path: '/snapshot_all',
name: 'SnapshotAll',
Expand All @@ -25,7 +37,7 @@ const routes: RouteRecordRaw[] = [
{
path: '/bot_all',
name: 'BotAll',
component: () => import('@/views/snapshot/bot_all.vue'),
component: () => import('@/views/bot/all.vue'),
},
{
path: '/admin',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { NModal, useDialog, useMessage } from 'naive-ui'
import Search from './components/Search.vue'
import Search from '../snapshot/components/Search.vue'
import { fetchSnapshotAll, fetchSnapshotDelete } from '@/api'
import { displayLocaleDate, formatYearMonth } from '@/utils/date'
import { HoverButton, SvgIcon } from '@/components/common'
Expand All @@ -17,7 +17,7 @@ interface PostLink {
}
function post_url(uuid: string): string {
return `#/snapshot/${uuid}`
return `#/bot/${uuid}`
}
const postsByYearMonth = ref<Record<string, PostLink[]>>({})
Expand Down
222 changes: 222 additions & 0 deletions web/src/views/bot/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<script lang='ts' setup>
import { computed, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { useDialog, useMessage, NSpin } from 'naive-ui'
import html2canvas from 'html2canvas'
import Message from '../snapshot/components/Message/index.vue'
import { useCopyCode } from '@/hooks/useCopyCode'
import Header from '../snapshot/components/Header/index.vue'
import { CreateSessionFromSnapshot, fetchChatSnapshot } from '@/api/chat_snapshot'
import { HoverButton, SvgIcon } from '@/components/common'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { t } from '@/locales'
import { genTempDownloadLink } from '@/utils/download'
import { getCurrentDate } from '@/utils/date'
import { useAuthStore, useChatStore } from '@/store'
import { useQuery } from '@tanstack/vue-query'
const authStore = useAuthStore()
const chatStore = useChatStore()
const route = useRoute()
const dialog = useDialog()
const nui_msg = useMessage()
useCopyCode()
const { isMobile } = useBasicLayout()
// session uuid
const { uuid } = route.params as { uuid: string }
const { data: snapshot_data, isLoading } = useQuery({
queryKey: ['chatSnapshot', uuid],
queryFn: async () => await fetchChatSnapshot(uuid),
})
function handleExport() {
const dialogBox = dialog.warning({
title: t('chat.exportImage'),
content: t('chat.exportImageConfirm'),
positiveText: t('common.yes'),
negativeText: t('common.no'),
onPositiveClick: async () => {
try {
dialogBox.loading = true
const ele = document.getElementById('image-wrapper')
const canvas = await html2canvas(ele as HTMLDivElement, {
useCORS: true,
})
const imgUrl = canvas.toDataURL('image/png')
const tempLink = genTempDownloadLink(imgUrl)
document.body.appendChild(tempLink)
tempLink.click()
document.body.removeChild(tempLink)
window.URL.revokeObjectURL(imgUrl)
dialogBox.loading = false
nui_msg.success(t('chat.exportSuccess'))
Promise.resolve()
}
catch (error: any) {
nui_msg.error(t('chat.exportFailed'))
}
finally {
dialogBox.loading = false
}
},
})
}
function format_chat_md(chat: Chat.Message): string {
return `<sup><kbd><var>${chat.dateTime}</var></kbd></sup>:\n ${chat.text}`
}
const chatToMarkdown = () => {
try {
/*
uuid: string,
dateTime: string
text: string
inversion?: boolean
error?: boolean
loading?: boolean
isPrompt?: boolean
*/
const chatData = snapshot_data.value.conversation;
const markdown = chatData.map((chat: Chat.Message) => {
if (chat.isPrompt)
return `**system** ${format_chat_md(chat)}}`
else if (chat.inversion)
return `**user** ${format_chat_md(chat)}`
else
return `**assistant** ${format_chat_md(chat)}`
}).join('\n\n----\n\n')
return markdown
}
catch (error) {
console.error(error)
throw error
}
}
function handleMarkdown() {
const dialogBox = dialog.warning({
title: t('chat.exportMD'),
content: t('chat.exportMDConfirm'),
positiveText: t('common.yes'),
negativeText: t('common.no'),
onPositiveClick: async () => {
try {
dialogBox.loading = true
const markdown = chatToMarkdown()
const ts = getCurrentDate()
const filename = `chat-${ts}.md`
const blob = new Blob([markdown], { type: 'text/plain;charset=utf-8' })
const url: string = URL.createObjectURL(blob)
const link: HTMLAnchorElement = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
dialogBox.loading = false
nui_msg.success(t('chat.exportSuccess'))
Promise.resolve()
}
catch (error: any) {
nui_msg.error(t('chat.exportFailed'))
}
finally {
dialogBox.loading = false
}
},
})
}
async function handleChat() {
if (!authStore.getToken())
nui_msg.error(t('common.ask_user_register'))
window.open(`/`, '_blank')
const { SessionUuid }: { SessionUuid: string } = await CreateSessionFromSnapshot(uuid)
await chatStore.setActiveLocal(SessionUuid)
// open link at static/#/chat/{SessionUuid}
//window.open(`/static/#/chat/${SessionUuid}`, '_blank')
}
const footerClass = computed(() => {
let classes = ['p-4']
if (isMobile.value)
classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3', 'overflow-hidden']
return classes
})
function onScrollToTop() {
const scrollRef = document.querySelector('#scrollRef')
if (scrollRef)
nextTick(() => scrollRef.scrollTop = 0)
}
</script>

<template>
<div class="flex flex-col w-full h-full">
<div v-if="isLoading">
<NSpin size="large" />
</div>
<div v-else>
<Header :title="snapshot_data.title" typ="chatbot" />
<main class="flex-1 overflow-hidden">
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
<div id="image-wrapper" class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
:class="[isMobile ? 'p-2' : 'p-4']">
<Message v-for="(item, index) of snapshot_data.conversation" :key="index" :date-time="item.dateTime"
:model="snapshot_data.model" :text="item.text" :inversion="item.inversion" :error="item.error"
:loading="item.loading" :index="index" />
</div>
</div>
</main>
<div class="floating-button">
<HoverButton testid="create-chat" :tooltip="$t('chat_snapshot.createChat')" @click="handleChat">
<span class="text-xl text-[#4f555e] dark:text-white m-auto mx-10">
<SvgIcon icon="mdi:chat-plus" width="32" height="32" />
</span>
</HoverButton>
</div>
<footer :class="footerClass">
<div class="w-full max-w-screen-xl m-auto">
<div class="flex items-center justify-between space-x-2">
<HoverButton v-if="!isMobile" :tooltip="$t('chat_snapshot.exportImage')" @click="handleExport">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="ri:download-2-line" />
</span>
</HoverButton>
<HoverButton v-if="!isMobile" :tooltip="$t('chat_snapshot.exportMarkdown')" @click="handleMarkdown">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="mdi:language-markdown" />
</span>
</HoverButton>
<HoverButton :tooltip="$t('chat_snapshot.scrollTop')" @click="onScrollToTop">
<span class="text-xl text-[#4f555e] dark:text-white">
<SvgIcon icon="material-symbols:vertical-align-top" />
</span>
</HoverButton>
</div>
</div>
</footer>
</div>
</div>
</template>

<style>
/* CSS for the button */
.floating-button {
position: fixed;
bottom: 10vh;
right: 10vmin;
z-index: 99;
padding: 0.5em;
border-radius: 50%;
cursor: pointer;
background-color: #4ff09a;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
</style>
10 changes: 8 additions & 2 deletions web/src/views/snapshot/components/Header/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import { useMutation, useQueryClient } from '@tanstack/vue-query'
const queryClient = useQueryClient()
defineProps<Props>()
const props = defineProps<Props>()
const route = useRoute()
interface Props {
title: string
typ: string
}
const { uuid } = route.params as { uuid: string }
Expand All @@ -22,7 +23,12 @@ const isEditing = ref<boolean>(false)
const titleRef = ref(null)
function handleHome() {
window.open('#/snapshot_all', '_blank')
const typ = props.typ
if (typ === 'snapshot') {
window.open('#/snapshot_all', '_blank')
} else if (typ === 'chatbot') {
window.open('#/bot_all', '_blank')
}
}
function handleChatHome() {
Expand Down
8 changes: 4 additions & 4 deletions web/src/views/snapshot/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useRoute } from 'vue-router'
import { useDialog, useMessage, NSpin } from 'naive-ui'
import html2canvas from 'html2canvas'
import Message from './components/Message/index.vue'
import { useCopyCode } from './hooks/useCopyCode'
import { useCopyCode } from '@/hooks/useCopyCode'
import Header from './components/Header/index.vue'
import { CreateSessionFromSnapshot, fetchChatSnapshot } from '@/api/chat_snapshot'
import { HoverButton, SvgIcon } from '@/components/common'
Expand Down Expand Up @@ -136,7 +136,7 @@ function handleMarkdown() {
async function handleChat() {
if (!authStore.getToken())
nui_msg.error(t('common.ask_user_register'))
window.open(`/`, '_blank')
window.open(`/`, '_blank')
const { SessionUuid }: { SessionUuid: string } = await CreateSessionFromSnapshot(uuid)
await chatStore.setActiveLocal(SessionUuid)
// open link at static/#/chat/{SessionUuid}
Expand All @@ -160,10 +160,10 @@ function onScrollToTop() {
<template>
<div class="flex flex-col w-full h-full">
<div v-if="isLoading">
<NSpin size="large" />
<NSpin size="large" />
</div>
<div v-else>
<Header :title="snapshot_data.title" />
<Header :title="snapshot_data.title" typ="snapshot" />
<main class="flex-1 overflow-hidden">
<div id="scrollRef" ref="scrollRef" class="h-full overflow-hidden overflow-y-auto">
<div id="image-wrapper" class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"
Expand Down

0 comments on commit adcbc46

Please sign in to comment.