Fix chat experience (#211)

This commit is contained in:
Germey
2025-07-12 00:46:26 +08:00
committed by GitHub
parent 117aabcb51
commit dddc2dcb99
11 changed files with 120 additions and 20 deletions
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix chat experience for error",
"packageName": "@acedatacloud/nexior",
"email": "cqc@cuiqingcai.com",
"dependentChangeType": "patch"
}
+1
View File
@@ -36,6 +36,7 @@ html.dark,
word-wrap: break-word;
border-radius: 1rem;
border: none;
--el-card-padding: 15px;
}
.el-breadcrumb__inner a,
+4
View File
@@ -171,9 +171,13 @@ export default defineComponent({
return this.$store.state.chat.modelGroup;
},
errorText() {
console.debug('error', this.message.error);
if (!this.message.error || !this.message.error?.code) {
return undefined;
}
if (this.message.error?.message) {
return this.message.error.message;
}
switch (this.message.error?.code) {
case ERROR_CODE_USED_UP:
return this.$t('chat.message.errorUsedUp');
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<div class="relative inline-block text-left">
<div class="center">
<div @click="toggleMenu">
<user-avatar class="cursor-pointer" />
</div>
+7
View File
@@ -0,0 +1,7 @@
export const ERROR_STATUS_BAD_REQUEST = 400;
export const ERROR_STATUS_FORBIDDEN = 403;
export const ERROR_STATUS_TOO_MANY_REQUESTS = 429;
export const ERROR_STATUS_NOT_FOUND = 404;
export const ERROR_STATUS_API_ERROR = 500;
export const ERROR_STATUS_TIMEOUT = 504;
export const ERROR_STATUS_BUSY = 500;
+1
View File
@@ -1,4 +1,5 @@
export * from './errorCode';
export * from './errorStatus';
export * from './endpoint';
export * from './action';
export * from './chat';
+1 -1
View File
@@ -64,7 +64,7 @@ export default defineComponent({
.chat {
height: 100%;
padding: 15px;
padding: 45px 0 0 15px;
flex: 1;
width: calc(100% - 250px);
height: 100%;
+69
View File
@@ -0,0 +1,69 @@
import {
ERROR_CODE_API_ERROR,
ERROR_CODE_BAD_REQUEST,
ERROR_CODE_BUSY,
ERROR_CODE_FORBIDDEN,
ERROR_CODE_TIMEOUT,
ERROR_CODE_TOO_MANY_REQUESTS,
ERROR_STATUS_API_ERROR,
ERROR_STATUS_BAD_REQUEST,
ERROR_STATUS_BUSY,
ERROR_STATUS_FORBIDDEN,
ERROR_STATUS_TIMEOUT,
ERROR_STATUS_TOO_MANY_REQUESTS
} from '@/constants';
export class BaseError extends Error {
public status: number;
public code: string;
public detail: string;
public extraData?: Record<string, any>;
constructor(status: number, code: string, detail: string, extraData?: Record<string, any>) {
super();
this.status = status;
this.code = code;
this.detail = detail;
this.extraData = extraData;
}
get message(): string {
return `${this.code} - ${this.detail}`;
}
}
export class BadRequestError extends BaseError {
constructor(detail: string, extraData?: Record<string, any>) {
super(ERROR_STATUS_BAD_REQUEST, ERROR_CODE_BAD_REQUEST, detail, extraData);
}
}
export class ApiError extends BaseError {
constructor(detail: string, extraData?: Record<string, any>) {
super(ERROR_STATUS_API_ERROR, ERROR_CODE_API_ERROR, detail, extraData);
}
}
export class BusyError extends BaseError {
constructor(detail: string, extraData?: Record<string, any>) {
super(ERROR_STATUS_BUSY, ERROR_CODE_BUSY, detail, extraData);
}
}
export class ForbiddenError extends BaseError {
constructor(detail: string, extraData?: Record<string, any>) {
super(ERROR_STATUS_FORBIDDEN, ERROR_CODE_FORBIDDEN, detail, extraData);
}
}
export class TimeoutError extends BaseError {
constructor(detail: string, extraData?: Record<string, any>) {
super(ERROR_STATUS_TIMEOUT, ERROR_CODE_TIMEOUT, detail, extraData);
}
}
export class TooManyRequestsError extends BaseError {
constructor(detail: string, extraData?: Record<string, any>) {
super(ERROR_STATUS_TOO_MANY_REQUESTS, ERROR_CODE_TOO_MANY_REQUESTS, detail, extraData);
}
}
+1
View File
@@ -21,3 +21,4 @@ export * from './headshots';
export * from './suno';
export * from './site';
export * from './exchange';
export * from './error';
+16 -2
View File
@@ -1,5 +1,7 @@
import axios, { AxiosResponse } from 'axios';
import {
ApiError,
BaseError,
IChatConversation,
IChatConversationAction,
IChatConversationOptions,
@@ -7,7 +9,7 @@ import {
IChatConversationResponse,
IChatConversationsResponse
} from '@/models';
import { BASE_URL_API } from '@/constants';
import { BASE_URL_API, ERROR_CODE_API_ERROR } from '@/constants';
class ChatOperator {
async chatConversation(
@@ -27,7 +29,19 @@ class ChatOperator {
body: JSON.stringify(data)
});
if (!response.body) throw new Error('ReadableStream not supported.');
// check response status
if (!response.ok) {
const errorText = await response.text();
const status = response.status;
const errorJson = errorText ? JSON.parse(errorText) : {};
const errorMessage = errorJson?.error?.message || 'An error occurred';
const errorCode = errorJson?.error?.code || ERROR_CODE_API_ERROR;
console.error('Error message:', errorMessage, 'Error code:', errorCode);
reject(new BaseError(status, errorCode, errorMessage));
return;
}
if (!response.body) throw new ApiError('ReadableStream not supported.');
const reader = response.body.getReader();
const decoder = new TextDecoder();
+12 -16
View File
@@ -17,7 +17,7 @@
@restart="onRestart"
/>
</div>
<div class="composer">
<div class="starter">
<composer
v-model:question="question"
:disabled="answering"
@@ -38,7 +38,7 @@ import axios from 'axios';
import { defineComponent, ref } from 'vue';
import Message from '@/components/chat/Message.vue';
import { CHAT_MODEL_GROUPS, CHAT_MODELS, ROLE_ASSISTANT, ROLE_USER } from '@/constants';
import { IChatMessageState, IChatConversationResponse, IChatConversation, IChatMessage } from '@/models';
import { IChatMessageState, IChatConversationResponse, IChatConversation, IChatMessage, BaseError } from '@/models';
import Composer from '@/components/chat/Composer.vue';
import ModelSelector from '@/components/chat/ModelSelector.vue';
import { ERROR_CODE_CANCELED, ERROR_CODE_NOT_APPLIED, ERROR_CODE_UNKNOWN } from '@/constants/errorCode';
@@ -447,27 +447,23 @@ export default defineComponent({
});
},
async handleRequestError(error: any) {
console.error('error happened', error);
if (this.messages && this.messages.length > 0) {
this.messages[this.messages.length - 1].state = IChatMessageState.FAILED;
}
if (error.name === 'AbortError') {
console.error('aborted');
return;
}
console.error(error);
if (axios.isCancel(error)) {
} else if (error instanceof BaseError) {
console.debug('BaseError', error);
this.messages[this.messages.length - 1].error = {
code: error.code,
message: error.detail
};
} else if (axios.isCancel(error)) {
this.messages[this.messages.length - 1].error = {
code: ERROR_CODE_CANCELED
};
} else if (error?.response?.data) {
let data = error?.response?.data;
if (isJSONString(data)) {
data = JSON.parse(data);
}
console.debug('error', data);
if (this.messages && this.messages.length > 0) {
this.messages[this.messages.length - 1].error = data.error;
}
} else {
if (this.messages && this.messages.length > 0) {
this.messages[this.messages.length - 1].error = {
@@ -526,7 +522,7 @@ export default defineComponent({
}
&.empty {
position: relative;
.composer {
.starter {
position: absolute;
width: 100%;
top: 50%;
@@ -543,7 +539,7 @@ export default defineComponent({
margin-bottom: 15px;
}
}
.composer {
.starter {
height: fit-content;
}
}