feat: The demonstration page supports modifying dialogue summaries (#2348)

This commit is contained in:
shaohuzhang1 2025-02-24 18:53:21 +08:00 committed by GitHub
parent 5a3acc8649
commit 3aa5dd3694
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 273 additions and 32 deletions

View File

@ -66,6 +66,10 @@ def valid_model_params_setting(model_id, model_params_setting):
credential.get_model_params_setting_form(model.model_name).valid_form(model_params_setting) credential.get_model_params_setting_form(model.model_name).valid_form(model_params_setting)
class ReAbstractInstanceSerializers(serializers.Serializer):
abstract = serializers.CharField(required=True, error_messages=ErrMessage.char(_("abstract")))
class ChatSerializers(serializers.Serializer): class ChatSerializers(serializers.Serializer):
class Operate(serializers.Serializer): class Operate(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID")))
@ -78,6 +82,15 @@ class ChatSerializers(serializers.Serializer):
is_deleted=True) is_deleted=True)
return True return True
def re_abstract(self, instance, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
ReAbstractInstanceSerializers(data=instance).is_valid(raise_exception=True)
QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id')).update(
abstract=instance.get('abstract'))
return True
def delete(self, with_valid=True): def delete(self, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)

View File

@ -23,6 +23,34 @@ class ChatClientHistoryApi(ApiMixin):
description=_('Application ID')) description=_('Application ID'))
] ]
class Operate(ApiMixin):
@staticmethod
def get_request_params_api():
return [openapi.Parameter(name='application_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Application ID')),
openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Conversation ID')),
]
class ReAbstract(ApiMixin):
@staticmethod
def get_request_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['abstract'],
properties={
'abstract': openapi.Schema(type=openapi.TYPE_STRING, title=_("abstract"),
description=_("abstract"))
}
)
class OpenAIChatApi(ApiMixin): class OpenAIChatApi(ApiMixin):
@staticmethod @staticmethod

View File

@ -150,7 +150,7 @@ class ChatView(APIView):
operation_id=_("Get the conversation list"), operation_id=_("Get the conversation list"),
manual_parameters=ChatApi.get_request_params_api(), manual_parameters=ChatApi.get_request_params_api(),
responses=result.get_api_array_response(ChatApi.get_response_body_api()), responses=result.get_api_array_response(ChatApi.get_response_body_api()),
tags=[_("Application/Conversation Log")] tags=[_("Application/Conversation Log")]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY],
@ -222,6 +222,23 @@ class ChatView(APIView):
data={'application_id': application_id, 'user_id': request.user.id, data={'application_id': application_id, 'user_id': request.user.id,
'chat_id': chat_id}).logic_delete()) 'chat_id': chat_id}).logic_delete())
@action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Client modifies dialogue summary"),
operation_id=_("Client modifies dialogue summary"),
request_body=ChatClientHistoryApi.Operate.ReAbstract.get_request_body_api(),
tags=[_("Application/Conversation Log")])
@has_permissions(ViewPermission(
[RoleConstants.APPLICATION_ACCESS_TOKEN],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND),
compare=CompareConstants.AND)
def put(self, request: Request, application_id: str, chat_id: str):
return result.success(
ChatSerializers.Operate(
data={'application_id': application_id, 'user_id': request.user.id,
'chat_id': chat_id}).re_abstract(request.data))
class Page(APIView): class Page(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]

View File

@ -6753,3 +6753,5 @@ msgstr ""
msgid "Image download failed, check network" msgid "Image download failed, check network"
msgstr "" msgstr ""
msgid "Client modifies dialogue summary"
msgstr ""

View File

@ -6892,4 +6892,5 @@ msgstr "超出许可证使用限制。"
msgid "Image download failed, check network" msgid "Image download failed, check network"
msgstr "图片下载失败,请检查网络" msgstr "图片下载失败,请检查网络"
msgid "Client modifies dialogue summary"
msgstr "客户端修改对话摘要"

View File

@ -6902,4 +6902,7 @@ msgid "License usage limit exceeded."
msgstr "超出許可證使用限制。" msgstr "超出許可證使用限制。"
msgid "Image download failed, check network" msgid "Image download failed, check network"
msgstr "圖片下載失敗,檢查網絡" msgstr "圖片下載失敗,檢查網絡"
msgid "Client modifies dialogue summary"
msgstr "用戶端修改對話摘要"

View File

