<template>
	<view class="content">
		<view class="header">
			<text class="title">VOSK 离线语音识别</text>
		</view>
		
		<!-- 服务器配置区域 -->
		<view class="config-area">
			<view class="config-title">
				<text>模型服务器配置</text>
			</view>
			<view class="config-item">
				<text class="config-label">服务器地址：</text>
				<input 
					class="config-input" 
					v-model="serverUrl" 
					placeholder="例如：http://192.168.1.100:8080/vosk-model-small-cn-0.22.zip"
					@blur="saveServerConfig"
				/>
			</view>
			<view class="config-tips">
				<text class="tips-text">💡 提示：配置服务器地址后，"开始说话"功能会优先从服务器下载模型</text>
				<text class="tips-text">📋 步骤：1. 双击运行"启动服务器.bat" 2. 复制显示的局域网地址 3. 粘贴到上方输入框</text>
			</view>
		</view>
		
		<view class="button-area">
			<button type="primary" size="large" @click="goToSpeech" class="go-speech-btn">开始说话</button>
			<view class="divider"></view>
			<button type="primary" @click="downModel" :disabled="downloading">在线下载模型</button>
			<button type="primary" @click="init" :disabled="initializing">初始化模型</button>
			<button type="primary" @click="unzipInnit" :disabled="!modelPath">使用已解压模型</button>
			<button type="success" @click="start" :disabled="!isModelReady || isRecording">开始识别</button>
			<button type="warn" @click="stop" :disabled="!isRecording">停止识别</button>
		</view>
		
		<view class="status-area">
			<text class="status-text">状态：{{ statusText }}</text>
			<text class="model-path" v-if="modelPath">模型路径：{{ modelPath }}</text>
		</view>
		
		<view class="result-area">
			<view class="result-title">
				<text>识别结果：</text>
			</view>
			<view class="result-content">
				<textarea :value="content" disabled class="textarea"></textarea>
			</view>
		</view>
	</view>
</template>

<script setup>
	import { ref, onMounted } from 'vue'
	import { downloadModel, initVoskModel, startSpeechVoice, stopSpeechVoice } from '@/uni_modules/xwq-speech-to-text'
	
	// 定义本地类型（因为 uni-app 无法直接从 .uts 文件导入类型）
	// CallbackType: 初始化回调类型
	// RedultType: 识别结果类型
	// DownloadBackInfo: 下载回调类型
	
	const modelPath = ref('') // 储存模型解压地址
	const content = ref('') // 识别结果
	const isModelReady = ref(false) // 模型是否已初始化
	const isRecording = ref(false) // 是否正在录音
	const downloading = ref(false) // 是否正在下载
	const initializing = ref(false) // 是否正在初始化
	const statusText = ref('未初始化')
	const serverUrl = ref('') // 服务器地址
	
	/**
	 * 页面加载时读取保存的服务器地址
	 */
	onMounted(() => {
		const savedServerUrl = uni.getStorageSync('vosk_server_url')
		if (savedServerUrl) {
			serverUrl.value = savedServerUrl
		}
		// 如果没有配置服务器地址，可以在这里设置默认值
		// 注意：将 192.168.1.100 替换为你的实际局域网 IP 地址
		// if (!serverUrl.value) {
		// 	serverUrl.value = 'http://192.168.1.100:8080/vosk-model-small-cn-0.22.zip'
		// }
	})
	
	/**
	 * 保存服务器配置
	 */
	const saveServerConfig = () => {
		if (serverUrl.value) {
			uni.setStorageSync('vosk_server_url', serverUrl.value)
			uni.showToast({
				title: '配置已保存',
				icon: 'success',
				duration: 1500
			})
		}
	}
	
	/**
	 * 跳转到语音识别页面
	 */
	const goToSpeech = () => {
		uni.navigateTo({
			url: '/pages/speech/speech'
		})
	}
	
	/**
	 * 在线下载模型
	 */
	const downModel = () => {
		downloading.value = true
		statusText.value = '正在下载模型...'
		// 注意：需要替换为真实的模型下载地址
		const url = 'https://alphacephei.com/vosk/models/vosk-model-small-cn-0.22.zip'
		downloadModel(url, (res) => {
			console.log('下载结果：', res)
			downloading.value = false
			if (res.code == 0 && res.filePath != '') {
				statusText.value = '下载完成，正在初始化...'
				initVoskModel({
					zipModelPath: res.filePath
				}, (result) => {
					console.log('模型地址====', result.data)
					if (result && result.data) {
						modelPath.value = result.data.modelPath
						isModelReady.value = true
						statusText.value = '模型初始化成功'
						uni.showToast({
							title: '模型初始化成功',
							icon: 'success'
						})
					}
				})
			} else {
				statusText.value = '下载失败'
				uni.showToast({
					title: '下载失败',
					icon: 'error'
				})
			}
		})
	}
	
	/**
	 * 初始化模型（从 static 目录）
	 * 将返回的模型地址缓存起来，在第二次用到的时候直接传入初始化，避免重复解压
	 */
	const init = () => {
		initializing.value = true
		statusText.value = '正在初始化模型...'
		let path = '/static/vosk-model-small-cn-0.22.zip'
		// #ifdef APP-PLUS
		const staticPath = plus.io.convertLocalFileSystemURL(path)
		initVoskModel({
			zipModelPath: staticPath
		}, (result) => {
			console.log('模型地址====', result.data)
			if (result && result.data) {
				modelPath.value = result.data.modelPath
				isModelReady.value = true
				initializing.value = false
				statusText.value = '模型初始化成功'
				uni.showToast({
					title: '模型初始化成功',
					icon: 'success'
				})
			}
		})
		// #endif
		// #ifndef APP-PLUS
		uni.showToast({
			title: '仅支持 APP 端',
			icon: 'error'
		})
		initializing.value = false
		// #endif
	}
	
	/**
	 * 存在模型解压路径(即已经初始化过一次)
	 */
	const unzipInnit = () => {
		if (!modelPath.value) {
			uni.showToast({
				title: '请先初始化模型',
				icon: 'error'
			})
			return
		}
		initializing.value = true
		statusText.value = '正在加载已解压模型...'
		initVoskModel({
			modelPath: modelPath.value,
			zipModelPath: ''
		}, (result) => {
			console.log('模型地址====', result.data)
			if (result && result.data) {
				isModelReady.value = true
				initializing.value = false
				statusText.value = '模型加载成功'
				uni.showToast({
					title: '模型加载成功',
					icon: 'success'
				})
			}
		})
	}
	
	/**
	 * 开始语音识别
	 */
	const start = () => {
		if (!isModelReady.value) {
			uni.showToast({
				title: '请先初始化模型',
				icon: 'error'
			})
			return
		}
		isRecording.value = true
		statusText.value = '正在录音识别...'
		startSpeechVoice((res) => {
			console.log('识别结果===', res.data)
			if (res && res.data && res.data.text) {
				content.value += res.data.text + ' '
			}
		})
	}
	
	/**
	 * 停止语音识别
	 */
	const stop = () => {
		stopSpeechVoice()
		isRecording.value = false
		statusText.value = '已停止识别'
		uni.showToast({
			title: '已停止识别',
			icon: 'success'
		})
	}
