This commit is contained in:
李志鹏
2025-11-18 11:41:24 +08:00
8 changed files with 233 additions and 34 deletions

View File

@@ -25,7 +25,7 @@ const setLike = (item,str)=>{
}
const setSelectList = (item)=>{
emit('selectItem', item)
if(item.status == 'SUCCEEDED')emit('selectItem', item)
}
const deleteStyle = (index)=>{
@@ -57,6 +57,9 @@ const {} = toRefs(data);
<div class="mask running" v-if="item.status == 'RUNNING'">
<van-loading type="spinner" size="20rem"/>
</div>
<div class="mask failed" v-if="item.status == 'FAILED'">
Generation failed. Please click refresh to try again.
</div>
<div class="mask" v-if="item.id == select?.oldId"></div>
</div>
<div class="btn">
@@ -116,6 +119,13 @@ const {} = toRefs(data);
background-color: rgba(0, 0, 0, 0.5);
top: 0;
left: 0;
&.failed{
color: #fff;
font-size: 2rem;
display: flex;
align-items: center;
justify-content: center;
}
.van-loading {
display: flex;
justify-content: center;

View File

@@ -83,19 +83,21 @@ export const useGenerateStore = defineStore({
actions: {
selectStyle(data: any) {
this.style.id = data.id
console.log(this)
},
//生成后去掉id 设置oldId来修改样式
useStyleGenerate() {
if (!this.style.id) return
this.style.oldId = this.style.id
this.style.id = ''
// this.style.id = ''
},
updateStyle(data) {
console.log(data)
if (data.id == this.style.oldId) {
this.style.oldId = ''
}
if(data.id == this.style.id) {
this.style.id = ''
}
console.log(this.style)
},
//模特相关
selectModel(data: any) {

View File

@@ -24,7 +24,7 @@ let data = reactive({
let getGenerateTime = null as any
const selectItem = (item)=>{
if((item.id == data.select?.oldId) || !item.id || item.status != 'SUCCEEDED'){
if(!item.id || item.status != 'SUCCEEDED'){
return
}
generateStore.selectStyle(item)
@@ -37,11 +37,11 @@ const updateStyle = ({item,index})=>{
}
const toProduct = ()=>{
if(!generateStore.style.id && !generateStore.style.oldId){
if(!generateStore.style.id){
showToast({ message: 'Please select a style.' });
return
}
if(generateStore.style.id){
if(generateStore.style.id != generateStore.style.oldId){
generateStore.setIsGenerate(true)
}
router.push({ path: 'product' })
@@ -94,7 +94,6 @@ onMounted(()=>{
emit('view-type', 1)
// if(!data.styleList[0]?.id)getRequestOutfitList(0)
if(getGenerateTime)clearTimeout(getGenerateTime)
console.log(data.styleList)
if(!data.styleList[0]?.taskId){
requestOutfit({num:4,index:0})
}else if(data.styleList.filter((item)=>item?.status != 'SUCCEEDED').length > 0){

View File

@@ -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[] = []

View File

@@ -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高度

View File

@@ -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(() => {

View File

@@ -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()

View File

@@ -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;
}
}
}
}