@ -220,6 +220,29 @@ const delChatClientLog: (
return del(`${prefix}/${application_id}/chat/client/${chat_id}`, undefined, {}, loading) return del(`${prefix}/${application_id}/chat/client/${chat_id}`, undefined, {}, loading)
} }
/**
* abstract
* @param
* application_id, chat_id,
* data {
"abstract": "string",
}
*/
const putChatClientLog: (
application_id: string,
chat_id: string,
data: any,
loading?: Ref<boolean>
) => Promise<Result<boolean>> = (application_id, chat_id, data, loading) => {
return put(
`${prefix}/${application_id}/chat/client/${chat_id}`,
data,
undefined,
loading
)
}
export default { export default {
getChatLog, getChatLog,
delChatLog, delChatLog,
@ -231,5 +254,6 @@ export default {
exportChatLog, exportChatLog,
getChatLogClient, getChatLogClient,
delChatClientLog, delChatClientLog,
postChatRecordLog postChatRecordLog,
putChatClientLog
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<el-dialog <el-dialog
class="execution-details-dialog" class="execution-details-dialog responsive-dialog"
:title="$t('chat.executionDetails.title')" :title="$t('chat.executionDetails.title')"
v-model="dialogVisible" v-model="dialogVisible"
destroy-on-close destroy-on-close
@ -697,9 +697,5 @@ defineExpose({ open })
} }
} }
@media only screen and (max-width: 768px) {
.execution-details-dialog {
width: 90% !important;
}
}
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<el-dialog <el-dialog
class="paragraph-source" class="paragraph-source responsive-dialog"
:title="$t('chat.paragraphSource.title')" :title="$t('chat.paragraphSource.title')"
v-model="dialogVisible" v-model="dialogVisible"
destroy-on-close destroy-on-close
@ -76,9 +76,4 @@ defineExpose({ open })
max-height: calc(100vh - 260px); max-height: calc(100vh - 260px);
} }
} }
@media only screen and (max-width: 768px) {
.paragraph-source {
width: 90% !important;
}
}
</style> </style>

View File

@ -90,5 +90,6 @@ export default {
title: 'Knowledge Quote', title: 'Knowledge Quote',
question: 'User Question', question: 'User Question',
optimizationQuestion: 'Optimized Question' optimizationQuestion: 'Optimized Question'
} },
editTitle: 'Edit Title',
} }

View File

@ -25,7 +25,6 @@ export default {
continue: '继续', continue: '继续',
stopChat: '停止回答' stopChat: '停止回答'
}, },
tip: { tip: {
error500Message: '抱歉,当前正在维护,无法提供服务,请稍后再试!', error500Message: '抱歉,当前正在维护,无法提供服务,请稍后再试!',
errorIdentifyMessage: '无法识别用户身份', errorIdentifyMessage: '无法识别用户身份',
@ -91,5 +90,6 @@ export default {
title: '知识库引用', title: '知识库引用',
question: '用户问题', question: '用户问题',
optimizationQuestion: '优化后问题' optimizationQuestion: '优化后问题'
} },
editTitle: '编辑标题',
} }

View File

@ -90,5 +90,6 @@ export default {
title: '知識庫引用', title: '知識庫引用',
question: '用戶問題', question: '用戶問題',
optimizationQuestion: '優化後問題' optimizationQuestion: '優化後問題'
} },
editTitle: '編輯標題',
} }

View File

@ -60,6 +60,18 @@ const useLogStore = defineStore({
reject(error) reject(error)
}) })
}) })
},
async asyncPutChatClientLog(id: string, chatId: string, data: any, loading?: Ref<boolean>) {
return new Promise((resolve, reject) => {
logApi
.putChatClientLog(id, chatId, data, loading)
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
} }
} }
}) })

View File

@ -756,3 +756,9 @@ h5 {
border-right: 1px solid var(--el-border-color); border-right: 1px solid var(--el-border-color);
} }
} }
@media only screen and (max-width: 768px) {
.responsive-dialog {
width: 90% !important;
}
}

View File