</script>

<style>
	.content {
		display: flex;
		flex-direction: column;
		padding: 20rpx;
		min-height: 100vh;
		background-color: #f5f5f5;
	}
	
	.header {
		display: flex;
		justify-content: center;
		margin: 40rpx 0;
	}
	
	.title {
		font-size: 40rpx;
		font-weight: bold;
		color: #333;
	}
	
	.button-area {
		display: flex;
		flex-direction: column;
		gap: 20rpx;
		margin: 20rpx 0;
	}
	
	.button-area button {
		margin: 0;
	}
	
	.go-speech-btn {
		margin-bottom: 10rpx !important;
		height: 100rpx;
		font-size: 36rpx;
		font-weight: bold;
	}
	
	.divider {
		height: 1px;
		background-color: #e0e0e0;
		margin: 30rpx 0;
	}
	
	.config-area {
		background-color: #fff;
		border-radius: 10rpx;
		padding: 20rpx;
		margin: 20rpx 0;
		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
	}
	
	.config-title {
		font-size: 32rpx;
		font-weight: bold;
		color: #333;
		margin-bottom: 20rpx;
	}
	
	.config-item {
		display: flex;
		flex-direction: column;
		margin-bottom: 15rpx;
	}
	
	.config-label {
		font-size: 28rpx;
		color: #666;
		margin-bottom: 10rpx;
	}
	
	.config-input {
		width: 100%;
		height: 80rpx;
		padding: 0 20rpx;
		border: 1px solid #ddd;
		border-radius: 8rpx;
		font-size: 28rpx;
		background-color: #f9f9f9;
		box-sizing: border-box;
	}
	
	.config-tips {
		margin-top: 15rpx;
		padding: 15rpx;
		background-color: #f0f7ff;
		border-radius: 8rpx;
	}
	
	.tips-text {
		font-size: 24rpx;
		color: #1890ff;
		line-height: 1.5;
	}
	
	.status-area {
		display: flex;
		flex-direction: column;
		padding: 20rpx;
		background-color: #fff;
		border-radius: 10rpx;
		margin: 20rpx 0;
	}
	
	.status-text {
		font-size: 28rpx;
		color: #666;
		margin-bottom: 10rpx;
	}
	
	.model-path {
		font-size: 24rpx;
		color: #999;
		word-break: break-all;
	}
	
	.result-area {
		flex: 1;
		display: flex;
		flex-direction: column;
		margin-top: 20rpx;
	}
	
	.result-title {
		font-size: 32rpx;
		font-weight: bold;
		color: #333;
		margin-bottom: 20rpx;
	}
	
	.result-content {
		flex: 1;
		background-color: #fff;
		border-radius: 10rpx;
		padding: 20rpx;
	}
	
	.textarea {
		width: 100%;
		min-height: 400rpx;
		font-size: 28rpx;
		line-height: 1.6;
		color: #333;
	}
</style>
