|
@@ -20,7 +20,6 @@ const md = new MarkdownIt({
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const renderedMessage = (content) => {
|
|
const renderedMessage = (content) => {
|
|
|
- // console.log('content', content)
|
|
|
|
|
if (!content) {
|
|
if (!content) {
|
|
|
return ''
|
|
return ''
|
|
|
}
|
|
}
|
|
@@ -69,9 +68,9 @@ const messages = ref<MessageItem[]>([
|
|
|
}
|
|
}
|
|
|
])
|
|
])
|
|
|
messages.value = JSON.parse(sessionStorage.getItem('AIChat')) || messages.value
|
|
messages.value = JSON.parse(sessionStorage.getItem('AIChat')) || messages.value
|
|
|
-onMounted(() => {
|
|
|
|
|
|
|
+const handleOpen = () => {
|
|
|
scrollToBottom()
|
|
scrollToBottom()
|
|
|
-})
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
watch(
|
|
watch(
|
|
|
() => messages.value,
|
|
() => messages.value,
|
|
@@ -97,9 +96,14 @@ const progressStatus = {
|
|
|
|
|
|
|
|
const isShowTips = ref(false) // 是否展示提示信息
|
|
const isShowTips = ref(false) // 是否展示提示信息
|
|
|
|
|
|
|
|
|
|
+const isShowLoadingDots = ref(false) // 是否展示加载点
|
|
|
const parseHtmlString = (data) => {
|
|
const parseHtmlString = (data) => {
|
|
|
|
|
+ if (!data) {
|
|
|
|
|
+ isShowLoadingDots.value = false // 停止显示加载点
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
const lines = data.split('\n')
|
|
const lines = data.split('\n')
|
|
|
-
|
|
|
|
|
|
|
+ isShowLoadingDots.value = true // 开始显示加载点
|
|
|
function streamMarkdown() {
|
|
function streamMarkdown() {
|
|
|
const lastMsg: any = messages.value[messages.value.length - 1]
|
|
const lastMsg: any = messages.value[messages.value.length - 1]
|
|
|
let index = 0
|
|
let index = 0
|
|
@@ -111,6 +115,7 @@ const parseHtmlString = (data) => {
|
|
|
index++
|
|
index++
|
|
|
scrollToBottom() // 滚动到底部
|
|
scrollToBottom() // 滚动到底部
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ isShowLoadingDots.value = false // 停止显示加载点
|
|
|
clearInterval(timer)
|
|
clearInterval(timer)
|
|
|
}
|
|
}
|
|
|
}, 150)
|
|
}, 150)
|
|
@@ -121,22 +126,35 @@ const parseHtmlString = (data) => {
|
|
|
|
|
|
|
|
const progressInterval = ref()
|
|
const progressInterval = ref()
|
|
|
const serial_no = ref()
|
|
const serial_no = ref()
|
|
|
|
|
+const is_FixedAnswer = ref(true) // 是否为预设问题 true是自由问题 false是预设问题
|
|
|
const aiChat = (question, isPresetQuestion) => {
|
|
const aiChat = (question, isPresetQuestion) => {
|
|
|
serial_no.value = userStore.userInfo?.uname + Date.now().toString()
|
|
serial_no.value = userStore.userInfo?.uname + Date.now().toString()
|
|
|
|
|
+ let fixed_faq = ''
|
|
|
|
|
+ if (!is_FixedAnswer.value) {
|
|
|
|
|
+ fixed_faq = messages.value[messages.value.length - 3].content
|
|
|
|
|
+ } else if (isPresetQuestion) {
|
|
|
|
|
+ fixed_faq = question
|
|
|
|
|
+ }
|
|
|
$api
|
|
$api
|
|
|
.aiChat({
|
|
.aiChat({
|
|
|
serial_no: serial_no.value,
|
|
serial_no: serial_no.value,
|
|
|
prompt: sessionStorage.getItem('prompt'),
|
|
prompt: sessionStorage.getItem('prompt'),
|
|
|
- // question_type: isPresetQuestion ? 'Predefined Question' : 'Free Question',
|
|
|
|
|
- question_type: 'Free Question',
|
|
|
|
|
- question_content: question
|
|
|
|
|
|
|
+ question_type:
|
|
|
|
|
+ isPresetQuestion || !is_FixedAnswer.value ? 'Predefined Question' : 'Free Question',
|
|
|
|
|
+ question_content: question,
|
|
|
|
|
+ fixed_faq: fixed_faq
|
|
|
})
|
|
})
|
|
|
.then((res) => {
|
|
.then((res) => {
|
|
|
- if (isPause.value) {
|
|
|
|
|
|
|
+ if (isPause.value || queryTime.value === -1) {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
if (res.code === 200) {
|
|
if (res.code === 200) {
|
|
|
clearInterval(progressInterval.value)
|
|
clearInterval(progressInterval.value)
|
|
|
|
|
+
|
|
|
|
|
+ is_FixedAnswer.value =
|
|
|
|
|
+ res.data.is_fixedAnswer_end !== null || res.data.is_fixedAnswer_end !== undefined
|
|
|
|
|
+ ? res.data.is_fixedAnswer_end
|
|
|
|
|
+ : true
|
|
|
const { data } = res.data
|
|
const { data } = res.data
|
|
|
messages.value[messages.value.length - 1] = {
|
|
messages.value[messages.value.length - 1] = {
|
|
|
id: serial_no.value,
|
|
id: serial_no.value,
|
|
@@ -154,6 +172,9 @@ const aiChat = (question, isPresetQuestion) => {
|
|
|
} else {
|
|
} else {
|
|
|
loadingAnswer.value = false
|
|
loadingAnswer.value = false
|
|
|
messages.value[messages.value.length - 1].isError = true
|
|
messages.value[messages.value.length - 1].isError = true
|
|
|
|
|
+ messages.value[messages.value.length - 1].content = progressStatus[120]
|
|
|
|
|
+ clearInterval(progressInterval.value)
|
|
|
|
|
+ queryTime.value = -1
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
@@ -175,15 +196,16 @@ const handleSend = (question, isPresetQuestion = true, isExternal = false) => {
|
|
|
}
|
|
}
|
|
|
isPause.value = false
|
|
isPause.value = false
|
|
|
loadingAnswer.value = true
|
|
loadingAnswer.value = true
|
|
|
-
|
|
|
|
|
- aiChat(question, isPresetQuestion)
|
|
|
|
|
// 将用户内容添加到消息列表
|
|
// 将用户内容添加到消息列表
|
|
|
messages.value.push({
|
|
messages.value.push({
|
|
|
type: 'user',
|
|
type: 'user',
|
|
|
- content: question
|
|
|
|
|
|
|
+ content: question,
|
|
|
|
|
+ isAnswer: true
|
|
|
})
|
|
})
|
|
|
!isPresetQuestion ? (userQuestion.value = '') : ''
|
|
!isPresetQuestion ? (userQuestion.value = '') : ''
|
|
|
queryTime.value = 0
|
|
queryTime.value = 0
|
|
|
|
|
+
|
|
|
|
|
+ aiChat(question, isPresetQuestion)
|
|
|
messages.value.push({
|
|
messages.value.push({
|
|
|
type: 'robot',
|
|
type: 'robot',
|
|
|
content: progressStatus[0]
|
|
content: progressStatus[0]
|
|
@@ -214,6 +236,7 @@ const handleSend = (question, isPresetQuestion = true, isExternal = false) => {
|
|
|
}
|
|
}
|
|
|
}, 1000)
|
|
}, 1000)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
const showScrollButton = ref(false) // 控制按钮显示
|
|
const showScrollButton = ref(false) // 控制按钮显示
|
|
|
const messagesRef = ref()
|
|
const messagesRef = ref()
|
|
|
const autoScroll = ref(true)
|
|
const autoScroll = ref(true)
|
|
@@ -232,7 +255,8 @@ function handleScroll() {
|
|
|
|
|
|
|
|
isShowHeaderShadow.value = !!messagesRef.value?.scrollTop
|
|
isShowHeaderShadow.value = !!messagesRef.value?.scrollTop
|
|
|
}
|
|
}
|
|
|
-function scrollToBottom() {
|
|
|
|
|
|
|
+const scrollToBottom = (isScroll = false) => {
|
|
|
|
|
+ if (!isScroll && (!autoScroll.value || !messagesRef.value)) return
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
if (messagesRef.value) {
|
|
if (messagesRef.value) {
|
|
|
messagesRef.value.scrollTop = messagesRef.value.scrollHeight
|
|
messagesRef.value.scrollTop = messagesRef.value.scrollHeight
|
|
@@ -250,6 +274,7 @@ const handlePause = () => {
|
|
|
.then((res) => {
|
|
.then((res) => {
|
|
|
if (res.code === 200) {
|
|
if (res.code === 200) {
|
|
|
clearInterval(progressInterval.value)
|
|
clearInterval(progressInterval.value)
|
|
|
|
|
+ is_FixedAnswer.value = true
|
|
|
queryTime.value = -1
|
|
queryTime.value = -1
|
|
|
messages.value[messages.value.length - 1].isCancel = true
|
|
messages.value[messages.value.length - 1].isCancel = true
|
|
|
messages.value[messages.value.length - 1].content = progressStatus.cancel
|
|
messages.value[messages.value.length - 1].content = progressStatus.cancel
|
|
@@ -279,12 +304,31 @@ const handleFeedback = (index, feedback) => {
|
|
|
const emit = defineEmits(['close'])
|
|
const emit = defineEmits(['close'])
|
|
|
// 关闭聊天窗口
|
|
// 关闭聊天窗口
|
|
|
const handleClose = () => {
|
|
const handleClose = () => {
|
|
|
- progressInterval.value && clearInterval(progressInterval.value)
|
|
|
|
|
|
|
+ // progressInterval.value && clearInterval(progressInterval.value)
|
|
|
emit('close')
|
|
emit('close')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const clearData = () => {
|
|
|
|
|
+ messages.value = [
|
|
|
|
|
+ {
|
|
|
|
|
+ type: 'robot',
|
|
|
|
|
+ isShowFeedback: false,
|
|
|
|
|
+ content: 'You can click on Frequently Asked Questions above or type your own question'
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ progressInterval.value && clearInterval(progressInterval.value)
|
|
|
|
|
+ sessionStorage.removeItem('AIChat')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const liabilityExeDialog = ref(true) // 免责声明弹窗
|
|
|
|
|
+const handleLiabilityExeDialog = () => {
|
|
|
|
|
+ liabilityExeDialog.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
defineExpose({
|
|
defineExpose({
|
|
|
- handleSend
|
|
|
|
|
|
|
+ handleSend,
|
|
|
|
|
+ handleOpen,
|
|
|
|
|
+ clearData
|
|
|
})
|
|
})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
@@ -294,17 +338,23 @@ defineExpose({
|
|
|
<div class="header">
|
|
<div class="header">
|
|
|
<span class="welcome">Hi! I'm your Freight Assistant</span>
|
|
<span class="welcome">Hi! I'm your Freight Assistant</span>
|
|
|
<div class="option-icon">
|
|
<div class="option-icon">
|
|
|
- <span
|
|
|
|
|
- v-if="modalSize === 'large'"
|
|
|
|
|
- class="font_family icon-icon_sidebar__window_b"
|
|
|
|
|
- @click="modalSize = 'small'"
|
|
|
|
|
- ></span>
|
|
|
|
|
- <span
|
|
|
|
|
- v-else-if="modalSize !== 'large'"
|
|
|
|
|
- class="font_family icon-icon_maximized__window_b"
|
|
|
|
|
- @click="modalSize = 'large'"
|
|
|
|
|
- ></span>
|
|
|
|
|
- <span @click="handleClose" class="font_family icon-icon_collapsed__to_widget_b"></span>
|
|
|
|
|
|
|
+ <el-tooltip v-if="modalSize === 'large'" trigger="hover" content="Sidebar Window">
|
|
|
|
|
+ <span
|
|
|
|
|
+ class="font_family icon-icon_sidebar__window_b"
|
|
|
|
|
+ @click="modalSize = 'small'"
|
|
|
|
|
+ ></span>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tooltip v-else-if="modalSize !== 'large'" trigger="hover" content="Maximized Window"
|
|
|
|
|
+ ><span
|
|
|
|
|
+ class="font_family icon-icon_maximized__window_b"
|
|
|
|
|
+ @click="modalSize = 'large'"
|
|
|
|
|
+ ></span>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+
|
|
|
|
|
+ <el-tooltip trigger="hover" content="Collapsed to Widget"
|
|
|
|
|
+ ><span @click="handleClose" class="font_family icon-icon_collapsed__to_widget_b"></span>
|
|
|
|
|
+ </el-tooltip>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<AIQuestions :modalSize="modalSize" @question="handleSend"></AIQuestions>
|
|
<AIQuestions :modalSize="modalSize" @question="handleSend"></AIQuestions>
|
|
@@ -347,10 +397,15 @@ defineExpose({
|
|
|
<div v-html="msg.html || renderedMessage(msg.content)" class="markdown-body"></div>
|
|
<div v-html="msg.html || renderedMessage(msg.content)" class="markdown-body"></div>
|
|
|
</div>
|
|
</div>
|
|
|
<LoadingDots
|
|
<LoadingDots
|
|
|
- v-if="index === messages.length - 1 && msg.isAnswer && loadingAnswer"
|
|
|
|
|
|
|
+ v-if="
|
|
|
|
|
+ index === messages.length - 1 &&
|
|
|
|
|
+ msg.isAnswer &&
|
|
|
|
|
+ isShowLoadingDots &&
|
|
|
|
|
+ msg.type === 'robot'
|
|
|
|
|
+ "
|
|
|
></LoadingDots>
|
|
></LoadingDots>
|
|
|
<!-- 评价 -->
|
|
<!-- 评价 -->
|
|
|
- <div class="review" v-if="msg.isShowFeedback && msg.isAnswer">
|
|
|
|
|
|
|
+ <div class="review" v-if="msg.isShowFeedback && msg.isAnswer && msg.type === 'robot'">
|
|
|
<el-button
|
|
<el-button
|
|
|
v-if="msg.feedback !== 'Cood'"
|
|
v-if="msg.feedback !== 'Cood'"
|
|
|
class="el-button--text"
|
|
class="el-button--text"
|
|
@@ -382,16 +437,18 @@ defineExpose({
|
|
|
<img class="robot-bubble-img" v-if="msg.type === 'robot'" :src="robotBubbleImg" alt="" />
|
|
<img class="robot-bubble-img" v-if="msg.type === 'robot'" :src="robotBubbleImg" alt="" />
|
|
|
<img class="user-bubble-img" v-else-if="msg.type === 'user'" :src="userBubbleImg" alt="" />
|
|
<img class="user-bubble-img" v-else-if="msg.type === 'user'" :src="userBubbleImg" alt="" />
|
|
|
<!-- 暂停回答 icon -->
|
|
<!-- 暂停回答 icon -->
|
|
|
- <div
|
|
|
|
|
- class="pause-btn"
|
|
|
|
|
|
|
+ <el-tooltip
|
|
|
v-if="index === messages.length - 1 && queryTime > 29 && queryTime < 120"
|
|
v-if="index === messages.length - 1 && queryTime > 29 && queryTime < 120"
|
|
|
- @click="handlePause"
|
|
|
|
|
- >
|
|
|
|
|
- <div class="dot"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ content="Cancel Answer"
|
|
|
|
|
+ placement="bottom-start"
|
|
|
|
|
+ effect="dark"
|
|
|
|
|
+ ><div class="pause-btn" @click="handlePause">
|
|
|
|
|
+ <div class="dot"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-tooltip>
|
|
|
</div>
|
|
</div>
|
|
|
<!-- 滚动到底部icon -->
|
|
<!-- 滚动到底部icon -->
|
|
|
- <div v-if="showScrollButton" class="scroll-to-bottom-btn" @click="scrollToBottom">
|
|
|
|
|
|
|
+ <div v-if="showScrollButton" class="scroll-to-bottom-btn" @click="scrollToBottom(true)">
|
|
|
<span class="font_family icon-icon_movedown_b"></span>
|
|
<span class="font_family icon-icon_movedown_b"></span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -416,17 +473,41 @@ defineExpose({
|
|
|
<span class="font_family icon-icon_send_b"></span>
|
|
<span class="font_family icon-icon_send_b"></span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="liability-exemption">
|
|
|
|
|
+ <span> Content is generated by Al, please check carefully!</span>
|
|
|
|
|
+ <span class="liability-exemption-btn" @click="handleLiabilityExeDialog">Disclaimer</span>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ class="liability-exemption-dialog"
|
|
|
|
|
+ v-model="liabilityExeDialog"
|
|
|
|
|
+ title="Disclaimer"
|
|
|
|
|
+ width="800"
|
|
|
|
|
+ >
|
|
|
|
|
+ <p style="line-height: 21px">
|
|
|
|
|
+ This feature generates automated answers summarised from FT articles. By using it, you are
|
|
|
|
|
+ interacting with an Al system (provided byAnthropic), not a human. Al can make mistakes.
|
|
|
|
|
+ Please refer to the source articles for verification and cifation purposes.
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <p style="line-height: 21px">
|
|
|
|
|
+ Our staff do not check or moderate Ask FT is answers in advance, but may review some later.
|
|
|
|
|
+ Our journalists continue to write and edit all thearticles Ask FT uses as sources. Learn
|
|
|
|
|
+ more about our editorial principles for generative Al To helo us understand how you are
|
|
|
|
|
+ usina this feafure and how we can improve it, we use your data. This includes your query and
|
|
|
|
|
+ any personadata you have shared with us, like your job title. For more information about how
|
|
|
|
|
+ we use your data, please read our Privacy Policy.
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
|
.ai-robot {
|
|
.ai-robot {
|
|
|
position: absolute;
|
|
position: absolute;
|
|
|
- top: 74px;
|
|
|
|
|
|
|
+ top: 24px;
|
|
|
right: 24px;
|
|
right: 24px;
|
|
|
- height: calc(100% - 98px);
|
|
|
|
|
- z-index: 4000;
|
|
|
|
|
|
|
+ height: calc(100% - 48px);
|
|
|
|
|
+ z-index: 2000;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
border-radius: 12px;
|
|
border-radius: 12px;
|
|
@@ -585,7 +666,7 @@ defineExpose({
|
|
|
height: 16px;
|
|
height: 16px;
|
|
|
width: 16px;
|
|
width: 16px;
|
|
|
border-radius: 50%;
|
|
border-radius: 50%;
|
|
|
- background-color: var(--color-customize-column-right-section-bg);
|
|
|
|
|
|
|
+ background-color: var(--color-pause-btn-bg);
|
|
|
.dot {
|
|
.dot {
|
|
|
height: 5px;
|
|
height: 5px;
|
|
|
width: 5px;
|
|
width: 5px;
|
|
@@ -594,11 +675,7 @@ defineExpose({
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- .query-style {
|
|
|
|
|
- span {
|
|
|
|
|
- color: #b5b9bf;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
.robot-bubble {
|
|
.robot-bubble {
|
|
|
background: var(--scoring-bg-color);
|
|
background: var(--scoring-bg-color);
|
|
|
align-self: flex-start;
|
|
align-self: flex-start;
|
|
@@ -625,7 +702,7 @@ defineExpose({
|
|
|
position: absolute;
|
|
position: absolute;
|
|
|
right: 50%;
|
|
right: 50%;
|
|
|
transform: translateX(50%);
|
|
transform: translateX(50%);
|
|
|
- bottom: 58px;
|
|
|
|
|
|
|
+ bottom: 76px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
@@ -635,7 +712,7 @@ defineExpose({
|
|
|
// background-color: #f5f4f4;
|
|
// background-color: #f5f4f4;
|
|
|
background: rgba(255, 255, 255, 0.6); /* 半透明背景色 */
|
|
background: rgba(255, 255, 255, 0.6); /* 半透明背景色 */
|
|
|
box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.3);
|
|
box-shadow: 2px 2px 12px 0px rgba(0, 0, 0, 0.3);
|
|
|
- backdrop-filter: blur(1px); /* 应用10px的模糊效果 */
|
|
|
|
|
|
|
+ backdrop-filter: blur(1.7px); /* 应用10px的模糊效果 */
|
|
|
span {
|
|
span {
|
|
|
color: #2b2f36;
|
|
color: #2b2f36;
|
|
|
}
|
|
}
|
|
@@ -692,6 +769,18 @@ defineExpose({
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ .liability-exemption {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: var(--color-neutral-3);
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ .liability-exemption-btn {
|
|
|
|
|
+ margin-left: 2px;
|
|
|
|
|
+ text-decoration: underline;
|
|
|
|
|
+ color: var(--color-theme);
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
@keyframes loading-rotate {
|
|
@keyframes loading-rotate {
|
|
|
0% {
|
|
0% {
|
|
@@ -703,4 +792,10 @@ defineExpose({
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+:deep(.liability-exemption-dialog) {
|
|
|
|
|
+ padding-bottom: 0;
|
|
|
|
|
+ .el-dialog__body {
|
|
|
|
|
+ padding-top: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|