From d1b5b05810f7e71f3930c97fc00e784c610bab66 Mon Sep 17 00:00:00 2001 From: ulleo Date: Thu, 17 Jul 2025 17:07:52 +0800 Subject: [PATCH] feat: chat --- backend/alembic/versions/029_modify_chat.py | 54 +++++ backend/apps/chat/models/chat_model.py | 8 +- backend/template.yaml | 4 +- frontend/package.json | 1 + .../src/assets/svg/icon_replace_outlined.svg | 4 + .../src/assets/svg/icon_screen_outlined.svg | 5 + .../src/assets/svg/icon_start_outlined.svg | 3 + frontend/src/i18n/zh-CN.json | 3 +- frontend/src/utils/markdown.ts | 20 +- frontend/src/views/chat/RecommendQuestion.vue | 1 - .../src/views/chat/answer/AnalysisAnswer.vue | 1 + frontend/src/views/chat/answer/BaseAnswer.vue | 5 + .../src/views/chat/answer/PredictAnswer.vue | 1 + .../src/views/chat/component/MdComponent.vue | 22 +- frontend/src/views/chat/index.vue | 190 +++++++++++++++--- 15 files changed, 277 insertions(+), 45 deletions(-) create mode 100644 backend/alembic/versions/029_modify_chat.py create mode 100644 frontend/src/assets/svg/icon_replace_outlined.svg create mode 100644 frontend/src/assets/svg/icon_screen_outlined.svg create mode 100644 frontend/src/assets/svg/icon_start_outlined.svg diff --git a/backend/alembic/versions/029_modify_chat.py b/backend/alembic/versions/029_modify_chat.py new file mode 100644 index 00000000..62b8ee9c --- /dev/null +++ b/backend/alembic/versions/029_modify_chat.py @@ -0,0 +1,54 @@ +"""029_modify_chat + +Revision ID: 77d4c39ec22f +Revises: e96b16d3daab +Create Date: 2025-07-17 17:05:13.392973 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '77d4c39ec22f' +down_revision = 'e96b16d3daab' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('chat', sa.Column('oid', sa.BigInteger(), nullable=True)) + op.execute('update chat set oid = 1') + op.alter_column('chat', 'create_time', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=True) + op.alter_column('chat_record', 'create_time', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=True) + op.alter_column('chat_record', 'finish_time', + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.DateTime(), + existing_nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('chat_record', 'finish_time', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=True) + op.alter_column('chat_record', 'create_time', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=True) + op.alter_column('chat', 'create_time', + existing_type=sa.DateTime(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=True) + op.drop_column('chat', 'oid') + # ### end Alembic commands ### diff --git a/backend/apps/chat/models/chat_model.py b/backend/apps/chat/models/chat_model.py index 3f891cbf..3d43fbcd 100644 --- a/backend/apps/chat/models/chat_model.py +++ b/backend/apps/chat/models/chat_model.py @@ -17,7 +17,8 @@ from apps.template.filter.generator import get_permissions_template class Chat(SQLModel, table=True): __tablename__ = "chat" id: Optional[int] = Field(sa_column=Column(BigInteger, Identity(always=True), primary_key=True)) - create_time: datetime = Field(sa_column=Column(DateTime(timezone=True), nullable=True)) + oid: Optional[int] = Field(sa_column=Column(BigInteger, nullable=True, default=1)) + create_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True)) create_by: int = Field(sa_column=Column(BigInteger, nullable=True)) brief: str = Field(max_length=64, nullable=True) chat_type: str = Field(max_length=20, default="chat") # chat, datasource @@ -31,8 +32,8 @@ class ChatRecord(SQLModel, table=True): chat_id: int = Field(sa_column=Column(BigInteger, nullable=False)) ai_modal_id: Optional[int] = Field(sa_column=Column(BigInteger)) first_chat: bool = Field(sa_column=Column(Boolean, nullable=True, default=False)) - create_time: datetime = Field(sa_column=Column(DateTime(timezone=True), nullable=True)) - finish_time: datetime = Field(sa_column=Column(DateTime(timezone=True), nullable=True)) + create_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True)) + finish_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True)) create_by: int = Field(sa_column=Column(BigInteger, nullable=True)) datasource: int = Field(sa_column=Column(BigInteger, nullable=True)) engine_type: str = Field(max_length=64) @@ -66,6 +67,7 @@ class ChatRecord(SQLModel, table=True): analysis_record_id: int = Field(sa_column=Column(BigInteger, nullable=True)) predict_record_id: int = Field(sa_column=Column(BigInteger, nullable=True)) + class CreateChat(BaseModel): id: int = None question: str = None diff --git a/backend/template.yaml b/backend/template.yaml index a801d88c..355a90e8 100644 --- a/backend/template.yaml +++ b/backend/template.yaml @@ -132,8 +132,8 @@ template: 你是一个数据分析师,你的任务是根据给定的数据进行数据预测,我将以json格式给你一组数据,你帮我预测之后的数据(一段可以展示趋势的数据,至少2个周期),用json格式返回。 ```json - 无法预测或者不支持预测的数据请直接返回:"抱歉,该数据无法进行预测。(有原因则返回无法预测的原因)" - 不需要返回原有数据,直接返回预测的部份 + 无法预测或者不支持预测的数据请直接返回(不需要返回JSON格式):"抱歉,该数据无法进行预测。(有原因则返回无法预测的原因)" + 如果可以预测,则不需要返回原有数据,直接返回预测的部份 user: | ### 请使用 i18n: {lang} 对应的语言输出你的结果 diff --git a/frontend/package.json b/frontend/package.json index 677c94c8..c06d2008 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "element-plus": "^2.10.1", "element-plus-secondary": "^1.0.0", "element-resize-detector": "^1.2.4", + "github-markdown-css": "^5.8.1", "highlight.js": "^11.11.1", "html2canvas": "^1.4.1", "lodash": "^4.17.21", diff --git a/frontend/src/assets/svg/icon_replace_outlined.svg b/frontend/src/assets/svg/icon_replace_outlined.svg new file mode 100644 index 00000000..b0de84d3 --- /dev/null +++ b/frontend/src/assets/svg/icon_replace_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/icon_screen_outlined.svg b/frontend/src/assets/svg/icon_screen_outlined.svg new file mode 100644 index 00000000..ed5a00ba --- /dev/null +++ b/frontend/src/assets/svg/icon_screen_outlined.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/assets/svg/icon_start_outlined.svg b/frontend/src/assets/svg/icon_start_outlined.svg new file mode 100644 index 00000000..9ac68907 --- /dev/null +++ b/frontend/src/assets/svg/icon_start_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index 66bb2feb..45e2ed8f 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -84,7 +84,8 @@ "data_predict": "数据分析", "chat_search": "搜索", "thinking": "思考中", - "thinking_step": "思考过程" + "thinking_step": "思考过程", + "ask_again": "重新生成" }, "ds": { "title": "数据源", diff --git a/frontend/src/utils/markdown.ts b/frontend/src/utils/markdown.ts index 6d9fe595..817f431d 100644 --- a/frontend/src/utils/markdown.ts +++ b/frontend/src/utils/markdown.ts @@ -2,25 +2,23 @@ import MarkdownIt from 'markdown-it' import hljs from 'highlight.js' const md = new MarkdownIt({ - html: true, - linkify: true, + html: false, + linkify: false, + breaks: true, + typographer: true, + quotes: '“”‘’', highlight: (str: any, lang: any): string => { if (lang && hljs.getLanguage(lang)) { try { - return ` -
-          
-          ${hljs.highlight(str, { language: lang, ignoreIllegals: true }).value}
-          
-          
- ` + return `
+                  ${hljs.highlight(str, { language: lang, ignoreIllegals: true }).value}
+                
` } catch (e) { console.error(e) - return str } } - return '
' + md.utils.escapeHtml(str) + '
' + return '
' + md.utils.escapeHtml(str) + '
' }, }) diff --git a/frontend/src/views/chat/RecommendQuestion.vue b/frontend/src/views/chat/RecommendQuestion.vue index dc705406..21f043a7 100644 --- a/frontend/src/views/chat/RecommendQuestion.vue +++ b/frontend/src/views/chat/RecommendQuestion.vue @@ -53,7 +53,6 @@ function clickQuestion(question: string): void { diff --git a/frontend/src/views/chat/answer/PredictAnswer.vue b/frontend/src/views/chat/answer/PredictAnswer.vue index bf157092..89aba684 100644 --- a/frontend/src/views/chat/answer/PredictAnswer.vue +++ b/frontend/src/views/chat/answer/PredictAnswer.vue @@ -193,6 +193,7 @@ defineExpose({ sendMessage, index: index.value, chatList: _chatList }) diff --git a/frontend/src/views/chat/component/MdComponent.vue b/frontend/src/views/chat/component/MdComponent.vue index 9f066685..ea0bdf29 100644 --- a/frontend/src/views/chat/component/MdComponent.vue +++ b/frontend/src/views/chat/component/MdComponent.vue @@ -1,6 +1,7 @@ - + diff --git a/frontend/src/views/chat/index.vue b/frontend/src/views/chat/index.vue index 8f28cb59..2f00b03a 100644 --- a/frontend/src/views/chat/index.vue +++ b/frontend/src/views/chat/index.vue @@ -117,23 +117,67 @@ @error="onChartAnswerError" >
todo
-
- - {{ t('chat.data_analysis') }} - - - {{ t('chat.data_predict') }} - +
+ {{ message.record?.error }} +
+
+
+ + + + + + + + +
+
+ {{ datetimeFormat(message.record?.create_time) }} +
@@ -233,8 +303,12 @@ import { useI18n } from 'vue-i18n' import { endsWith, find, startsWith } from 'lodash-es' import icon_new_chat_outlined from '@/assets/svg/icon_new_chat_outlined.svg' import icon_sidebar_outlined from '@/assets/svg/icon_sidebar_outlined.svg' +import icon_replace_outlined from '@/assets/svg/icon_replace_outlined.svg' +import icon_screen_outlined from '@/assets/svg/icon_screen_outlined.svg' +import icon_start_outlined from '@/assets/svg/icon_start_outlined.svg' import logo_fold from '@/assets/LOGO-fold.svg' import logo from '@/assets/LOGO.svg' +import { datetimeFormat } from '@/utils/utils.ts' import { useAssistantStore } from '@/stores/assistant' const assistantStore = useAssistantStore() @@ -536,6 +610,13 @@ function onAnalysisAnswerError() { isTyping.value = false } +function askAgain(message: ChatMessage) { + inputMessage.value = message.record?.question ?? '' + nextTick(() => { + sendMessage() + }) +} + async function clickAnalysis(id?: number) { const baseRecord = find(currentChat.value.records, (value) => id === value.id) if (baseRecord == undefined) { @@ -750,11 +831,72 @@ defineExpose({ } } -.analysis-container { - color: var(--ed-text-color-primary); - font-size: 12px; - line-height: 1.7692307692; - padding: 16px 22px; +.error-container { + margin-top: 12px; + font-weight: 400; + font-size: 16px; + line-height: 24px; + color: rgba(31, 35, 41, 1); +} + +.tool-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + + min-height: 22px; + + margin-top: 12px; + margin-bottom: 12px; + + .tool-times { + font-size: 14px; + font-weight: 400; + line-height: 22px; + color: rgba(100, 106, 115, 1); + } + + .tool-btns { + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + + column-gap: 16px; + row-gap: 8px; + + .tool-btn { + font-size: 14px; + font-weight: 400; + line-height: 22px; + color: rgba(100, 106, 115, 1); + + .tool-btn-inner { + display: flex; + flex-direction: row; + align-items: center; + } + + &:hover { + background: rgba(31, 35, 41, 0.1); + } + &:active { + background: rgba(31, 35, 41, 0.1); + } + } + + .btn-text { + margin-left: 4px; + } + + .divider { + width: 1px; + height: 16px; + border-left: 1px solid rgba(31, 35, 41, 0.15); + } + } } .welcome-content-block {