feat: dressfor页面作为聊天起始
This commit is contained in:
@@ -6,8 +6,8 @@
|
||||
v-for="(line, index) in lines"
|
||||
:key="index"
|
||||
:class="['line', `line-${line.type}`]"
|
||||
></div> </template
|
||||
>>
|
||||
></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -44,7 +44,8 @@ const calculateLines = (): Line[] => {
|
||||
// 这样当滚动到50%时,内容看起来和开始一样
|
||||
const availableWidth = containerWidth
|
||||
const lineWithGap = lineWidthPx + gapPx
|
||||
const linesNeeded = Math.ceil(availableWidth / lineWithGap)
|
||||
// 使用四舍五入让一个周期的宽度尽量接近容器宽度,减少滚动结束时的“回跳”感
|
||||
const linesNeeded = Math.max(1, Math.round(availableWidth / lineWithGap))
|
||||
|
||||
const generatedLines: Line[] = []
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ const shortcutList: string[] = [
|
||||
|
||||
const handleSend = (): void => {
|
||||
if (inputValue.value.trim()) {
|
||||
console.log('input发送消息:', inputValue.value)
|
||||
// console.log('input发送消息:', inputValue.value)
|
||||
emit('send-message', inputValue.value)
|
||||
inputValue.value = ''
|
||||
// 重置textarea高度
|
||||
|
||||
@@ -30,13 +30,14 @@ import NoticeList from './components/NoticeList.vue'
|
||||
import InputArea from './components/InputArea.vue'
|
||||
import GenerateLoading from './components/GenerateLoading.vue'
|
||||
import { ref, onMounted, onUnmounted, onActivated } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useUserInfoStore, useGenerateStore } from '@/stores'
|
||||
import { streamChatAddress } from '@/api/workshop'
|
||||
import { generateUUID } from '@/utils/tools'
|
||||
import { showToast } from 'vant'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const generateStore = useGenerateStore()
|
||||
const userInfoStore = useUserInfoStore()
|
||||
|
||||
@@ -68,9 +69,21 @@ const isStreaming = ref<boolean>(false)
|
||||
const currentStreamingMessage = ref<ChatMessage | null>(null)
|
||||
const sessionId = ref<string>('')
|
||||
|
||||
const sendPrefilledMessage = () => {
|
||||
const { message, ...restQuery } = route.query
|
||||
if (typeof message === 'string' && message.trim()) {
|
||||
handleSendMessage(message)
|
||||
router.replace({
|
||||
path: route.path,
|
||||
query: restQuery
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
sessionId.value = Math.floor(Date.now() / 1000).toString()
|
||||
generateStore.setSessionId(sessionId.value)
|
||||
sendPrefilledMessage()
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
|
||||
<div class="glass-form">
|
||||
<div class="form-field">
|
||||
<label class="field-label">Customer Name</label>
|
||||
<input v-model="customerData.name" type="text" placeholder="Name" class="form-input" />
|
||||
<label class="field-label">VIP ID</label>
|
||||
<input v-model="customerData.name" type="text" placeholder="Enter your ID" class="form-input" />
|
||||
</div>
|
||||
<div class="form-field email">
|
||||
<label class="field-label">Customer Email</label>
|
||||
<label class="field-label">Email Address</label>
|
||||
<input
|
||||
v-model="customerData.email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
placeholder="Enter your email"
|
||||
class="form-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -78,7 +78,6 @@ const handleConfirm = async () => {
|
||||
})
|
||||
return
|
||||
}
|
||||
console.log('customerData.value', customerData.value)
|
||||
|
||||
customerCheckin(customerData.value).then((res) => {
|
||||
useUserInfoStore().resetGenerateParams()
|
||||
|
||||
@@ -7,25 +7,164 @@
|
||||
<SvgIcon name="setting" size="70" />
|
||||
</div> -->
|
||||
<div class="text">What are you dressing for?</div>
|
||||
<div class="start-btn" @click="handleStart">Start</div>
|
||||
<!-- <div class="start-btn" @click="handleStart">Start</div> -->
|
||||
<div class="chatbox flex flex-center">
|
||||
<div class="input-box flex">
|
||||
<div class="input-wrapper flex-1 flex">
|
||||
<input
|
||||
type="text"
|
||||
class="input-item flex-1"
|
||||
v-model="inputValue"
|
||||
placeholder="Ask sth!"
|
||||
v-show="!isRecording"
|
||||
/>
|
||||
<div class="recording-visualizer flex-1" v-show="isRecording">
|
||||
<AudioVisualizer ref="audioVisualizerRef" />
|
||||
</div>
|
||||
</div>
|
||||
<SvgIcon
|
||||
class="audio-icon"
|
||||
:name="isRecording ? 'pause' : 'audio'"
|
||||
size="52"
|
||||
@click="handleClickAudio"
|
||||
/>
|
||||
</div>
|
||||
<div class="send flex flex-center" @click="handleSendMessage">
|
||||
<SvgIcon class="send-icon" name="send" size="26" color="#000000" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer-navigation />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onUnmounted, nextTick, watch } from 'vue'
|
||||
import { showToast } from 'vant'
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AudioVisualizer from '@/views/asistant/components/AudioVisualizer.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleBack = () => {
|
||||
router.go(-1)
|
||||
const inputValue = ref('')
|
||||
const isRecording = ref(false)
|
||||
const audioVisualizerRef = ref<InstanceType<typeof AudioVisualizer> | null>(null)
|
||||
let speechRecognition: any = null
|
||||
let lastTranscript = ''
|
||||
let isSpeechRecognitionActive = false
|
||||
|
||||
const refreshAudioVisualizer = () => {
|
||||
audioVisualizerRef.value?.updateLines?.()
|
||||
}
|
||||
|
||||
const handleStart = () => {
|
||||
console.log('click start')
|
||||
router.push('/asistant')
|
||||
watch(isRecording, async (newVal) => {
|
||||
if (newVal) {
|
||||
await nextTick()
|
||||
refreshAudioVisualizer()
|
||||
setTimeout(() => {
|
||||
refreshAudioVisualizer()
|
||||
}, 50)
|
||||
}
|
||||
})
|
||||
|
||||
const handleSendMessage = () => {
|
||||
const message = inputValue.value.trim()
|
||||
if(!message){
|
||||
showToast('Please enter a message')
|
||||
return
|
||||
}
|
||||
router.push({
|
||||
path: '/asistant',
|
||||
query: message ? { message } : undefined
|
||||
})
|
||||
}
|
||||
|
||||
const handleClickAudio = () => {
|
||||
if (isRecording.value) {
|
||||
stopRecording()
|
||||
} else {
|
||||
startRecording()
|
||||
}
|
||||
}
|
||||
|
||||
const startRecording = () => {
|
||||
if (isSpeechRecognitionActive) {
|
||||
console.warn('Speech recognition already running')
|
||||
return
|
||||
}
|
||||
if (!speechRecognition) {
|
||||
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
|
||||
showToast(
|
||||
'Your browser does not support speech recognition, please try again with another browser'
|
||||
)
|
||||
return
|
||||
}
|
||||
const SpeechRecognition =
|
||||
(window as any).SpeechRecognition || (window as any).webkitSpeechRecognition
|
||||
speechRecognition = new SpeechRecognition()
|
||||
speechRecognition.continuous = true
|
||||
speechRecognition.interimResults = true
|
||||
speechRecognition.lang = 'en-US'
|
||||
}
|
||||
|
||||
speechRecognition.onstart = () => {
|
||||
isRecording.value = true
|
||||
}
|
||||
|
||||
speechRecognition.onresult = (event: any) => {
|
||||
let finalTranscript = ''
|
||||
let interimTranscript = ''
|
||||
|
||||
for (let i = event.resultIndex; i < event.results.length; i++) {
|
||||
const transcript = event.results[i][0].transcript
|
||||
if (event.results[i].isFinal) {
|
||||
finalTranscript += transcript
|
||||
} else {
|
||||
interimTranscript += transcript
|
||||
}
|
||||
}
|
||||
|
||||
if (finalTranscript && finalTranscript !== lastTranscript) {
|
||||
lastTranscript = finalTranscript
|
||||
inputValue.value = finalTranscript
|
||||
}
|
||||
|
||||
if (interimTranscript) {
|
||||
console.log('Speech recognition interim result:', interimTranscript)
|
||||
}
|
||||
}
|
||||
|
||||
speechRecognition.onend = () => {
|
||||
isRecording.value = false
|
||||
lastTranscript = ''
|
||||
isSpeechRecognitionActive = false
|
||||
}
|
||||
|
||||
speechRecognition.onerror = (event: any) => {
|
||||
console.error('Speech recognition error:', event.error)
|
||||
isRecording.value = false
|
||||
isSpeechRecognitionActive = false
|
||||
showToast('Speech recognition failed, please try again')
|
||||
}
|
||||
|
||||
speechRecognition.start()
|
||||
isSpeechRecognitionActive = true
|
||||
}
|
||||
|
||||
const stopRecording = () => {
|
||||
if (speechRecognition && isSpeechRecognitionActive) {
|
||||
speechRecognition.stop()
|
||||
isSpeechRecognitionActive = false
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (speechRecognition && isRecording.value) {
|
||||
speechRecognition.stop()
|
||||
}
|
||||
speechRecognition = null
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.dressfor-container {
|
||||
@@ -54,16 +193,52 @@ const handleStart = () => {
|
||||
margin-top: 43.8rem;
|
||||
margin-bottom: 14rem;
|
||||
}
|
||||
.start-btn {
|
||||
font-size: 5.6rem;
|
||||
width: 32.5rem;
|
||||
height: 8.1rem;
|
||||
border: .2rem solid #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4rem;
|
||||
margin: 0 auto;
|
||||
.chatbox {
|
||||
height: 9.3rem;
|
||||
// background-color: #fff;
|
||||
column-gap: 2.29rem;
|
||||
.input-box {
|
||||
width: 59.8rem;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border: 2px solid #5f5f5f;
|
||||
border-radius: 1rem;
|
||||
color: #222222;
|
||||
font-size: 3.2rem;
|
||||
font-family: 'satoshiRegular';
|
||||
padding: 0 2.6rem;
|
||||
column-gap: 2.6rem;
|
||||
overflow: hidden;
|
||||
.input-wrapper{
|
||||
overflow: hidden;
|
||||
}
|
||||
.recording-visualizer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
:deep(.audio-visualizer) {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
:deep(.visualizer-container) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.input-item {
|
||||
// width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
.audio-icon {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
.send {
|
||||
width: 7.6rem;
|
||||
height: 7.6rem;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user