Skip to content

Commit

Permalink
Add Chatdoc feature (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
崔庆才丨静觅 authored Jan 28, 2024
1 parent 78e4e09 commit 1a7fc4a
Show file tree
Hide file tree
Showing 51 changed files with 2,742 additions and 25 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,6 @@
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"cSpell.words": ["Chatdoc"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "major",
"comment": "add chatdoc feature",
"packageName": "@zhishuyun/hub",
"email": "[email protected]",
"dependentChangeType": "patch"
}
8 changes: 3 additions & 5 deletions src/components/chat/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import AnsweringMark from './AnsweringMark.vue';
import copy from 'copy-to-clipboard';
import { ElAlert, ElButton } from 'element-plus';
import MarkdownRenderer from '@/components/common/MarkdownRenderer.vue';
import { IApplication, IChatMessage, IChatMessageState, ROLE_ASSISTANT } from '@/operators';
import { IApplication, IChatMessage, IChatMessageState } from '@/operators';
import CopyToClipboard from '../common/CopyToClipboard.vue';
import {
ERROR_CODE_API_ERROR,
Expand All @@ -43,7 +43,8 @@ import {
ERROR_CODE_TIMEOUT,
ERROR_CODE_TOO_MANY_REQUESTS,
ERROR_CODE_UNKNOWN,
ERROR_CODE_USED_UP
ERROR_CODE_USED_UP,
ROLE_ASSISTANT
} from '@/constants';
import message from '@/i18n/zh/common/message';
import { ROUTE_CONSOLE_APPLICATION_BUY } from '@/router';
Expand Down Expand Up @@ -125,9 +126,6 @@ export default defineComponent({
}
});
}
// onStop() {
// this.$emit('stop');
// }
}
});
</script>
Expand Down
40 changes: 40 additions & 0 deletions src/components/chatdoc/AnsweringMark.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<div class="box"></div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AnsweringMark',
data() {
return {};
},
computed: {
conversationId() {
return this.$route.params?.conversationId?.toString();
}
}
});
</script>

<style lang="scss">
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.box {
width: 2px;
height: 16px;
margin-top: 3px;
background-color: var(--el-color-black);
animation: blink 1s infinite;
}
</style>
198 changes: 198 additions & 0 deletions src/components/chatdoc/Conversations.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<template>
<div class="panel">
<el-skeleton v-if="loading && conversations === undefined" />
<div v-else class="conversations">
<div class="conversation" @click="onNewConversation">
<div class="icons">
<font-awesome-icon icon="fa-solid fa-plus" class="icon" />
</div>
<div class="title">
{{ $t('chatdoc.message.startNewChat') }}
</div>
</div>
<div
v-for="(conversation, conversationIndex) in conversations"
:key="conversationIndex"
:class="{ conversation: true, active: conversation.id === conversationId }"
@click="onClick(conversation.id)"
>
<div class="icons">
<font-awesome-icon icon="fa-regular fa-comment" class="icon" />
</div>
<div class="title">
<span v-if="conversation?.deleting">
{{ `${$t('chatdoc.message.confirmDelete')}?` }}
</span>
<span v-else-if="conversation?.editing">
<el-input v-model="conversation.title" @keydown.enter="onConfirm(conversation)" />
</span>
<span v-else-if="conversation?.title || conversation?.messages">{{
conversation?.title || conversation?.messages[conversation?.messages.length - 1]?.content
}}</span>
</div>
<div class="operations">
<font-awesome-icon
v-if="!conversation?.editing && !conversation.deleting"
icon="fa-solid fa-edit"
class="icon icon-edit"
@click.stop="conversation.editing = true"
/>
<font-awesome-icon
v-if="!conversation?.editing && !conversation.deleting"
icon="fa-solid fa-trash"
class="icon icon-delete"
@click.stop="conversation.deleting = true"
/>
<font-awesome-icon
v-if="conversation?.editing || conversation.deleting"
icon="fa-solid fa-check"
class="icon icon-confirm"
@click.stop="onConfirm(conversation)"
/>
<font-awesome-icon
v-if="conversation?.editing || conversation.deleting"
icon="fa-solid fa-xmark"
class="icon icon-cancel"
@click.stop="
conversation.editing = false;
conversation.deleting = false;
"
/>
</div>
</div>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { ElSkeleton, ElInput } from 'element-plus';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { ROUTE_CHATDOC_CONVERSATION, ROUTE_CHATDOC_CONVERSATION_NEW } from '@/router/constants';
import { IChatdocRepository, chatdocOperator, IChatdocConversation } from '@/operators';
export default defineComponent({
name: 'SidePanel',
components: {
ElInput,
FontAwesomeIcon,
ElSkeleton
},
props: {},
emits: ['click'],
computed: {
repositoryId() {
return this.$route.params?.repositoryId?.toString();
},
conversationId() {
return this.$route.params?.conversationId?.toString();
},
repository(): IChatdocRepository | undefined {
return this.$store.state?.chatdoc?.repositories?.find((repository) => repository.id === this.repositoryId);
},
conversations() {
return this.repository?.conversations;
},
applications() {
return this.$store.state.chatdoc.applications;
}
},
methods: {
async onNewConversation() {
this.$router.push({
name: ROUTE_CHATDOC_CONVERSATION_NEW,
params: {
repositoryId: this.repositoryId
}
});
},
async onConfirm(conversation: IChatdocConversation) {
if (conversation?.deleting) {
await chatdocOperator.deleteConversation(conversation.id);
await this.$store.dispatch('chat/getConversations');
} else if (conversation?.editing) {
await chatdocOperator.updateConversation(conversation);
await this.$store.dispatch('chat/getConversations');
} else {
conversation.editing = true;
}
},
onClick(id: string) {
if (!id) {
return;
}
this.$router.push({
name: ROUTE_CHATDOC_CONVERSATION,
params: {
repositoryId: this.repositoryId,
conversationId: id
}
});
this.$emit('click', id);
}
}
});
</script>

