Merge branch 'main' into agent-demo
This commit is contained in:
commit
cde6b98336
@ -31,7 +31,8 @@ class OpenAIBase():
|
||||
stream=True,
|
||||
)
|
||||
for chunk in response:
|
||||
yield chunk.choices[0].delta
|
||||
if len(chunk.choices) > 0:
|
||||
yield chunk.choices[0].delta
|
||||
|
||||
def _get_response(self, messages):
|
||||
response = self.client.chat.completions.create(
|
||||
|
||||
@ -77,6 +77,7 @@
|
||||
<div class="chat-box" :class="{ 'wide-screen': meta.wideScreen, 'font-smaller': meta.fontSize === 'smaller', 'font-larger': meta.fontSize === 'larger' }">
|
||||
<MessageComponent
|
||||
v-for="message in conv.messages"
|
||||
:message="message"
|
||||
:key="message.id"
|
||||
:role="message.role"
|
||||
:content="message.text"
|
||||
@ -87,9 +88,10 @@
|
||||
:is-processing="isStreaming"
|
||||
:error-message="message.message"
|
||||
@retry="retryMessage(message.id)"
|
||||
@retryStoppedMessage="retryStoppedMessage(message.id)"
|
||||
>
|
||||
<template #refs v-if="message.role=='received' && message.status=='finished'">
|
||||
<RefsComponent :message="message" />
|
||||
<RefsComponent :message="message" :conv="conv" @retry="retryMessage(message.id)" />
|
||||
</template>
|
||||
</MessageComponent>
|
||||
</div>
|
||||
@ -100,7 +102,7 @@
|
||||
:is-loading="isStreaming"
|
||||
:send-button-disabled="!conv.inputText && !isStreaming"
|
||||
:auto-size="{ minRows: 2, maxRows: 10 }"
|
||||
@send="sendMessage"
|
||||
@send="handleSendOrStop"
|
||||
@keydown="handleKeyDown"
|
||||
>
|
||||
<template #options-left>
|
||||
@ -171,6 +173,9 @@ import {
|
||||
BulbOutlined,
|
||||
CaretRightOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
PauseOutlined,
|
||||
ReloadOutlined,
|
||||
CopyOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { Marked } from 'marked';
|
||||
@ -221,7 +226,7 @@ const meta = reactive(JSON.parse(localStorage.getItem('meta')) || {
|
||||
selectedKB: null,
|
||||
stream: true,
|
||||
summary_title: false,
|
||||
history_round: 5,
|
||||
history_round: 20,
|
||||
db_id: null,
|
||||
fontSize: 'default',
|
||||
wideScreen: false,
|
||||
@ -253,6 +258,24 @@ const renderMarkdown = (msg) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 从 message 中获取 history 信息,每个消息都是 {role, content} 的格式
|
||||
const getHistory = () => {
|
||||
const history = conv.value.messages.map((msg) => {
|
||||
if (msg.text) {
|
||||
return {
|
||||
role: msg.role === 'sent' ? 'user' : 'assistant',
|
||||
content: msg.text
|
||||
}
|
||||
}
|
||||
}).reduce((acc, cur) => {
|
||||
if (cur) {
|
||||
acc.push(cur)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
return history.slice(-meta.history_round)
|
||||
}
|
||||
|
||||
const useDatabase = (index) => {
|
||||
const selected = opts.databases[index]
|
||||
console.log(selected)
|
||||
@ -441,18 +464,24 @@ const loadDatabases = () => {
|
||||
|
||||
// 新函数用于处理 fetch 请求
|
||||
const fetchChatResponse = (user_input, cur_res_id) => {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
|
||||
const params = {
|
||||
query: user_input,
|
||||
history: getHistory(),
|
||||
meta: meta,
|
||||
cur_res_id: cur_res_id,
|
||||
}
|
||||
console.log(params)
|
||||
|
||||
fetch('/api/chat/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
query: user_input,
|
||||
history: conv.value.history,
|
||||
meta: meta,
|
||||
cur_res_id: cur_res_id,
|
||||
thread_id: conv.value.id.toString(),
|
||||
}),
|
||||
body: JSON.stringify(params),
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
signal // 添加 signal 用于中断请求
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.body) throw new Error("ReadableStream not supported.");
|
||||
@ -510,13 +539,24 @@ const fetchChatResponse = (user_input, cur_res_id) => {
|
||||
readChunk();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
updateMessage({
|
||||
id: cur_res_id,
|
||||
status: "error",
|
||||
});
|
||||
if (error.name === 'AbortError') {
|
||||
console.log('Fetch aborted');
|
||||
} else {
|
||||
console.error(error);
|
||||
updateMessage({
|
||||
id: cur_res_id,
|
||||
status: "error",
|
||||
});
|
||||
}
|
||||
isStreaming.value = false;
|
||||
});
|
||||
|
||||
// 监听 isStreaming 变化,当为 false 时中断请求
|
||||
watch(isStreaming, (newValue) => {
|
||||
if (!newValue) {
|
||||
controller.abort();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -606,6 +646,37 @@ watch(
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 处理发送或停止
|
||||
const handleSendOrStop = () => {
|
||||
if (isStreaming.value) {
|
||||
// 停止生成
|
||||
isStreaming.value = false;
|
||||
const lastMessage = conv.value.messages[conv.value.messages.length - 1];
|
||||
if (lastMessage) {
|
||||
lastMessage.isStoppedByUser = true;
|
||||
lastMessage.status = 'stopped';
|
||||
}
|
||||
} else {
|
||||
// 发送消息
|
||||
sendMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// 重试被停止的消息
|
||||
const retryStoppedMessage = (id) => {
|
||||
// 找到用户的原始问题
|
||||
const messageIndex = conv.value.messages.findIndex(msg => msg.id === id);
|
||||
if (messageIndex > 0) {
|
||||
const userMessage = conv.value.messages[messageIndex - 1];
|
||||
if (userMessage && userMessage.role === 'sent') {
|
||||
conv.value.inputText = userMessage.text;
|
||||
// 删除被停止的消息,以及所有后面的消息
|
||||
conv.value.messages = conv.value.messages.slice(0, messageIndex-1);
|
||||
// sendMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const modelNames = computed(() => configStore.config?.model_names)
|
||||
const modelStatus = computed(() => configStore.config?.model_provider_status)
|
||||
const customModels = computed(() => configStore.config?.custom_models || [])
|
||||
@ -620,6 +691,7 @@ const selectModel = (provider, name) => {
|
||||
configStore.setConfigValue('model_provider', provider)
|
||||
configStore.setConfigValue('model_name', name)
|
||||
message.success(`已切换到模型: ${provider}/${name}`)
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -41,6 +41,11 @@
|
||||
<!-- 消息内容 -->
|
||||
<div v-else-if="contentHtml" v-html="contentHtml" class="message-md"></div>
|
||||
|
||||
<div v-if="message.isStoppedByUser" class="retry-hint">
|
||||
你停止生成了本次回答
|
||||
<span class="retry-link" @click="emit('retryStoppedMessage', message.id)">重新编辑问题</span>
|
||||
</div>
|
||||
|
||||
<!-- 工具调用 (AgentView特有) -->
|
||||
<slot name="tool-calls"></slot>
|
||||
|
||||
@ -60,6 +65,10 @@ import { CaretRightOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps({
|
||||
// 消息角色:'user'|'assistant'|'sent'|'received'
|
||||
message: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
required: true
|
||||
@ -114,7 +123,7 @@ const statusDefination = {
|
||||
error: '错误'
|
||||
}
|
||||
|
||||
const emit = defineEmits(['retry']);
|
||||
const emit = defineEmits(['retry', 'retryStoppedMessage']);
|
||||
|
||||
// 推理面板展开状态
|
||||
const reasoningActiveKey = ref(['show']);
|
||||
@ -319,6 +328,35 @@ const isEmptyAndLoading = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.retry-hint {
|
||||
margin-top: 8px;
|
||||
padding: 8px 16px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.retry-link {
|
||||
color: #1890ff;
|
||||
cursor: pointer;
|
||||
margin-left: 4px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-icon-only {
|
||||
&:has(.anticon-stop) {
|
||||
background-color: #ff4d4f !important;
|
||||
|
||||
&:hover {
|
||||
background-color: #ff7875 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.loading-dots {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@ -15,11 +15,17 @@
|
||||
<slot name="options-left"></slot>
|
||||
</div>
|
||||
<div class="options__right">
|
||||
<a-button @click="handleSend" :disabled="sendButtonDisabled" type="link">
|
||||
<a-tooltip :title="isLoading ? '停止回答' : ''">
|
||||
<a-button
|
||||
@click="handleSendOrStop"
|
||||
:disabled="sendButtonDisabled"
|
||||
type="link"
|
||||
>
|
||||
<template #icon>
|
||||
<component :is="getIcon" />
|
||||
<component :is="getIcon" class="send-btn"/>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -30,7 +36,8 @@ import { ref, computed, toRefs } from 'vue';
|
||||
import {
|
||||
SendOutlined,
|
||||
ArrowUpOutlined,
|
||||
LoadingOutlined
|
||||
LoadingOutlined,
|
||||
PauseOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps({
|
||||
@ -74,13 +81,13 @@ const emit = defineEmits(['update:modelValue', 'send', 'keydown']);
|
||||
const iconComponents = {
|
||||
'SendOutlined': SendOutlined,
|
||||
'ArrowUpOutlined': ArrowUpOutlined,
|
||||
'LoadingOutlined': LoadingOutlined
|
||||
'PauseOutlined': PauseOutlined
|
||||
};
|
||||
|
||||
// 根据传入的图标名动态获取组件
|
||||
const getIcon = computed(() => {
|
||||
if (props.isLoading) {
|
||||
return LoadingOutlined;
|
||||
return PauseOutlined;
|
||||
}
|
||||
return iconComponents[props.sendIcon] || ArrowUpOutlined;
|
||||
});
|
||||
@ -97,7 +104,7 @@ const handleKeyPress = (e) => {
|
||||
};
|
||||
|
||||
// 处理发送按钮点击
|
||||
const handleSend = () => {
|
||||
const handleSendOrStop = () => {
|
||||
emit('send');
|
||||
};
|
||||
</script>
|
||||
@ -218,6 +225,7 @@ button.ant-btn-icon-only {
|
||||
&:hover {
|
||||
background-color: var(--main-600);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
<!-- <span class="item btn" @click="likeThisResponse(msg)"><LikeOutlined /></span> -->
|
||||
<!-- <span class="item btn" @click="dislikeThisResponse(msg)"><DislikeOutlined /></span> -->
|
||||
<span class="item"><BulbOutlined /> {{ msg.meta.server_model_name }}</span>
|
||||
<span class="item btn" @click="copyText(msg.text)"><CopyOutlined /></span>
|
||||
<span class="item btn" @click="copyText(msg.text)" title="复制"><CopyOutlined /></span>
|
||||
<span class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined /></span>
|
||||
<span
|
||||
class="item btn"
|
||||
@click="openSubGraph(msg)"
|
||||
@ -107,20 +108,23 @@ import {
|
||||
GlobalOutlined,
|
||||
FileTextOutlined,
|
||||
CopyOutlined,
|
||||
LikeOutlined,
|
||||
DislikeOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
BulbOutlined,
|
||||
FileOutlined,
|
||||
ClockCircleOutlined
|
||||
ClockCircleOutlined,
|
||||
ReloadOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import GraphContainer from './GraphContainer.vue' // 导入 GraphContainer 组件
|
||||
|
||||
|
||||
const emit = defineEmits(['retry']);
|
||||
const props = defineProps({
|
||||
message: Object,
|
||||
conv: Object,
|
||||
})
|
||||
|
||||
const msg = ref(props.message)
|
||||
const conv = ref(props.conv)
|
||||
|
||||
// 使用 useClipboard 实现复制功能
|
||||
const { copy, isSupported } = useClipboard()
|
||||
@ -210,6 +214,11 @@ const formatDate = (timestamp) => {
|
||||
const getPercent = (value) => {
|
||||
return parseFloat((value * 100).toFixed(2))
|
||||
}
|
||||
|
||||
// 添加重新生成方法
|
||||
const regenerateMessage = () => {
|
||||
emit('retry')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user