Browse Source

agent文件替换

dev_des
1054425342@qq.com 1 month ago
parent
commit
9514e33cca
  1. 2
      components/GPT/index.vue
  2. 12
      components/GPT/utils/audio.js
  3. 103
      components/GPT/utils/pcm-player.js
  4. 6
      pages.json
  5. 552
      subPackages/user/couponList.vue

2
components/GPT/index.vue

@ -61,7 +61,7 @@
<view class="chatinput-content">
<view class='chatinput-input-wrap' v-if="videoStatus">
<view style="text-align: center; flex: 1; color:#9FA0A0" @longpress="startVideo" @touchend="stopVideo">
点击 说话
按住 说话
</view>
</view>
<view v-else class='chatinput-input-wrap'>

12
components/GPT/utils/audio.js

@ -81,8 +81,8 @@ export default class Audio {
}
}
// this.on(type, e);
this.createInter();
resolve();
// this.createInter();
});
// 失败
socket.onError((e) => {
@ -199,6 +199,16 @@ export default class Audio {
}
}
createInter() {
if (this.timeoutObj) {
clearTimeout(this.timeoutObj);
}
this.timeoutObj = setTimeout(() => {
console.log(111111, this.socket)
this.socket && this.socket.send && this.socket.send({ data: 3 });
}, HEART_BEAT_TIME);
}
// 关闭socket
destroy() {
if (this.socket && this.socket.readyState == 1) {

103
components/GPT/utils/pcm-player.js

@ -1,47 +1,76 @@
// utils/pcm-player.js
export default class PCMPlayer {
export default class PcmPlayer {
constructor(options) {
this.queue = [];
this._options = options
this.AllTexts = [];
this._options = options;
this.audioCtx = null;
this.rate = this._options.rate ?? 16000;
this.ch = this._options.ch ?? 1;
this.samplesPerFrame = (16000 * 20) / 1000;
this.queue = [];
this.isPlaying = false;
this.startTime = 0;
this._t = null
this._t = null;
this.AllTexts = [];
}
feed(data) {
if (!this.audioCtx) {
this.audioCtx = wx.createWebAudioContext();
}
this.playPCM(data);
this.queue.push(new Int16Array(data));
if (!this.isPlaying) this._play();
}
playPCM(pcmBuffer, sampleRate = 16000) {
const length = pcmBuffer.byteLength / 2; // 16-bit = 2 bytes per sample
const audioBuffer = this.audioCtx.createBuffer(1, length, sampleRate);
const channelData = audioBuffer.getChannelData(0); // Float32Array
const view = new DataView(pcmBuffer);
// 将 16-bit PCM 转换为 Float32 [-1, 1]
for (let i = 0; i < length; i++) {
const sample = view.getInt16(i * 2, true); // little-endian
channelData[i] = sample / 0x8000; // convert to [-1, 1]
async _play() {
this.isPlaying = true;
if (!this.audioCtx) {
return
}
this.queue.push(audioBuffer);
if (!this.isPlaying) {
// 开始播放
this.startTime = this.audioCtx.currentTime ?? 0;
this.playNext();
this.isPlaying = true;
setTimeout(() => {
this.syncLoop();
});
while (this.queue.length) {
const pcm = this.queue.shift();
const len = pcm.length / this.ch;
const buf = this.audioCtx.createBuffer(this.ch, len, this.rate);
// 1. 归一化(防溢出)
for (let c = 0; c < this.ch; c++) {
const data = buf.getChannelData(c);
for (let i = 0; i < len; i++) {
const val = pcm[i * this.ch + c];
data[i] = val > 0 ? val / 0x7fff : val / 0x8000;
}
}
// 2. 帧边界淡入淡出(防不连续滋滋)
const fadeSamples = Math.min(80, len); // 5ms@16kHz
const data = buf.getChannelData(0);
for (let j = 0; j < fadeSamples; j++) {
const gain = j / fadeSamples;
data[j] *= gain; // fade-in
data[len - 1 - j] *= gain; // fade-out
}
// 3. 播放并等待结束
await new Promise((resolve) => {
const src = this.audioCtx.createBufferSource();
src.buffer = buf;
src.connect(this.audioCtx.destination);
src.onended = resolve;
src.start();
});
}
this.isPlaying = false;
}
// 监听音频播放进度
syncLoop() {
const loop = () => {
if (!this.audioCtx) {
return
}
const now = this.audioCtx.currentTime - this.startTime;
// 找当前字幕
const index = this.AllTexts.findIndex(
@ -51,13 +80,13 @@ export default class PCMPlayer {
const obj = this.AllTexts[index];
if (this.AllTexts.length == 1) {
// 最后一条数据
obj.is_final = true
obj.is_final = true;
}
const tmp = this.AllTexts.shift();
this._options.onPlay && this._options.onPlay(tmp);
if (!this.AllTexts.length) {
clearTimeout(this._t);
return
return;
}
}
if (!this.AllTexts.length) {
@ -68,32 +97,14 @@ export default class PCMPlayer {
} else {
// 结束
clearTimeout(this._t);
console.log('语音播放结束')
this.destroy()
console.log("语音播放结束");
this.destroy();
}
}
};
loop();
}
playNext() {
if (this.queue.length === 0) {
// 结束
this.isPlaying = true;
return;
}
this.isPlaying = true;
const source = this.audioCtx.createBufferSource();
source.buffer = this.queue.shift();
source.connect(this.audioCtx.destination);
source.start();
source.onended = () => {
setTimeout(() => {
this.playNext();
});
};
}
// 增加文字
addText(res) {
if (res.result && res.result.subtitles && res.result.subtitles.length) {
@ -104,7 +115,7 @@ export default class PCMPlayer {
beginTime: it.beginTime / 1000,
endTime: it.endTime / 1000,
request_id: res.requestId,
sessionId: res.sessionId
sessionId: res.sessionId,
};
});
this.AllTexts = [...this.AllTexts, ...tmpText];
@ -122,6 +133,6 @@ export default class PCMPlayer {
this.audioCtx = null;
this.AllTexts = [];
this.startTime = 0;
this._options.onAudioEnd && this._options.onAudioEnd()
this._options.onAudioEnd && this._options.onAudioEnd();
}
}

6
pages.json

@ -315,6 +315,12 @@
"navigationBarTextStyle": "white",
"navigationStyle": "custom"
}
},
{
"path": "user/couponList",
"style": {
"navigationBarTitleText": "优惠券"
}
}
]
},

552
subPackages/user/couponList.vue

@ -0,0 +1,552 @@
<template>
<view class="coupon-list-container">
<!-- 状态筛选 -->
<view class="filter-tabs">
<view
class="tab-item"
:class="[{ active: activeTab === 'all' }]"
@click="switchTab('all')"
>
全部
</view>
<view
class="tab-item"
:class="[{ active: activeTab === 'unused' }]"
@click="switchTab('unused')"
>
未使用
</view>
<view
class="tab-item"
:class="[{ active: activeTab === 'used' }]"
@click="switchTab('used')"
>
已使用
</view>
<view
class="tab-item"
:class="[{ active: activeTab === 'expired' }]"
@click="switchTab('expired')"
>
已过期
</view>
</view>
<!-- 优惠券列表 -->
<view class="coupon-list">
<view
v-for="(coupon, index) in filteredCoupons"
:key="index"
class="coupon-item"
:class="[
{
used: coupon.status === 1,
expired: coupon.status === 2,
},
]"
>
<!-- 优惠券主体 -->
<view class="coupon-main">
<!-- 左侧金额区域 -->
<view class="coupon-amount">
<view class="amount-value">
<text class="currency">¥</text>
<text class="value">{{ formatAmount(coupon) }}</text>
</view>
<view class="amount-desc" v-if="coupon.minAmount > 0">
{{ coupon.minAmount }}可用
</view>
<view class="amount-desc" v-else> 无门槛 </view>
</view>
<!-- 右侧信息区域 -->
<view class="coupon-info">
<view class="coupon-type">{{
getCouponTypeName(coupon.type)
}}</view>
<view class="coupon-scope" v-if="coupon.scope === 0">
全场通用
</view>
<view class="coupon-scope" v-else>
<view class="scope-title" @click="toggleProductList(coupon.id)">
指定商品可用
<text
class="expand-icon"
:class="{ expanded: coupon.showProducts }"
></text
>
</view>
<view class="product-list" v-if="coupon.showProducts">
<view
class="product-item"
v-for="product in coupon.products"
:key="product.id"
>
{{ product.name }}
</view>
</view>
</view>
</view>
<!-- 状态标识 -->
<view class="coupon-status">
<view class="status-tag" :class="[getStatusClass(coupon.status)]">
{{ getStatusText(coupon.status) }}
</view>
</view>
</view>
<!-- 有效期和使用按钮 -->
<view class="coupon-bottom">
<view class="coupon-validity">
<text class="validity-label">有效期</text>
<text class="validity-date"
>{{ coupon.startDate }} - {{ coupon.endDate }}</text
>
</view>
<view class="coupon-action" v-if="coupon.status === 0">
<button class="use-btn" @click="useCoupon(coupon)">立即使用</button>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="filteredCoupons.length === 0">
<view class="empty-icon">📋</view>
<view class="empty-text">暂无优惠券</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
activeTab: "all",
coupons: [
{
id: 1,
type: 1, //
amount: 20,
minAmount: 100,
status: 0, // 0:使 1:使 2:
startDate: "2024-01-01",
endDate: "2024-12-31",
scope: 0, // 0: 1:
showProducts: false,
},
{
id: 2,
type: 2, //
discount: 8.5, // 8.5
minAmount: 50,
status: 1,
startDate: "2024-01-01",
endDate: "2024-06-30",
scope: 1,
showProducts: false,
products: [
{ id: 1, name: "iPhone 15 Pro" },
{ id: 2, name: "MacBook Air M2" },
{ id: 3, name: "AirPods Pro" },
],
},
{
id: 3,
type: 3, //
amount: 10,
minAmount: 0,
status: 2,
startDate: "2023-12-01",
endDate: "2023-12-31",
scope: 0,
showProducts: false,
},
{
id: 4,
type: 4, //
amount: 0,
minAmount: 0,
status: 0,
startDate: "2024-01-01",
endDate: "2024-12-31",
scope: 1,
showProducts: false,
products: [
{ id: 4, name: "星巴克咖啡券" },
{ id: 5, name: "肯德基套餐券" },
],
},
],
};
},
computed: {
filteredCoupons() {
if (this.activeTab === "all") {
return this.coupons;
}
if (this.activeTab === "unused") {
return this.coupons.filter((coupon) => coupon.status === 0);
}
if (this.activeTab === "used") {
return this.coupons.filter((coupon) => coupon.status === 1);
}
if (this.activeTab === "expired") {
return this.coupons.filter((coupon) => coupon.status === 2);
}
return [];
},
},
mounted() {
console.log("couponList mounted");
console.log("activeTab:", this.activeTab);
console.log("coupons:", this.coupons);
console.log("filteredCoupons:", this.filteredCoupons);
},
methods: {
switchTab(tab) {
this.activeTab = tab;
},
getCouponTypeName(type) {
const typeMap = {
1: "满减券",
2: "折扣券",
3: "立减券",
4: "兑换券",
};
return typeMap[type] || "未知类型";
},
formatAmount(coupon) {
if (coupon.type === 2) {
//
return coupon.discount + "折";
} else if (coupon.type === 4) {
//
return "兑换";
} else {
//
return coupon.amount;
}
},
getStatusText(status) {
const statusMap = {
0: "未使用",
1: "已使用",
2: "已过期",
};
return statusMap[status] || "未知状态";
},
getStatusClass(status) {
const classMap = {
0: "unused",
1: "used",
2: "expired",
};
return classMap[status] || "";
},
useCoupon(coupon) {
//
uni.showToast({
title: "跳转到商品页面",
icon: "none",
});
},
toggleProductList(couponId) {
console.log("toggleProductList called with couponId:", couponId);
const couponIndex = this.coupons.findIndex((c) => c.id === couponId);
console.log("found coupon index:", couponIndex);
if (couponIndex !== -1) {
const newShowProducts = !this.coupons[couponIndex].showProducts;
console.log(
"toggling showProducts from",
this.coupons[couponIndex].showProducts,
"to",
newShowProducts
);
// 使 Vue.set
this.$set(this.coupons[couponIndex], "showProducts", newShowProducts);
console.log(
"after toggle, showProducts is:",
this.coupons[couponIndex].showProducts
);
}
},
},
onLoad() {
//
// this.loadCoupons();
},
};
</script>
<style lang="scss" scoped>
.coupon-list-container {
min-height: 100vh;
background-color: #f5f5f5;
}
.header {
background: #fff;
padding: 20rpx 30rpx;
border-bottom: 1rpx solid #eee;
.header-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
text-align: center;
}
}
.filter-tabs {
display: flex;
background: #fff;
padding: 0 30rpx;
border-bottom: 1rpx solid #eee;
.tab-item {
flex: 1;
padding: 30rpx 0;
text-align: center;
font-size: 28rpx;
color: #666;
position: relative;
&.active {
color: #77f3f9;
font-weight: 600;
&::after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: #77f3f9;
border-radius: 2rpx;
}
}
}
}
.coupon-list {
padding: 20rpx 30rpx;
}
.coupon-item {
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
position: relative;
&.used,
&.expired {
opacity: 0.6;
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.1);
pointer-events: none;
}
}
}
.coupon-main {
display: flex;
padding: 30rpx;
position: relative;
&::after {
content: "";
position: absolute;
right: 200rpx;
top: 20rpx;
bottom: 20rpx;
width: 2rpx;
background: linear-gradient(
to bottom,
transparent 0%,
#ddd 20%,
#ddd 80%,
transparent 100%
);
}
}
.coupon-amount {
width: 200rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.amount-value {
display: flex;
align-items: baseline;
margin-bottom: 10rpx;
.currency {
font-size: 24rpx;
color: #ff4d4f;
font-weight: 600;
}
.value {
font-size: 48rpx;
color: #ff4d4f;
font-weight: 700;
margin-left: 4rpx;
}
}
.amount-desc {
font-size: 22rpx;
color: #999;
}
}
.coupon-info {
flex: 1;
padding-left: 30rpx;
display: flex;
flex-direction: column;
justify-content: center;
.coupon-type {
font-size: 32rpx;
color: #333;
font-weight: 600;
margin-bottom: 12rpx;
}
.coupon-scope {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.coupon-scope {
.scope-title {
display: flex;
align-items: center;
cursor: pointer;
.expand-icon {
margin-left: 8rpx;
font-size: 20rpx;
transition: transform 0.3s ease;
&.expanded {
transform: rotate(180deg);
}
}
}
.product-list {
margin-top: 8rpx;
padding: 8rpx 12rpx;
background: #f8f8f8;
border-radius: 8rpx;
.product-item {
font-size: 20rpx;
color: #666;
line-height: 1.5;
margin-bottom: 4rpx;
&:last-child {
margin-bottom: 0;
}
}
}
}
}
.coupon-status {
position: absolute;
top: 20rpx;
right: 20rpx;
.status-tag {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 20rpx;
color: #fff;
&.unused {
background: #52c41a;
}
&.used {
background: #999;
}
&.expired {
background: #ff4d4f;
}
}
}
.coupon-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
border-top: 1rpx solid #f0f0f0;
.coupon-validity {
flex: 1;
font-size: 22rpx;
color: #999;
.validity-label {
margin-right: 8rpx;
}
.validity-date {
line-height: 1.4;
}
}
.coupon-action {
.use-btn {
width: 160rpx;
height: 60rpx;
background: linear-gradient(135deg, #fffdb7 0%, #97fffa 100%);
color: #000;
border: none;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 600;
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
opacity: 0.3;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
</style>
Loading…
Cancel
Save