<style lang="scss" scoped>
.panel {
display: flex;
flex-direction: column;
align-items: flex-end;
padding: 15px;
width: 300px;
height: 100%;
border-right: 1px solid #eee;
overflow-y: scroll;
.conversations {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
.conversation {
width: 100%;
height: 55px;
display: flex;
flex-direction: row;
padding: 10px;
margin-bottom: 5px;
border: 1px dashed hsl(0, 0%, 93%);
line-height: 30px;
border-radius: 10px;
color: #666;
cursor: pointer;
&.active,
&:hover {
background-color: #eee;
}
.icons {
width: 30px;
padding-left: 10px;
.icon {
font-size: 14px;
}
}
.title {
flex: 1;
font-size: 14px;
line-height: 32px;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 8px;
color: #666;
}
.operations {
width: 40px;
.icon {
cursor: pointer;
font-size: 14px;
margin-right: 6px;
}
}
}
}
}
</style>
98 changes: 98 additions & 0 deletions src/components/chatdoc/CreateRepository.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<template>
<el-dialog v-model="dialogVisible" width="500px">
<el-form ref="form" label-width="60px">
<el-form-item :label="$t('chatdoc.field.name')" :prop="form.name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item :label="$t('chatdoc.field.description')" :prop="form.description">
<el-input v-model="form.description" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">{{ $t('common.button.create') }}</el-button>
</el-form-item>
</el-form>
</el-dialog>
<button class="btn btn-create" @click="dialogVisible = true">
<font-awesome-icon :icon="['fas', 'plus']" />
</button>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { ElButton, ElDialog, ElMessage, ElForm, ElFormItem, ElInput } from 'element-plus';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
interface IData {
dialogVisible: boolean;
creating: boolean;
form: {
name: string;
description: string;
};
}
export default defineComponent({
name: 'UploadDocument',
components: {
ElButton,
ElDialog,
ElForm,
ElFormItem,
ElInput,
FontAwesomeIcon
},
data(): IData {
return {
dialogVisible: false,
creating: false,
form: {
name: '',
description: ''
}
};
},
computed: {},
methods: {
async onSubmit() {
if (!this.form.name) {
ElMessage.error(this.$t('chatdoc.message.nameRequired'));
return;
}
this.creating = true;
this.$store
.dispatch('chatdoc/createRepository', {
name: this.form.name,
description: this.form.description
})
.then(() => {
this.creating = false;
ElMessage.success(this.$t('chatdoc.message.createDocumentSuccess'));
this.dialogVisible = false;
this.$store.dispatch('chatdoc/getRepositories');
})
.catch(() => {
this.creating = false;
ElMessage.error(this.$t('chatdoc.message.createRepositoryFailed'));
});
}
}
});
</script>

<style scoped lang="scss">
.btn.btn-create {
text-align: center;
cursor: pointer;
border: none;
width: 50px;
height: 50px;
border-radius: 50%;
margin-top: 5px;
background-color: #eee;
margin-bottom: 0;
}
.el-button {
border-radius: 20px;
}
</style>
Loading

0 comments on commit 1a7fc4a

Please sign in to comment.