@ -88,10 +88,22 @@
> >
<template #default="{ row }"> <template #default="{ row }">
<div class="flex-between"> <div class="flex-between">
<auto-tooltip :content="row.abstract"> <ReadWrite
{{ row.abstract }} @change="editName($event, row)"
</auto-tooltip> :data="row.abstract"
<div @click.stop v-if="mouseId === row.id && row.id !== 'new'"> trigger="manual"
:write="row.writeStatus"
@close="closeWrite(row)"
:maxlength="1024"
/>
<div
@click.stop
v-if="mouseId === row.id && row.id !== 'new' && !row.writeStatus"
class="flex"
>
<el-button style="padding: 0" link @click.stop="openWrite(row)">
<el-icon><EditPen /></el-icon>
</el-button>
<el-button style="padding: 0" link @click.stop="deleteLog(row)"> <el-button style="padding: 0" link @click.stop="deleteLog(row)">
<el-icon><Delete /></el-icon> <el-icon><Delete /></el-icon>
</el-button> </el-button>
@ -120,6 +132,7 @@ import { ref, onMounted, reactive, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { isAppIcon } from '@/utils/application' import { isAppIcon } from '@/utils/application'
import { hexToRgba } from '@/utils/theme' import { hexToRgba } from '@/utils/theme'
import { MsgError } from '@/utils/message'
import useStore from '@/stores' import useStore from '@/stores'
const { user, log } = useStore() const { user, log } = useStore()
const route = useRoute() const route = useRoute()
@ -160,6 +173,29 @@ const customStyle = computed(() => {
} }
}) })
function editName(val: string, item: any) {
if (val) {
const obj = {
abstract: val
}
log.asyncPutChatClientLog(applicationDetail.value.id, item.id, obj, loading).then(() => {
getChatLog(applicationDetail.value.id)
item['writeStatus'] = false
})
} else {
MsgError(t('views.applicationWorkflow.tip.nameMessage'))
}
}
function openWrite(item: any) {
item['writeStatus'] = true
}
function closeWrite(item: any) {
item['writeStatus'] = false
}
function mouseenter(row: any) { function mouseenter(row: any) {
mouseId.value = row.id mouseId.value = row.id
} }
@ -312,7 +348,7 @@ onMounted(() => {
right: 16px; right: 16px;
font-size: 22px; font-size: 22px;
} }
&.chat-embed--popup{ &.chat-embed--popup {
.chat-popover-button { .chat-popover-button {
right: 85px; right: 85px;
} }

View File

@ -0,0 +1,87 @@
<template>
<el-dialog
class="responsive-dialog"
:title="$t('chat.editTitle')"
v-model="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
append-to-body
>
<el-form
label-position="top"
ref="fieldFormRef"
:model="form"
require-asterisk-position="right"
>
<el-form-item
prop="abstract"
:rules="[
{
required: true,
message: $t('common.inputPlaceholder'),
trigger: 'blur'
}
]"
>
<el-input
v-model="form.abstract"
maxlength="1024"
show-word-limit
type="textarea"
@blur="form.abstract = form.abstract.trim()"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
{{ $t('common.save') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import type { FormInstance } from 'element-plus'
import useStore from '@/stores'
import { t } from '@/locales'
const { log } = useStore()
const emit = defineEmits(['refresh'])
const fieldFormRef = ref()
const loading = ref<boolean>(false)
const applicationId = ref<string>('')
const chatId = ref<string>('')
const form = ref<any>({
abstract: ''
})
const dialogVisible = ref<boolean>(false)
const open = (row: any, id: string) => {
applicationId.value = id
chatId.value = row.id
form.value.abstract = row.abstract
dialogVisible.value = true
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
log.asyncPutChatClientLog(applicationId.value, chatId.value, form.value, loading).then(() => {
emit('refresh')
dialogVisible.value = false
})
}
})
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped></style>

View File

@ -66,10 +66,22 @@
<auto-tooltip :content="row.abstract"> <auto-tooltip :content="row.abstract">
{{ row.abstract }} {{ row.abstract }}
</auto-tooltip> </auto-tooltip>
<div @click.stop v-if="mouseId === row.id && row.id !== 'new'"> <div @click.stop v-show="mouseId === row.id && row.id !== 'new'">
<el-button style="padding: 0" link @click.stop="deleteLog(row)"> <el-dropdown trigger="click" :teleported="false">
<el-icon><Delete /></el-icon> <el-icon class="rotate-90 mt-4"><MoreFilled /></el-icon>
</el-button> <template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click.stop="editLogTitle(row)">
<el-icon><EditPen /></el-icon>
{{ $t('common.edit') }}
</el-dropdown-item>
<el-dropdown-item @click.stop="deleteLog(row)">
<el-icon><Delete /></el-icon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div> </div>
</div> </div>
</template> </template>
@ -145,6 +157,7 @@
</div> </div>
</div> </div>
</div> </div>
<EditTitleDialog ref="EditTitleDialogRef" @refresh="refreshFieldTitle" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -155,14 +168,13 @@ import { isAppIcon } from '@/utils/application'
import useStore from '@/stores' import useStore from '@/stores'
import useResize from '@/layout/hooks/useResize' import useResize from '@/layout/hooks/useResize'
import { hexToRgba } from '@/utils/theme' import { hexToRgba } from '@/utils/theme'
import EditTitleDialog from './EditTitleDialog.vue'
import { t } from '@/locales' import { t } from '@/locales'
useResize() useResize()
const { user, log, common } = useStore() const { user, log, common } = useStore()
const isDefaultTheme = computed(() => { const EditTitleDialogRef = ref()
return user.isDefaultTheme()
})
const isCollapse = ref(false) const isCollapse = ref(false)
@ -216,6 +228,13 @@ const mouseId = ref('')
function mouseenter(row: any) { function mouseenter(row: any) {
mouseId.value = row.id mouseId.value = row.id
} }
function editLogTitle(row: any) {
EditTitleDialogRef.value.open(row, applicationDetail.value.id)
}
function refreshFieldTitle() {
getChatLog(applicationDetail.value.id)
}
function deleteLog(row: any) { function deleteLog(row: any) {
log.asyncDelChatClientLog(applicationDetail.value.id, row.id, left_loading).then(() => { log.asyncDelChatClientLog(applicationDetail.value.id, row.id, left_loading).then(() => {
if (currentChatId.value === row.id) { if (currentChatId.value === row.id) {