feat(chat): 添加对话反馈功能和置信度显示
- 新增对话记录反馈提交功能,支持用户提交反馈内容 - 添加多语言支持包括英文、简体中文、繁体中文的反馈相关文案 - 在AI聊天界面添加反馈按钮和对话框组件 - 实现反馈类型选择和内容输入验证功能 - 添加置信度分数显示功能,在答案内容区域展示综合评分 - 更新应用发布流程,支持内部发布和公开发布的模式选择 - 优化系统资源管理中的应用访问控制逻辑 - 扩展应用聊天记录数据结构,增加反馈内容字段 - 实现反馈列表和详情展示功能,支持历史对话中的反馈查看v3.2
parent
193361fd64
commit
c4361b158a
|
|
@ -42,6 +42,10 @@ class ApplicationChatResponseSerializers(serializers.Serializer):
|
||||||
star_num = serializers.IntegerField(required=True, label=_("Number of Likes"))
|
star_num = serializers.IntegerField(required=True, label=_("Number of Likes"))
|
||||||
trample_num = serializers.IntegerField(required=True, label=_("Number of thumbs-downs"))
|
trample_num = serializers.IntegerField(required=True, label=_("Number of thumbs-downs"))
|
||||||
mark_sum = serializers.IntegerField(required=True, label=_("Number of tags"))
|
mark_sum = serializers.IntegerField(required=True, label=_("Number of tags"))
|
||||||
|
feedback_count = serializers.IntegerField(required=False, label=_("Feedback count"))
|
||||||
|
feedback_content = serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
||||||
|
label=_("Feedback content"))
|
||||||
|
feedback_list = serializers.ListField(required=False, label=_("Feedback list"))
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatRecordExportRequest(serializers.Serializer):
|
class ApplicationChatRecordExportRequest(serializers.Serializer):
|
||||||
|
|
@ -136,11 +140,45 @@ class ApplicationChatQuerySerializers(serializers.Serializer):
|
||||||
def list(self, with_valid=True):
|
def list(self, with_valid=True):
|
||||||
if with_valid:
|
if with_valid:
|
||||||
self.is_valid(raise_exception=True)
|
self.is_valid(raise_exception=True)
|
||||||
return native_search(self.get_query_set(), select_string=get_file_content(
|
result = native_search(self.get_query_set(), select_string=get_file_content(
|
||||||
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
||||||
('list_application_chat_ee.sql' if ['PE', 'EE'].__contains__(
|
('list_application_chat_ee.sql' if ['PE', 'EE'].__contains__(
|
||||||
edition) else 'list_application_chat.sql'))),
|
edition) else 'list_application_chat.sql'))),
|
||||||
with_table_name=False)
|
with_table_name=False)
|
||||||
|
return self.append_feedback_content(result)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_feedback_items(details):
|
||||||
|
if not isinstance(details, dict):
|
||||||
|
return []
|
||||||
|
feedback = details.get('feedback')
|
||||||
|
if isinstance(feedback, list):
|
||||||
|
return feedback
|
||||||
|
if isinstance(feedback, dict):
|
||||||
|
items = feedback.get('items')
|
||||||
|
return items if isinstance(items, list) else []
|
||||||
|
return []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def append_feedback_content(cls, records):
|
||||||
|
if not records:
|
||||||
|
return records
|
||||||
|
chat_id_list = [row.get('id') for row in records if row.get('id') is not None]
|
||||||
|
feedback_map = {}
|
||||||
|
chat_records = QuerySet(ChatRecord).filter(chat_id__in=chat_id_list).order_by('create_time')
|
||||||
|
for chat_record in chat_records:
|
||||||
|
feedback_items = cls.get_feedback_items(chat_record.details)
|
||||||
|
if not feedback_items:
|
||||||
|
continue
|
||||||
|
feedback_map[str(chat_record.chat_id)] = feedback_items
|
||||||
|
|
||||||
|
for row in records:
|
||||||
|
feedback_items = feedback_map.get(str(row.get('id')), [])
|
||||||
|
latest_feedback = feedback_items[-1] if feedback_items else {}
|
||||||
|
row['feedback_count'] = len(feedback_items)
|
||||||
|
row['feedback_content'] = latest_feedback.get('content') if isinstance(latest_feedback, dict) else ''
|
||||||
|
row['feedback_list'] = feedback_items
|
||||||
|
return records
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def paragraph_list_to_string(paragraph_list):
|
def paragraph_list_to_string(paragraph_list):
|
||||||
|
|
@ -239,11 +277,13 @@ class ApplicationChatQuerySerializers(serializers.Serializer):
|
||||||
def page(self, current_page: int, page_size: int, with_valid=True):
|
def page(self, current_page: int, page_size: int, with_valid=True):
|
||||||
if with_valid:
|
if with_valid:
|
||||||
self.is_valid(raise_exception=True)
|
self.is_valid(raise_exception=True)
|
||||||
return native_page_search(current_page, page_size, self.get_query_set(), select_string=get_file_content(
|
page = native_page_search(current_page, page_size, self.get_query_set(), select_string=get_file_content(
|
||||||
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
||||||
('list_application_chat_ee.sql' if ['PE', 'EE'].__contains__(
|
('list_application_chat_ee.sql' if ['PE', 'EE'].__contains__(
|
||||||
edition) else 'list_application_chat.sql'))),
|
edition) else 'list_application_chat.sql'))),
|
||||||
with_table_name=False)
|
with_table_name=False)
|
||||||
|
page['records'] = self.append_feedback_content(page.get('records'))
|
||||||
|
return page
|
||||||
|
|
||||||
|
|
||||||
class ChatCountSerializer(serializers.Serializer):
|
class ChatCountSerializer(serializers.Serializer):
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from functools import reduce
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
import uuid_utils.compat as uuid
|
import uuid_utils.compat as uuid
|
||||||
|
from django.utils import timezone
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.db.models.aggregates import Max, Min
|
from django.db.models.aggregates import Max, Min
|
||||||
|
|
@ -45,6 +46,8 @@ class ChatRecordOperateSerializer(serializers.Serializer):
|
||||||
workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID"))
|
workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID"))
|
||||||
application_id = serializers.UUIDField(required=True, label=_("Application ID"))
|
application_id = serializers.UUIDField(required=True, label=_("Application ID"))
|
||||||
chat_record_id = serializers.UUIDField(required=True, label=_("Conversation record id"))
|
chat_record_id = serializers.UUIDField(required=True, label=_("Conversation record id"))
|
||||||
|
feedback_type = serializers.CharField(required=False, allow_blank=True, label=_("Feedback type"))
|
||||||
|
content = serializers.CharField(required=False, allow_blank=True, label=_("Feedback content"))
|
||||||
|
|
||||||
def is_valid(self, *, debug=False, raise_exception=False):
|
def is_valid(self, *, debug=False, raise_exception=False):
|
||||||
super().is_valid(raise_exception=True)
|
super().is_valid(raise_exception=True)
|
||||||
|
|
@ -85,6 +88,49 @@ class ChatRecordOperateSerializer(serializers.Serializer):
|
||||||
return ApplicationChatRecordQuerySerializers.reset_chat_record(
|
return ApplicationChatRecordQuerySerializers.reset_chat_record(
|
||||||
chat_record, True if debug else show_source, True if debug else show_exec)
|
chat_record, True if debug else show_source, True if debug else show_exec)
|
||||||
|
|
||||||
|
def feedback(self, request=None, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
chat_record = self.get_chat_record()
|
||||||
|
if chat_record is None:
|
||||||
|
raise AppApiException(500, gettext('Conversation record does not exist'))
|
||||||
|
|
||||||
|
feedback_type = self.data.get('feedback_type')
|
||||||
|
content = self.data.get('content')
|
||||||
|
|
||||||
|
if not feedback_type:
|
||||||
|
raise AppApiException(500, gettext('Feedback type is required'))
|
||||||
|
if not content:
|
||||||
|
raise AppApiException(500, gettext('Feedback content is required'))
|
||||||
|
|
||||||
|
feedback_items = []
|
||||||
|
feedback_details = chat_record.details.get('feedback')
|
||||||
|
if isinstance(feedback_details, list):
|
||||||
|
feedback_items = feedback_details
|
||||||
|
elif isinstance(feedback_details, dict):
|
||||||
|
feedback_items = feedback_details.get('items') or []
|
||||||
|
|
||||||
|
feedback_data = {
|
||||||
|
'feedback_type': feedback_type,
|
||||||
|
'content': content,
|
||||||
|
'create_time': timezone.now().isoformat()
|
||||||
|
}
|
||||||
|
chat_record.details['feedback'] = {
|
||||||
|
'type': 'feedback-node',
|
||||||
|
'items': [*feedback_items, feedback_data]
|
||||||
|
}
|
||||||
|
chat_record.save()
|
||||||
|
|
||||||
|
application_access_token = QuerySet(ApplicationAccessToken).filter(
|
||||||
|
application_id=self.data.get('application_id')).first()
|
||||||
|
show_source = False
|
||||||
|
show_exec = False
|
||||||
|
if application_access_token is not None:
|
||||||
|
show_exec = application_access_token.show_exec
|
||||||
|
show_source = application_access_token.show_source
|
||||||
|
return ApplicationChatRecordQuerySerializers.reset_chat_record(
|
||||||
|
chat_record, show_source, show_exec)
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatRecordQuerySerializers(serializers.Serializer):
|
class ApplicationChatRecordQuerySerializers(serializers.Serializer):
|
||||||
workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID"))
|
workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID"))
|
||||||
|
|
@ -114,12 +160,14 @@ class ApplicationChatRecordQuerySerializers(serializers.Serializer):
|
||||||
def reset_chat_record(chat_record, show_source, show_exec):
|
def reset_chat_record(chat_record, show_source, show_exec):
|
||||||
knowledge_list = []
|
knowledge_list = []
|
||||||
paragraph_list = []
|
paragraph_list = []
|
||||||
if 'search_step' in chat_record.details and chat_record.details.get('search_step').get(
|
if 'search_step' in chat_record.details and isinstance(chat_record.details.get('search_step'), dict) and chat_record.details.get('search_step').get(
|
||||||
'paragraph_list') is not None:
|
'paragraph_list') is not None:
|
||||||
paragraph_list = chat_record.details.get('search_step').get(
|
paragraph_list = chat_record.details.get('search_step').get(
|
||||||
'paragraph_list')
|
'paragraph_list')
|
||||||
|
|
||||||
for item in chat_record.details.values():
|
for item in chat_record.details.values():
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
continue
|
||||||
if item.get('type') == 'search-knowledge-node' and item.get('show_knowledge', False):
|
if item.get('type') == 'search-knowledge-node' and item.get('show_knowledge', False):
|
||||||
paragraph_list = paragraph_list + (item.get(
|
paragraph_list = paragraph_list + (item.get(
|
||||||
'paragraph_list') or [])
|
'paragraph_list') or [])
|
||||||
|
|
@ -151,6 +199,7 @@ class ApplicationChatRecordQuerySerializers(serializers.Serializer):
|
||||||
show_source_dict = {'knowledge_list': knowledge_list,
|
show_source_dict = {'knowledge_list': knowledge_list,
|
||||||
'paragraph_list': paragraph_list, }
|
'paragraph_list': paragraph_list, }
|
||||||
show_exec_dict = {'execution_details': [chat_record.details[key] for key in chat_record.details if
|
show_exec_dict = {'execution_details': [chat_record.details[key] for key in chat_record.details if
|
||||||
|
isinstance(chat_record.details[key], dict) and chat_record.details[key].get('type') != 'feedback-node' and
|
||||||
(True if show_exec else chat_record.details[key].get(
|
(True if show_exec else chat_record.details[key].get(
|
||||||
'type') == 'start-node')]}
|
'type') == 'start-node')]}
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ urlpatterns = [
|
||||||
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<int:current_page>/<int:page_size>', views.ApplicationChat.Page.as_view()),
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<int:current_page>/<int:page_size>', views.ApplicationChat.Page.as_view()),
|
||||||
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record', views.ApplicationChatRecord.as_view()),
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record', views.ApplicationChatRecord.as_view()),
|
||||||
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>', views.ApplicationChatRecordOperateAPI.as_view()),
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>', views.ApplicationChatRecordOperateAPI.as_view()),
|
||||||
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/feedback', views.ApplicationChatRecordOperateAPI.as_view()),
|
||||||
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<int:current_page>/<int:page_size>', views.ApplicationChatRecord.Page.as_view()),
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<int:current_page>/<int:page_size>', views.ApplicationChatRecord.Page.as_view()),
|
||||||
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/improve', views.ApplicationChatRecordImprove.as_view()),
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/improve', views.ApplicationChatRecordImprove.as_view()),
|
||||||
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/improve', views.ApplicationChatRecordImproveParagraph.as_view()),
|
path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/improve', views.ApplicationChatRecordImproveParagraph.as_view()),
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,30 @@ class ApplicationChatRecordOperateAPI(APIView):
|
||||||
'chat_id': chat_id,
|
'chat_id': chat_id,
|
||||||
'chat_record_id': chat_record_id}).one(True))
|
'chat_record_id': chat_record_id}).one(True))
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['POST'],
|
||||||
|
description=_('Submit conversation feedback'),
|
||||||
|
summary=_('Submit conversation feedback'),
|
||||||
|
operation_id=_('Submit conversation feedback'), # type: ignore
|
||||||
|
tags=[_('Application/Conversation Log')] # type: ignore
|
||||||
|
)
|
||||||
|
@has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(),
|
||||||
|
PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(),
|
||||||
|
PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),
|
||||||
|
PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),
|
||||||
|
ViewPermission([RoleConstants.USER.get_workspace_role()],
|
||||||
|
[PermissionConstants.APPLICATION.get_workspace_application_permission()],
|
||||||
|
CompareConstants.AND),
|
||||||
|
RoleConstants.WORKSPACE_MANAGE.get_workspace_role())
|
||||||
|
def post(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str):
|
||||||
|
return result.success(ChatRecordOperateSerializer(
|
||||||
|
data={
|
||||||
|
'workspace_id': workspace_id,
|
||||||
|
'application_id': application_id,
|
||||||
|
'chat_id': chat_id,
|
||||||
|
'chat_record_id': chat_record_id,
|
||||||
|
**request.data}).feedback(request=request))
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatRecordAddKnowledge(APIView):
|
class ApplicationChatRecordAddKnowledge(APIView):
|
||||||
authentication_classes = [TokenAuth]
|
authentication_classes = [TokenAuth]
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ urlpatterns = [
|
||||||
name='application/chat_completions'),
|
name='application/chat_completions'),
|
||||||
path('vote/chat/<str:chat_id>/chat_record/<str:chat_record_id>', views.VoteView.as_view(), name='vote'),
|
path('vote/chat/<str:chat_id>/chat_record/<str:chat_record_id>', views.VoteView.as_view(), name='vote'),
|
||||||
path('historical_conversation', views.HistoricalConversationView.as_view(), name='historical_conversation'),
|
path('historical_conversation', views.HistoricalConversationView.as_view(), name='historical_conversation'),
|
||||||
|
path('historical_conversation/<str:chat_id>/record/<str:chat_record_id>/feedback', views.ChatRecordView.as_view(), name='conversation_feedback'),
|
||||||
path('historical_conversation/<str:chat_id>/record/<str:chat_record_id>',views.ChatRecordView.as_view(),name='conversation_details'),
|
path('historical_conversation/<str:chat_id>/record/<str:chat_record_id>',views.ChatRecordView.as_view(),name='conversation_details'),
|
||||||
path('historical_conversation/<int:current_page>/<int:page_size>', views.HistoricalConversationView.PageView.as_view(), name='historical_conversation'),
|
path('historical_conversation/<int:current_page>/<int:page_size>', views.HistoricalConversationView.PageView.as_view(), name='historical_conversation'),
|
||||||
path('historical_conversation/clear',views.HistoricalConversationView.BatchDelete.as_view(), name='historical_conversation_clear'),
|
path('historical_conversation/clear',views.HistoricalConversationView.BatchDelete.as_view(), name='historical_conversation_clear'),
|
||||||
|
|
|
||||||
|
|
@ -198,3 +198,22 @@ class ChatRecordView(APIView):
|
||||||
'application_id': request.auth.application_id,
|
'application_id': request.auth.application_id,
|
||||||
'chat_user_id': request.auth.chat_user_id,
|
'chat_user_id': request.auth.chat_user_id,
|
||||||
}).one(False))
|
}).one(False))
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
methods=['POST'],
|
||||||
|
description=_("Submit conversation feedback"),
|
||||||
|
summary=_("Submit conversation feedback"),
|
||||||
|
operation_id=_("Submit conversation feedback"), # type: ignore
|
||||||
|
parameters=PageHistoricalConversationRecordAPI.get_parameters(),
|
||||||
|
responses=PageHistoricalConversationRecordAPI.get_response(),
|
||||||
|
tags=[_('Chat')] # type: ignore
|
||||||
|
)
|
||||||
|
def post(self, request: Request, chat_id: str, chat_record_id: str):
|
||||||
|
return result.success(ChatRecordOperateSerializer(
|
||||||
|
data={
|
||||||
|
'chat_id': chat_id,
|
||||||
|
'chat_record_id': chat_record_id,
|
||||||
|
'application_id': request.auth.application_id,
|
||||||
|
'chat_user_id': request.auth.chat_user_id,
|
||||||
|
**request.data
|
||||||
|
}).feedback())
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
prefix: '/admin',
|
prefix: '/admin',
|
||||||
chatPrefix: '/chat',
|
chatPrefix: '/chat',
|
||||||
}
|
}
|
||||||
})()</script><script type="module" crossorigin src="./assets/admin-BHHIGtO5.js"></script><link rel="stylesheet" crossorigin href="./assets/admin-uFds98Rz.css"></head><body><div id="app"></div></body></html>
|
})()</script><script type="module" crossorigin src="./assets/admin-Da392tT3.js"></script><link rel="stylesheet" crossorigin href="./assets/admin-D4RuFzCU.css"></head><body><div id="app"></div></body></html>
|
||||||
|
|
@ -198,6 +198,30 @@ const getChatRecordDetails: (
|
||||||
loading,
|
loading,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交对话记录反馈
|
||||||
|
* @param application_id
|
||||||
|
* @param chat_id
|
||||||
|
* @param chat_record_id
|
||||||
|
* @param data
|
||||||
|
* @param loading
|
||||||
|
*/
|
||||||
|
const postChatRecordFeedback: (
|
||||||
|
application_id: string,
|
||||||
|
chat_id: string,
|
||||||
|
chat_record_id: string,
|
||||||
|
data: any,
|
||||||
|
loading?: Ref<boolean>,
|
||||||
|
) => Promise<Result<any>> = (application_id, chat_id, chat_record_id, data, loading) => {
|
||||||
|
return post(
|
||||||
|
`${prefix.value}/${application_id}/chat/${chat_id}/chat_record/${chat_record_id}/feedback`,
|
||||||
|
data,
|
||||||
|
undefined,
|
||||||
|
loading,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
postChatLogAddKnowledge,
|
postChatLogAddKnowledge,
|
||||||
getChatLog,
|
getChatLog,
|
||||||
|
|
@ -207,4 +231,5 @@ export default {
|
||||||
delMarkChatRecord,
|
delMarkChatRecord,
|
||||||
postExportChatLog,
|
postExportChatLog,
|
||||||
getChatRecordDetails,
|
getChatRecordDetails,
|
||||||
|
postChatRecordFeedback,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,15 @@ const getChatRecord: (
|
||||||
) => Promise<Result<any>> = (chat_id, chat_record_id, loading) => {
|
) => Promise<Result<any>> = (chat_id, chat_record_id, loading) => {
|
||||||
return get(`historical_conversation/${chat_id}/record/${chat_record_id}`, {}, loading)
|
return get(`historical_conversation/${chat_id}/record/${chat_record_id}`, {}, loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const postChatRecordFeedback: (
|
||||||
|
chat_id: string,
|
||||||
|
chat_record_id: string,
|
||||||
|
data: any,
|
||||||
|
loading?: Ref<boolean>,
|
||||||
|
) => Promise<Result<any>> = (chat_id, chat_record_id, data, loading) => {
|
||||||
|
return post(`historical_conversation/${chat_id}/record/${chat_record_id}/feedback`, data, undefined, loading)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 文本转语音
|
* 文本转语音
|
||||||
*/
|
*/
|
||||||
|
|
@ -356,6 +365,7 @@ export default {
|
||||||
resetCurrentPassword,
|
resetCurrentPassword,
|
||||||
getChatUserProfile,
|
getChatUserProfile,
|
||||||
getChatRecord,
|
getChatRecord,
|
||||||
|
postChatRecordFeedback,
|
||||||
textToSpeech,
|
textToSpeech,
|
||||||
speechToText,
|
speechToText,
|
||||||
deleteChat,
|
deleteChat,
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,22 @@ const getChatRecordDetails: (
|
||||||
loading,
|
loading,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const postChatRecordFeedback: (
|
||||||
|
application_id: string,
|
||||||
|
chat_id: string,
|
||||||
|
chat_record_id: string,
|
||||||
|
data: any,
|
||||||
|
loading?: Ref<boolean>,
|
||||||
|
) => Promise<Result<any>> = (application_id, chat_id, chat_record_id, data, loading) => {
|
||||||
|
return post(
|
||||||
|
`${prefix}/${application_id}/chat/${chat_id}/chat_record/${chat_record_id}/feedback`,
|
||||||
|
data,
|
||||||
|
undefined,
|
||||||
|
loading,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
postChatLogAddKnowledge,
|
postChatLogAddKnowledge,
|
||||||
getChatLog,
|
getChatLog,
|
||||||
|
|
@ -189,4 +205,5 @@ export default {
|
||||||
delMarkChatRecord,
|
delMarkChatRecord,
|
||||||
postExportChatLog,
|
postExportChatLog,
|
||||||
getChatRecordDetails,
|
getChatRecordDetails,
|
||||||
|
postChatRecordFeedback,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ interface chatType {
|
||||||
id: string
|
id: string
|
||||||
problem_text: string
|
problem_text: string
|
||||||
answer_text: string
|
answer_text: string
|
||||||
|
comprehensive_score?: number | string | null
|
||||||
buffer: Array<string>
|
buffer: Array<string>
|
||||||
answer_text_list: Array<
|
answer_text_list: Array<
|
||||||
Array<{
|
Array<{
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,17 @@
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<div
|
||||||
|
v-if="formattedComprehensiveScore !== null"
|
||||||
|
class="content confidence-row"
|
||||||
|
:style="{
|
||||||
|
'padding-left': showAvatar ? 'var(--padding-left)' : '0',
|
||||||
|
'padding-right': showUserAvatar ? 'var(--padding-left)' : '0',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="confidence-row__label">{{ $t('chat.comprehensiveScore') }}</span>
|
||||||
|
<span class="confidence-row__value">{{ formattedComprehensiveScore }}</span>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="content"
|
class="content"
|
||||||
:style="{
|
:style="{
|
||||||
|
|
@ -146,6 +157,18 @@ const answer_text_list = computed(() => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const formattedComprehensiveScore = computed(() => {
|
||||||
|
const score = props.chatRecord?.comprehensive_score
|
||||||
|
if (score === undefined || score === null || score === '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const numericScore = Number(score)
|
||||||
|
if (Number.isNaN(numericScore)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return numericScore.toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
function showSource(row: any) {
|
function showSource(row: any) {
|
||||||
if (props.type === 'log') {
|
if (props.type === 'log') {
|
||||||
return true
|
return true
|
||||||
|
|
@ -182,4 +205,19 @@ onMounted(() => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.confidence-row {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--app-text-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confidence-row__label {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confidence-row__value {
|
||||||
|
color: var(--app-text-color);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,15 @@
|
||||||
<AppIcon iconName="app-oppose-color"></AppIcon>
|
<AppIcon iconName="app-oppose-color"></AppIcon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-tooltip effect="dark" :content="$t('views.chatLog.feedback.shortTitle')" placement="top">
|
||||||
|
<el-button text @click="openFeedback(data)" class="feedback-trigger">
|
||||||
|
<el-icon class="feedback-trigger__icon"><ChatDotRound /></el-icon>
|
||||||
|
<span class="feedback-trigger__label">{{ $t('views.chatLog.feedback.shortTitle') }}</span>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
<FeedbackDialog ref="FeedbackDialogRef" @refresh="refreshFeedback" />
|
||||||
<div ref="audioCiontainer"></div>
|
<div ref="audioCiontainer"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -101,12 +109,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick, onMounted, ref, onBeforeUnmount, type Ref } from 'vue'
|
import { nextTick, onMounted, ref, onBeforeUnmount, type Ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import { ChatDotRound } from '@element-plus/icons-vue'
|
||||||
import { copyClick } from '@/utils/clipboard'
|
import { copyClick } from '@/utils/clipboard'
|
||||||
import applicationApi from '@/api/application/application'
|
import applicationApi from '@/api/application/application'
|
||||||
import chatAPI from '@/api/chat/chat'
|
import chatAPI from '@/api/chat/chat'
|
||||||
import { datetimeFormat } from '@/utils/time'
|
import { datetimeFormat } from '@/utils/time'
|
||||||
import { MsgError } from '@/utils/message'
|
import { MsgError } from '@/utils/message'
|
||||||
import bus from '@/bus'
|
import bus from '@/bus'
|
||||||
|
import FeedbackDialog from '@/views/chat-log/component/FeedbackDialog.vue'
|
||||||
const copy = (data: any) => {
|
const copy = (data: any) => {
|
||||||
try {
|
try {
|
||||||
const text = data.answer_text_list
|
const text = data.answer_text_list
|
||||||
|
|
@ -143,6 +153,7 @@ const emit = defineEmits(['update:data', 'regeneration'])
|
||||||
|
|
||||||
const audioPlayer = ref<HTMLAudioElement[] | null>([])
|
const audioPlayer = ref<HTMLAudioElement[] | null>([])
|
||||||
const audioCiontainer = ref<HTMLDivElement>()
|
const audioCiontainer = ref<HTMLDivElement>()
|
||||||
|
const FeedbackDialogRef = ref()
|
||||||
const buttonData = ref(props.data)
|
const buttonData = ref(props.data)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
|
@ -152,6 +163,19 @@ function regeneration() {
|
||||||
emit('regeneration')
|
emit('regeneration')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openFeedback(data: any) {
|
||||||
|
FeedbackDialogRef.value.open({
|
||||||
|
...data,
|
||||||
|
chat_id: data.chat_id || props.chatId,
|
||||||
|
application_id: props.applicationId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFeedback(data: any) {
|
||||||
|
buttonData.value = data
|
||||||
|
emit('update:data', buttonData.value)
|
||||||
|
}
|
||||||
|
|
||||||
function voteHandle(val: string) {
|
function voteHandle(val: string) {
|
||||||
chatAPI.vote(props.chatId, props.data.record_id, val, loading).then(() => {
|
chatAPI.vote(props.chatId, props.data.record_id, val, loading).then(() => {
|
||||||
buttonData.value['vote_status'] = val
|
buttonData.value['vote_status'] = val
|
||||||
|
|
@ -560,6 +584,36 @@ onBeforeUnmount(() => {
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.feedback-trigger {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-trigger__icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-trigger__label {
|
||||||
|
max-width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
transition:
|
||||||
|
max-width 0.18s ease,
|
||||||
|
opacity 0.18s ease,
|
||||||
|
margin-left 0.18s ease;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-trigger:hover .feedback-trigger__label {
|
||||||
|
max-width: 32px;
|
||||||
|
opacity: 1;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss" scoped>
|
||||||
@media only screen and (max-width: 430px) {
|
@media only screen and (max-width: 430px) {
|
||||||
.chat-operation-button {
|
.chat-operation-button {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,15 @@
|
||||||
<el-button text disabled v-if="buttonData?.vote_status === '1'">
|
<el-button text disabled v-if="buttonData?.vote_status === '1'">
|
||||||
<AppIcon iconName="app-oppose-color"></AppIcon>
|
<AppIcon iconName="app-oppose-color"></AppIcon>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-tooltip effect="dark" :content="$t('views.chatLog.feedback.title')" placement="top">
|
||||||
|
<el-button text @click="openFeedback(data)">
|
||||||
|
<AppIcon iconName="app-feedback"></AppIcon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
<EditContentDialog ref="EditContentDialogRef" @refresh="refreshContent" />
|
<EditContentDialog ref="EditContentDialogRef" @refresh="refreshContent" />
|
||||||
<EditMarkDialog ref="EditMarkDialogRef" @refresh="refreshMark" />
|
<EditMarkDialog ref="EditMarkDialogRef" @refresh="refreshMark" />
|
||||||
|
<FeedbackDialog ref="FeedbackDialogRef" @refresh="refreshFeedback" />
|
||||||
<!-- 先渲染,不然不能播放 -->
|
<!-- 先渲染,不然不能播放 -->
|
||||||
<audio ref="audioPlayer" v-for="item in audioList" :key="item" controls hidden="hidden"></audio>
|
<audio ref="audioPlayer" v-for="item in audioList" :key="item" controls hidden="hidden"></audio>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -61,6 +68,7 @@ import { computed, onMounted, ref } from 'vue'
|
||||||
import { copyClick } from '@/utils/clipboard'
|
import { copyClick } from '@/utils/clipboard'
|
||||||
import EditContentDialog from '@/views/chat-log/component/EditContentDialog.vue'
|
import EditContentDialog from '@/views/chat-log/component/EditContentDialog.vue'
|
||||||
import EditMarkDialog from '@/views/chat-log/component/EditMarkDialog.vue'
|
import EditMarkDialog from '@/views/chat-log/component/EditMarkDialog.vue'
|
||||||
|
import FeedbackDialog from '@/views/chat-log/component/FeedbackDialog.vue'
|
||||||
import { datetimeFormat } from '@/utils/time'
|
import { datetimeFormat } from '@/utils/time'
|
||||||
import applicationApi from '@/api/application/application'
|
import applicationApi from '@/api/application/application'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
@ -103,6 +111,7 @@ const audioPlayer = ref<HTMLAudioElement[] | null>(null)
|
||||||
|
|
||||||
const EditContentDialogRef = ref()
|
const EditContentDialogRef = ref()
|
||||||
const EditMarkDialogRef = ref()
|
const EditMarkDialogRef = ref()
|
||||||
|
const FeedbackDialogRef = ref()
|
||||||
|
|
||||||
const buttonData = ref(props.data)
|
const buttonData = ref(props.data)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
@ -118,6 +127,19 @@ function editMark(data: any) {
|
||||||
EditMarkDialogRef.value.open(data)
|
EditMarkDialogRef.value.open(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openFeedback(data: any) {
|
||||||
|
FeedbackDialogRef.value.open({
|
||||||
|
...data,
|
||||||
|
chat_id: data.chat_id || '',
|
||||||
|
application_id: props.applicationId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFeedback(data: any) {
|
||||||
|
buttonData.value = data
|
||||||
|
emit('update:data', buttonData.value)
|
||||||
|
}
|
||||||
|
|
||||||
const audioPlayerStatus = ref(false)
|
const audioPlayerStatus = ref(false)
|
||||||
|
|
||||||
function markdownToPlainText(md: string) {
|
function markdownToPlainText(md: string) {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
:applicationId="application.id"
|
:applicationId="application.id"
|
||||||
:chatId="chatRecord.chat_id"
|
:chatId="chatRecord.chat_id"
|
||||||
:chat_loading="loading"
|
:chat_loading="loading"
|
||||||
|
@update:data="(event: any) => emit('update:chatRecord', event)"
|
||||||
@regeneration="regenerationChart(chatRecord)"
|
@regeneration="regenerationChart(chatRecord)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,43 @@ export const iconMap: any = {
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'app-feedback': {
|
||||||
|
iconReader: () => {
|
||||||
|
return h('i', [
|
||||||
|
h(
|
||||||
|
'svg',
|
||||||
|
{
|
||||||
|
style: { height: '100%', width: '100%' },
|
||||||
|
viewBox: '0 0 1024 1024',
|
||||||
|
version: '1.1',
|
||||||
|
xmlns: 'http://www.w3.org/2000/svg',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h('path', {
|
||||||
|
d: 'M512 106.666667c-224.853333 0-407.04 182.186667-407.04 407.04s182.186667 407.04 407.04 407.04 407.04-182.186667 407.04-407.04-182.186667-407.04-407.04-407.04z m0 768a360.96 360.96 0 1 1 0-721.92 360.96 360.96 0 0 1 0 721.92z',
|
||||||
|
fill: 'currentColor',
|
||||||
|
}),
|
||||||
|
h('path', {
|
||||||
|
d: 'M341.333333 469.333333a42.666667 42.666667 0 1 0 0 85.333334 42.666667 42.666667 0 0 0 0-85.333334z',
|
||||||
|
fill: 'currentColor',
|
||||||
|
}),
|
||||||
|
h('path', {
|
||||||
|
d: 'M512 469.333333a42.666667 42.666667 0 1 0 0 85.333334 42.666667 42.666667 0 0 0 0-85.333334z',
|
||||||
|
fill: 'currentColor',
|
||||||
|
}),
|
||||||
|
h('path', {
|
||||||
|
d: 'M682.666667 469.333333a42.666667 42.666667 0 1 0 0 85.333334 42.666667 42.666667 0 0 0 0-85.333334z',
|
||||||
|
fill: 'currentColor',
|
||||||
|
}),
|
||||||
|
h('path', {
|
||||||
|
d: 'M512 597.333333a128 128 0 1 0 0-256 128 128 0 0 0 0 256z m0 42.666667a170.666667 170.666667 0 1 1 0-341.333334 170.666667 170.666667 0 0 1 0 341.333334z',
|
||||||
|
fill: 'currentColor',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
},
|
||||||
// 动态加载的图标
|
// 动态加载的图标
|
||||||
...dynamicIcons,
|
...dynamicIcons,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,10 @@ function getProvider() {
|
||||||
model
|
model
|
||||||
.asyncGetProvider()
|
.asyncGetProvider()
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
providerOptions.value = res?.data
|
const excludedProviders = ['OpenAI', 'Azure OpenAI']
|
||||||
|
providerOptions.value = (res?.data || []).filter(
|
||||||
|
(provider: Provider) => !excludedProviders.includes(provider.name),
|
||||||
|
)
|
||||||
loading.value = false
|
loading.value = false
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export default {
|
||||||
chatId: 'Chat ID',
|
chatId: 'Chat ID',
|
||||||
chatUserId: 'Chat User ID',
|
chatUserId: 'Chat User ID',
|
||||||
chatUserType: 'Chat User Type',
|
chatUserType: 'Chat User Type',
|
||||||
|
comprehensiveScore: 'Confidence:',
|
||||||
userInput: 'User Input',
|
userInput: 'User Input',
|
||||||
quote: 'Quote',
|
quote: 'Quote',
|
||||||
download: 'Click to Download',
|
download: 'Click to Download',
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,13 @@ export default {
|
||||||
toChat: 'Chat',
|
toChat: 'Chat',
|
||||||
publish: 'Publish',
|
publish: 'Publish',
|
||||||
},
|
},
|
||||||
|
publishDialog: {
|
||||||
|
title: 'Select Publish Mode',
|
||||||
|
internalTitle: 'Internal Publish',
|
||||||
|
internalDesc: 'Publish the app and automatically turn off the public URL.',
|
||||||
|
publicTitle: 'Public Publish',
|
||||||
|
publicDesc: 'Publish the app and automatically turn on the public URL.',
|
||||||
|
},
|
||||||
delete: {
|
delete: {
|
||||||
confirmTitle: 'Are you sure you want to delete this APP: ',
|
confirmTitle: 'Are you sure you want to delete this APP: ',
|
||||||
confirmMessage:
|
confirmMessage:
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ export default {
|
||||||
feedback: {
|
feedback: {
|
||||||
label: 'User Feedback',
|
label: 'User Feedback',
|
||||||
star: 'Agree',
|
star: 'Agree',
|
||||||
trample: 'Disagree'
|
trample: 'Disagree',
|
||||||
|
countLabel: 'Feedback Count',
|
||||||
|
contentLabel: 'Feedback Content'
|
||||||
},
|
},
|
||||||
mark: 'Marks',
|
mark: 'Marks',
|
||||||
recenTimes: 'Last Chat Time'
|
recenTimes: 'Last Chat Time'
|
||||||
|
|
@ -38,5 +40,20 @@ export default {
|
||||||
title: {
|
title: {
|
||||||
placeholder: 'Please set a title for the current content for management and viewing'
|
placeholder: 'Please set a title for the current content for management and viewing'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
feedback: {
|
||||||
|
title: 'Chat Feedback',
|
||||||
|
shortTitle: 'Feedback',
|
||||||
|
type: 'Feedback Type',
|
||||||
|
typePlaceholder: 'Please select feedback type',
|
||||||
|
typeSuggestion: 'Suggestion',
|
||||||
|
typeQuestion: 'Question',
|
||||||
|
typeSupplement: 'Supplement',
|
||||||
|
typeOther: 'Other',
|
||||||
|
content: 'Feedback Content',
|
||||||
|
listTitle: 'Feedback Details',
|
||||||
|
contentPlaceholder: 'Please enter your feedback content',
|
||||||
|
typeRequired: 'Please select feedback type',
|
||||||
|
contentRequired: 'Please enter feedback content'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export default {
|
||||||
chatId: '对话 ID',
|
chatId: '对话 ID',
|
||||||
chatUserId: '对话用户 ID',
|
chatUserId: '对话用户 ID',
|
||||||
chatUserType: '对话用户类型',
|
chatUserType: '对话用户类型',
|
||||||
|
comprehensiveScore: '置信度:',
|
||||||
userInput: '用户输入',
|
userInput: '用户输入',
|
||||||
quote: '引用',
|
quote: '引用',
|
||||||
download: '点击下载文件',
|
download: '点击下载文件',
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,13 @@ export default {
|
||||||
toChat: '去对话',
|
toChat: '去对话',
|
||||||
publish: '发布',
|
publish: '发布',
|
||||||
},
|
},
|
||||||
|
publishDialog: {
|
||||||
|
title: '选择发布方式',
|
||||||
|
internalTitle: '内部发布',
|
||||||
|
internalDesc: '发布后自动关闭公开访问链接,仅保留内部使用。',
|
||||||
|
publicTitle: '公开发布',
|
||||||
|
publicDesc: '发布后自动打开公开访问链接,允许外部访问。',
|
||||||
|
},
|
||||||
delete: {
|
delete: {
|
||||||
confirmTitle: '是否删除应用:',
|
confirmTitle: '是否删除应用:',
|
||||||
confirmMessage: '删除后该应用将不再提供服务,请谨慎操作。',
|
confirmMessage: '删除后该应用将不再提供服务,请谨慎操作。',
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ export default {
|
||||||
feedback: {
|
feedback: {
|
||||||
label: '用户反馈',
|
label: '用户反馈',
|
||||||
star: '赞同',
|
star: '赞同',
|
||||||
trample: '反对'
|
trample: '反对',
|
||||||
|
countLabel: '反馈条数',
|
||||||
|
contentLabel: '反馈内容'
|
||||||
},
|
},
|
||||||
mark: '改进标注',
|
mark: '改进标注',
|
||||||
recenTimes: '最近对话时间'
|
recenTimes: '最近对话时间'
|
||||||
|
|
@ -38,5 +40,20 @@ export default {
|
||||||
title: {
|
title: {
|
||||||
placeholder: '请给当前内容设置一个标题,以便管理查看'
|
placeholder: '请给当前内容设置一个标题,以便管理查看'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
feedback: {
|
||||||
|
title: '对话反馈',
|
||||||
|
shortTitle: '反馈',
|
||||||
|
type: '反馈类型',
|
||||||
|
typePlaceholder: '请选择反馈类型',
|
||||||
|
typeSuggestion: '建议',
|
||||||
|
typeQuestion: '疑问',
|
||||||
|
typeSupplement: '补充',
|
||||||
|
typeOther: '其他',
|
||||||
|
content: '反馈内容',
|
||||||
|
listTitle: '反馈详情',
|
||||||
|
contentPlaceholder: '请输入您的反馈内容',
|
||||||
|
typeRequired: '请选择反馈类型',
|
||||||
|
contentRequired: '请输入反馈内容'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export default {
|
||||||
chatId: '對話 ID',
|
chatId: '對話 ID',
|
||||||
chatUserId: '對話用戶 ID',
|
chatUserId: '對話用戶 ID',
|
||||||
chatUserType: '對話用戶類型',
|
chatUserType: '對話用戶類型',
|
||||||
|
comprehensiveScore: '置信度:',
|
||||||
userInput: '用戶輸入',
|
userInput: '用戶輸入',
|
||||||
quote: '引用',
|
quote: '引用',
|
||||||
download: '點擊下載文件',
|
download: '點擊下載文件',
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,13 @@ export default {
|
||||||
publish: '發布',
|
publish: '發布',
|
||||||
addModel: '新增模型',
|
addModel: '新增模型',
|
||||||
},
|
},
|
||||||
|
publishDialog: {
|
||||||
|
title: '選擇發布方式',
|
||||||
|
internalTitle: '內部發布',
|
||||||
|
internalDesc: '發布後自動關閉公開訪問連結,僅保留內部使用。',
|
||||||
|
publicTitle: '公開發布',
|
||||||
|
publicDesc: '發布後自動開啟公開訪問連結,允許外部訪問。',
|
||||||
|
},
|
||||||
delete: {
|
delete: {
|
||||||
confirmTitle: '是否刪除應用:',
|
confirmTitle: '是否刪除應用:',
|
||||||
confirmMessage: '刪除後該應用將不再提供服務,請謹慎操作。',
|
confirmMessage: '刪除後該應用將不再提供服務,請謹慎操作。',
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ export default {
|
||||||
feedback: {
|
feedback: {
|
||||||
label: '用戶反饋',
|
label: '用戶反饋',
|
||||||
star: '贊同',
|
star: '贊同',
|
||||||
trample: '反對'
|
trample: '反對',
|
||||||
|
countLabel: '反饋條數',
|
||||||
|
contentLabel: '反饋內容'
|
||||||
},
|
},
|
||||||
mark: '改進標註',
|
mark: '改進標註',
|
||||||
recenTimes: '最近對話時間'
|
recenTimes: '最近對話時間'
|
||||||
|
|
@ -38,5 +40,20 @@ export default {
|
||||||
title: {
|
title: {
|
||||||
placeholder: '請給當前內容設定一個標題,以便管理查看'
|
placeholder: '請給當前內容設定一個標題,以便管理查看'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
feedback: {
|
||||||
|
title: '對話反饋',
|
||||||
|
shortTitle: '反饋',
|
||||||
|
type: '反饋類型',
|
||||||
|
typePlaceholder: '請選擇反饋類型',
|
||||||
|
typeSuggestion: '建議',
|
||||||
|
typeQuestion: '疑問',
|
||||||
|
typeSupplement: '補充',
|
||||||
|
typeOther: '其他',
|
||||||
|
content: '反饋內容',
|
||||||
|
listTitle: '反饋詳情',
|
||||||
|
contentPlaceholder: '請輸入您的反饋內容',
|
||||||
|
typeRequired: '請選擇反饋類型',
|
||||||
|
contentRequired: '請輸入反饋內容'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
||||||
import { type Ref } from 'vue'
|
import { type Ref } from 'vue'
|
||||||
const useApplicationStore = defineStore('application', {
|
const useApplicationStore = defineStore('application', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
location: `${window.location.origin}${window.MaxKB.chatPrefix ? window.MaxKB.chatPrefix : window.MaxKB.prefix}/`,
|
location: `${window.location.origin}${window.MaxKB.chatPrefix ? window.MaxKB.chatPrefix : '/chat'}/`,
|
||||||
}),
|
}),
|
||||||
actions: {},
|
actions: {},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="publish(applicationFormRef)"
|
@click="openPublishDialog(applicationFormRef)"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
v-if="permissionPrecise.edit(id)"
|
v-if="permissionPrecise.edit(id)"
|
||||||
>
|
>
|
||||||
|
|
@ -633,6 +633,46 @@
|
||||||
/>
|
/>
|
||||||
<McpServersDialog ref="mcpServersDialogRef" @refresh="submitMcpServersDialog" />
|
<McpServersDialog ref="mcpServersDialogRef" @refresh="submitMcpServersDialog" />
|
||||||
<ToolDialog ref="toolDialogRef" @refresh="submitToolDialog" />
|
<ToolDialog ref="toolDialogRef" @refresh="submitToolDialog" />
|
||||||
|
<el-dialog
|
||||||
|
v-model="publishDialogVisible"
|
||||||
|
:title="$t('views.application.publishDialog.title')"
|
||||||
|
width="520"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<div class="publish-mode-list">
|
||||||
|
<div
|
||||||
|
class="publish-mode-card"
|
||||||
|
:class="{ active: publishMode === 'internal' }"
|
||||||
|
@click="publishMode = 'internal'"
|
||||||
|
>
|
||||||
|
<div class="publish-mode-card__title">
|
||||||
|
{{ $t('views.application.publishDialog.internalTitle') }}
|
||||||
|
</div>
|
||||||
|
<div class="publish-mode-card__desc">
|
||||||
|
{{ $t('views.application.publishDialog.internalDesc') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="publish-mode-card"
|
||||||
|
:class="{ active: publishMode === 'public' }"
|
||||||
|
@click="publishMode = 'public'"
|
||||||
|
>
|
||||||
|
<div class="publish-mode-card__title">
|
||||||
|
{{ $t('views.application.publishDialog.publicTitle') }}
|
||||||
|
</div>
|
||||||
|
<div class="publish-mode-card__desc">
|
||||||
|
{{ $t('views.application.publishDialog.publicDesc') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="publishDialogVisible = false">{{ $t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" :loading="loading" @click="confirmPublish(applicationFormRef)">
|
||||||
|
{{ $t('common.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -703,6 +743,8 @@ const AddKnowledgeDialogRef = ref()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const knowledgeLoading = ref(false)
|
const knowledgeLoading = ref(false)
|
||||||
|
const publishDialogVisible = ref(false)
|
||||||
|
const publishMode = ref<'internal' | 'public'>('public')
|
||||||
|
|
||||||
const applicationForm = ref<ApplicationFormType>({
|
const applicationForm = ref<ApplicationFormType>({
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -791,10 +833,32 @@ const publish = (formEl: FormInstance | undefined) => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
return loadSharedApi({ type: 'application', systemType: apiType.value }).putAccessToken(
|
||||||
|
id,
|
||||||
|
{
|
||||||
|
is_active: publishMode.value === 'public',
|
||||||
|
},
|
||||||
|
loading,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
publishDialogVisible.value = false
|
||||||
MsgSuccess(t('views.application.tip.publishSuccess'))
|
MsgSuccess(t('views.application.tip.publishSuccess'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openPublishDialog = (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
formEl.validate().then(() => {
|
||||||
|
publishMode.value = 'public'
|
||||||
|
publishDialogVisible.value = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmPublish = (formEl: FormInstance | undefined) => {
|
||||||
|
publish(formEl)
|
||||||
|
}
|
||||||
const submit = async (formEl: FormInstance | undefined) => {
|
const submit = async (formEl: FormInstance | undefined) => {
|
||||||
if (!formEl) return
|
if (!formEl) return
|
||||||
await formEl.validate((valid, fields) => {
|
await formEl.validate((valid, fields) => {
|
||||||
|
|
@ -1127,6 +1191,39 @@ onMounted(() => {
|
||||||
color: var(--app-text-color);
|
color: var(--app-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.publish-mode-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-mode-card {
|
||||||
|
border: 1px solid var(--el-border-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-mode-card.active {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
background: var(--el-color-primary-light-9);
|
||||||
|
box-shadow: 0 0 0 1px var(--el-color-primary-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-mode-card__title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--app-text-color);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.publish-mode-card__desc {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--app-text-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-bg {
|
.dialog-bg {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--dialog-bg-gradient-color);
|
background: var(--dialog-bg-gradient-color);
|
||||||
|
|
|
||||||
|
|
@ -572,7 +572,19 @@ function toChat(row: any) {
|
||||||
aips = aips ? aips : []
|
aips = aips ? aips : []
|
||||||
const apiParams = mapToUrlParams(aips) ? '?' + mapToUrlParams(aips) : ''
|
const apiParams = mapToUrlParams(aips) ? '?' + mapToUrlParams(aips) : ''
|
||||||
ApplicationApi.getAccessToken(row.id, loading).then((res: any) => {
|
ApplicationApi.getAccessToken(row.id, loading).then((res: any) => {
|
||||||
window.open(application.location + res?.data?.access_token + apiParams)
|
if (res?.data?.is_active) {
|
||||||
|
window.open(application.location + res?.data?.access_token + apiParams)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const debugPreviewUrl = router.resolve({
|
||||||
|
name: 'AppSetting',
|
||||||
|
params: {
|
||||||
|
from: 'workspace',
|
||||||
|
id: row.id,
|
||||||
|
type: row.type,
|
||||||
|
},
|
||||||
|
}).href
|
||||||
|
window.open(debugPreviewUrl)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.chatLog.feedback.title')"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="600"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
label-position="top"
|
||||||
|
require-asterisk-position="right"
|
||||||
|
:rules="rules"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<el-form-item :label="$t('views.chatLog.feedback.type')" prop="feedback_type">
|
||||||
|
<el-select v-model="form.feedback_type" :placeholder="$t('views.chatLog.feedback.typePlaceholder')">
|
||||||
|
<el-option :label="$t('views.chatLog.feedback.typeSuggestion')" value="SUGGESTION"></el-option>
|
||||||
|
<el-option :label="$t('views.chatLog.feedback.typeQuestion')" value="QUESTION"></el-option>
|
||||||
|
<el-option :label="$t('views.chatLog.feedback.typeSupplement')" value="SUPPLEMENT"></el-option>
|
||||||
|
<el-option :label="$t('views.chatLog.feedback.typeOther')" value="OTHER"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="$t('views.chatLog.feedback.content')" prop="content">
|
||||||
|
<el-input
|
||||||
|
v-model="form.content"
|
||||||
|
type="textarea"
|
||||||
|
:placeholder="$t('views.chatLog.feedback.contentPlaceholder')"
|
||||||
|
:rows="6"
|
||||||
|
maxlength="2000"
|
||||||
|
show-word-limit
|
||||||
|
></el-input>
|
||||||
|
</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" native-type="button" @click.prevent="submitForm(formRef)" :loading="loading">
|
||||||
|
{{ $t('common.save') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, reactive, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { t } from '@/locales'
|
||||||
|
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||||
|
import chatAPI from '@/api/chat/chat'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const isChatRoute = computed(() => {
|
||||||
|
return route.name === 'chat' || typeof route.params?.accessToken === 'string'
|
||||||
|
})
|
||||||
|
const apiType = computed(() => {
|
||||||
|
if (route.path.includes('resource-management')) {
|
||||||
|
return 'systemManage'
|
||||||
|
} else {
|
||||||
|
return 'workspace'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['refresh'])
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const form = ref<any>({
|
||||||
|
chat_id: '',
|
||||||
|
record_id: '',
|
||||||
|
application_id: '',
|
||||||
|
feedback_type: '',
|
||||||
|
content: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
feedback_type: [
|
||||||
|
{ required: true, message: t('views.chatLog.feedback.typeRequired'), trigger: 'blur' },
|
||||||
|
],
|
||||||
|
content: [
|
||||||
|
{ required: true, message: t('views.chatLog.feedback.contentRequired'), trigger: 'blur' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(dialogVisible, (bool) => {
|
||||||
|
if (!bool) {
|
||||||
|
form.value = {
|
||||||
|
chat_id: '',
|
||||||
|
record_id: '',
|
||||||
|
application_id: '',
|
||||||
|
feedback_type: '',
|
||||||
|
content: '',
|
||||||
|
}
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (data: any) => {
|
||||||
|
form.value.chat_id = data.chat_id
|
||||||
|
form.value.record_id = data.record_id || data.id
|
||||||
|
form.value.application_id = data.application_id
|
||||||
|
formRef.value?.clearValidate()
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return
|
||||||
|
await formEl.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
const obj = {
|
||||||
|
feedback_type: form.value.feedback_type,
|
||||||
|
content: form.value.content,
|
||||||
|
}
|
||||||
|
const request = isChatRoute.value
|
||||||
|
? chatAPI.postChatRecordFeedback(form.value.chat_id, form.value.record_id, obj, loading)
|
||||||
|
: loadSharedApi({ type: 'chatLog', systemType: apiType.value }).postChatRecordFeedback(
|
||||||
|
form.value.application_id,
|
||||||
|
form.value.chat_id,
|
||||||
|
form.value.record_id,
|
||||||
|
obj,
|
||||||
|
loading,
|
||||||
|
)
|
||||||
|
|
||||||
|
request
|
||||||
|
.then((res: any) => {
|
||||||
|
emit('refresh', res.data)
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
.catch(() => undefined)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -163,6 +163,14 @@
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="mark_sum" :label="$t('views.chatLog.table.mark')" align="right" />
|
<el-table-column prop="mark_sum" :label="$t('views.chatLog.table.mark')" align="right" />
|
||||||
|
<el-table-column prop="feedback_count" :label="$t('views.chatLog.table.feedback.countLabel')" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button v-if="row.feedback_count" link type="primary" @click.stop="openFeedbackList(row)">
|
||||||
|
{{ row.feedback_count }}
|
||||||
|
</el-button>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="asker" :label="$t('views.chatLog.table.user')">
|
<el-table-column prop="asker" :label="$t('views.chatLog.table.user')">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ row.asker?.username }}
|
{{ row.asker?.username }}
|
||||||
|
|
@ -238,6 +246,23 @@
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<el-dialog
|
||||||
|
:title="$t('views.chatLog.feedback.listTitle')"
|
||||||
|
v-model="feedbackListDialogVisible"
|
||||||
|
width="720px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<div v-if="currentFeedbackList.length" class="feedback-list">
|
||||||
|
<div v-for="(item, index) in currentFeedbackList" :key="index" class="feedback-list__item">
|
||||||
|
<div class="feedback-list__meta">
|
||||||
|
<span>{{ feedbackTypeLabelMap[item.feedback_type] || item.feedback_type }}</span>
|
||||||
|
<span>{{ datetimeFormat(item.create_time) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="feedback-list__content">{{ item.content }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>-</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -321,6 +346,7 @@ const multipleSelection = ref<any[]>([])
|
||||||
const ChatRecordRef = ref()
|
const ChatRecordRef = ref()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const documentLoading = ref(false)
|
const documentLoading = ref(false)
|
||||||
|
const feedbackListDialogVisible = ref(false)
|
||||||
const paginationConfig = reactive({
|
const paginationConfig = reactive({
|
||||||
current_page: 1,
|
current_page: 1,
|
||||||
page_size: 20,
|
page_size: 20,
|
||||||
|
|
@ -330,6 +356,13 @@ const dialogVisible = ref(false)
|
||||||
const documentDialogVisible = ref(false)
|
const documentDialogVisible = ref(false)
|
||||||
const days = ref<number>(180)
|
const days = ref<number>(180)
|
||||||
const tableData = ref<any[]>([])
|
const tableData = ref<any[]>([])
|
||||||
|
const currentFeedbackList = ref<any[]>([])
|
||||||
|
const feedbackTypeLabelMap = {
|
||||||
|
SUGGESTION: t('views.chatLog.feedback.typeSuggestion'),
|
||||||
|
QUESTION: t('views.chatLog.feedback.typeQuestion'),
|
||||||
|
SUPPLEMENT: t('views.chatLog.feedback.typeSupplement'),
|
||||||
|
OTHER: t('views.chatLog.feedback.typeOther'),
|
||||||
|
} as Record<string, string>
|
||||||
const tableIndexMap = computed<Dict<number>>(() => {
|
const tableIndexMap = computed<Dict<number>>(() => {
|
||||||
return tableData.value
|
return tableData.value
|
||||||
.map((row, index) => ({
|
.map((row, index) => ({
|
||||||
|
|
@ -570,6 +603,11 @@ function openDocumentDialog() {
|
||||||
documentDialogVisible.value = true
|
documentDialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openFeedbackList(row: any) {
|
||||||
|
currentFeedbackList.value = row.feedback_list || []
|
||||||
|
feedbackListDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
changeDayHandle(history_day.value)
|
changeDayHandle(history_day.value)
|
||||||
getDetail()
|
getDetail()
|
||||||
|
|
@ -581,4 +619,32 @@ onMounted(() => {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.feedback-list {
|
||||||
|
max-height: 480px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-list__item {
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-list__item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-list__meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-list__content {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,8 @@ const checkModelType = (model_type: string) => {
|
||||||
const excludedProviders = [
|
const excludedProviders = [
|
||||||
'Anthropic',
|
'Anthropic',
|
||||||
'Amazon Bedrock',
|
'Amazon Bedrock',
|
||||||
|
'OpenAI',
|
||||||
|
'Azure OpenAI',
|
||||||
'Gemini',
|
'Gemini',
|
||||||
'SILICONFLOW',
|
'SILICONFLOW',
|
||||||
'Xorbits Inference',
|
'Xorbits Inference',
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,8 @@ onMounted(() => {
|
||||||
const excludedProviders = [
|
const excludedProviders = [
|
||||||
'Anthropic',
|
'Anthropic',
|
||||||
'Amazon Bedrock',
|
'Amazon Bedrock',
|
||||||
|
'OpenAI',
|
||||||
|
'Azure OpenAI',
|
||||||
'Gemini',
|
'Gemini',
|
||||||
'SILICONFLOW',
|
'SILICONFLOW',
|
||||||
'Xorbits Inference',
|
'Xorbits Inference',
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,17 @@
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="mb-16">{{ data.title || '-' }}</h2>
|
<div class="paragraph-box__header mb-16">
|
||||||
|
<h2 class="paragraph-box__title">{{ data.title || '-' }}</h2>
|
||||||
|
<div class="paragraph-box__meta" v-if="data.create_time || data.update_time">
|
||||||
|
<span v-if="data.create_time">
|
||||||
|
{{ $t('common.createTime') }}:{{ datetimeFormat(data.create_time) }}
|
||||||
|
</span>
|
||||||
|
<span v-if="data.update_time">
|
||||||
|
{{ $t('common.updateTime') }}:{{ datetimeFormat(data.update_time) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<MdPreview
|
<MdPreview
|
||||||
ref="editorRef"
|
ref="editorRef"
|
||||||
editorId="preview-only"
|
editorId="preview-only"
|
||||||
|
|
@ -154,6 +164,7 @@ import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||||
import permissionMap from '@/permission'
|
import permissionMap from '@/permission'
|
||||||
import { t } from '@/locales'
|
import { t } from '@/locales'
|
||||||
|
import { datetimeFormat } from '@/utils/time'
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: any
|
data: any
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
|
@ -337,6 +348,25 @@ watch(dialogVisible, (val: boolean) => {
|
||||||
overflow: inherit;
|
overflow: inherit;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paragraph-box__header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paragraph-box__title {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paragraph-box__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--app-text-color-light);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,19 @@ function toChat(row: any) {
|
||||||
? '?' + mapToUrlParams(apiInputParams.value)
|
? '?' + mapToUrlParams(apiInputParams.value)
|
||||||
: ''
|
: ''
|
||||||
ApplicationResourceApi.getAccessToken(row.id, loading).then((res: any) => {
|
ApplicationResourceApi.getAccessToken(row.id, loading).then((res: any) => {
|
||||||
window.open(application.location + res?.data?.access_token + apiParams)
|
if (res?.data?.is_active) {
|
||||||
|
window.open(application.location + res?.data?.access_token + apiParams)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const debugPreviewUrl = router.resolve({
|
||||||
|
name: 'AppSetting',
|
||||||
|
params: {
|
||||||
|
from: 'resource-management',
|
||||||
|
id: row.id,
|
||||||
|
type: row.type,
|
||||||
|
},
|
||||||
|
}).href
|
||||||
|
window.open(debugPreviewUrl)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue