<template>
	<view class="page">
		<scroll-view scroll-y class="page-scroll">
			<view class="hero-card">
				<view class="hero-kicker">AI CONTENT STUDIO</view>
				<view class="hero-title">AI 内容创作与宣传助手</view>
				<view class="hero-desc">
					面向团委宣传、活动报道、新闻稿和推文草稿的快速生成场景。填写创作条件后，页面会调用
					`/ai/manuscriptGen` 并通过 WebSocket 实时接收生成内容。
				</view>
				<view class="hero-actions">
					<view class="hero-action" @tap="fillDemo('news')">活动新闻稿</view>
					<view class="hero-action" @tap="fillDemo('wechat')">公众号推文</view>
					<view class="hero-action" @tap="fillDemo('speech')">发言稿提纲</view>
					<view class="hero-action hero-action--primary" @tap="goChatPage">进入问答助手</view>
				</view>
			</view>

			<view class="panel-card">
				<view class="panel-title">创作配置</view>
				<view class="field-list">
					<view class="field-item">
						<view class="field-label">稿件标题</view>
						<uv-input
							v-model="form.theme"
							class="field-input"
							placeholder="例如：广西医科大学开展2026年春季志愿服务活动"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>

					<view class="field-item">
						<view class="field-label">稿件类型</view>
						<picker mode="selector" :range="docTypeLabels" :value="docTypeIndex" @change="handleDocTypeChange">
							<view class="field-picker">
								<text class="field-value">{{ form.docType || '请选择稿件类型' }}</text>
							</view>
						</picker>
					</view>

					<view class="field-item">
						<view class="field-label">活动类型</view>
						<picker mode="selector" :range="eventTypeLabels" :value="eventTypeIndex" @change="handleEventTypeChange">
							<view class="field-picker">
								<text class="field-value">{{ form.eventType || '请选择活动类型' }}</text>
							</view>
						</picker>
					</view>

					<view class="field-item">
						<view class="field-label">活动时间</view>
						<uv-input
							v-model="form.time"
							class="field-input"
							placeholder="例如：2026年4月8日下午"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>

					<view class="field-item">
						<view class="field-label">活动地点</view>
						<uv-input
							v-model="form.location"
							class="field-input"
							placeholder="例如：广西医科大学校史馆广场"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>

					<view class="field-item">
						<view class="field-label">参与人员</view>
						<uv-input
							v-model="form.participants"
							class="field-input"
							placeholder="例如：校团委老师、青年志愿者代表共120人"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>

					<view class="field-item">
						<view class="field-label">建议篇幅</view>
						<uv-input
							v-model="form.number"
							class="field-input"
							type="number"
							placeholder="请输入建议字数"
							placeholder-style="color: #c0c4cc;"
							:maxlength="-1"
						/>
					</view>

					<view class="field-item">
						<view class="field-label">亮点内容</view>
						<textarea
							v-model="form.highlights"
							class="field-textarea"
							placeholder="填写活动亮点、流程、成果、典型细节等关键信息"
							placeholder-style="color: #c0c4cc;"
							maxlength="-1"
						/>
					</view>

					<view class="field-item">
						<view class="field-label">附件材料</view>
						<view class="upload-bar">
							<view class="upload-btn" @tap="chooseFiles">{{ uploading ? '上传中...' : '选择附件' }}</view>
							<view class="upload-tip">支持图片、文档等附件，上传后将按 `files` 字段提交。</view>
						</view>
						<view v-if="uploadedFiles.length" class="file-list">
							<view
								v-for="(item, index) in uploadedFiles"
								:key="`${item.url || item.path || item.name}-${index}`"
								class="file-item-card"
							>
								<view class="file-main">
									<view class="file-badge">附件</view>
									<view class="file-text">
										<view class="file-name">{{ item.name || '未命名文件' }}</view>
										<view class="file-url">{{ item.url || item.path || '-' }}</view>
									</view>
								</view>
								<view class="file-remove" @tap="removeUploadedFile(index)">移除</view>
							</view>
						</view>
					</view>
				</view>

				<view class="submit-bar">
					<view class="ghost-btn" @tap="resetForm">重置</view>
					<view class="primary-btn" :class="{ 'primary-btn--disabled': !canSubmit || loading }" @tap="handleGenerate">
						{{ loading ? '生成中' : '生成内容' }}
					</view>
				</view>

				<view class="hint-block">
					<view class="hint-title">提交说明</view>
					<view class="hint-text">
						页面会先提交 `POST /ai/manuscriptGen`，再通过 WebSocket 流式接收生成结果；如果后端直接返回正文，也会自动兼容显示。
					</view>
				</view>
			</view>

			<view class="panel-card result-card">
				<view class="result-head">
					<view class="panel-title">生成结果</view>
					<view class="result-status" :class="loading ? 'result-status--loading' : markdownContent ? 'result-status--done' : ''">
						{{ loading ? '生成中' : markdownContent ? '已生成' : '待生成' }}
					</view>
				</view>

				<view class="field-item">
					<view class="field-label">生成标题</view>
					<uv-input
						v-model="generatedTitle"
						class="field-input"
						placeholder="生成结果中的标题会显示在这里"
						placeholder-style="color: #c0c4cc;"
						:maxlength="-1"
					/>
				</view>

				<view class="field-item">
					<view class="field-label">正文内容</view>
					<view class="result-shell">
						<view v-if="loading" class="result-streaming">
							{{ markdownContent || 'AI 正在生成内容...' }}
						</view>
						<zero-markdown-view v-else-if="markdownContent" class="result-content" :markdown="markdownContent" />
						<view v-else class="result-empty">填写左侧创作条件后开始生成</view>
					</view>
				</view>

				<view v-if="responseSummary" class="response-meta">
					<view class="response-label">接口摘要</view>
					<view class="response-value">{{ responseSummary }}</view>
				</view>

				<view class="result-actions">
					<view class="result-btn" @tap="copyGenerated">复制正文</view>
					<view class="result-btn" @tap="copyGeneratedTitle">复制标题</view>
					<view class="result-btn" @tap="clearGenerated">清空结果</view>
				</view>
			</view>
		</scroll-view>
	</view>
</template>

<script>
	import {
		buildWebSocketUrl,
		getCurrentUser,
		manuscriptGen,
		uploadTempFile
	} from './service'

	const STREAM_END_TEXT = '__END__'
	const SOCKET_CONNECTED_TEXT = '连接成功'
	const THINK_OPEN_TAG = '<think>'
	const THINK_CLOSE_TAG = '</think>'

	const DOC_TYPE_OPTIONS = ['新闻稿', '公众号推文', '宣传稿', '活动总结', '发言稿', '短视频脚本']
	const EVENT_TYPE_OPTIONS = ['校园活动', '志愿服务', '会议论坛', '社会实践', '文体活动', '品牌项目']

	const createDefaultForm = () => ({
		docType: '新闻稿',
		eventType: '校园活动',
		theme: '',
		time: '',
		location: '',
		participants: '',
		highlights: '',
		number: '800'
	})

	export default {
		data() {
			return {
				form: createDefaultForm(),
				docTypeLabels: DOC_TYPE_OPTIONS,
				eventTypeLabels: EVENT_TYPE_OPTIONS,
				loading: false,
				uploading: false,
				wsConnected: false,
				generatedTitle: '',
				generatedContent: '',
				responseSummary: '',
				uploadedFiles: [],
				activeConversationId: '',
				socketTask: null,
				socketConnectPromise: null,
				streamStarted: false,
				streamResolver: null,
				currentUser: {
					userId: null
				}
			}
		},
		computed: {
			docTypeIndex() {
				const index = this.docTypeLabels.indexOf(this.form.docType)
				return index > -1 ? index : 0
			},
			eventTypeIndex() {
				const index = this.eventTypeLabels.indexOf(this.form.eventType)
				return index > -1 ? index : 0
			},
			canSubmit() {
				return !!(
					String(this.form.theme || '').trim() ||
					String(this.form.highlights || '').trim() ||
					this.uploadedFiles.length
				)
			},
			markdownContent() {
				return this.parseAssistantContent(this.generatedContent).content.trim()
			}
		},
		onUnload() {
			this.clearStreamResolver()
			this.disconnectWebSocket()
		},
		methods: {
			showToast(title) {
				uni.showToast({
					title,
					icon: 'none'
				})
			},
			handleDocTypeChange(event) {
				const index = Number(event.detail.value)
				this.form.docType = this.docTypeLabels[index] || this.form.docType
			},
			handleEventTypeChange(event) {
				const index = Number(event.detail.value)
				this.form.eventType = this.eventTypeLabels[index] || this.form.eventType
			},
			parseAssistantContent(value) {
				if (!value) {
					return {
						content: '',
						thinking: false
					}
				}

				let cursor = 0
				let content = ''
				let thinking = false

				while (cursor < value.length) {
					if (!thinking) {
						const openIndex = value.indexOf(THINK_OPEN_TAG, cursor)
						const closeIndex = value.indexOf(THINK_CLOSE_TAG, cursor)

						if (closeIndex !== -1 && (openIndex === -1 || closeIndex < openIndex)) {
							cursor = closeIndex + THINK_CLOSE_TAG.length
							continue
						}
						if (openIndex === -1) {
							content += value.slice(cursor)
							break
						}
						content += value.slice(cursor, openIndex)
						cursor = openIndex + THINK_OPEN_TAG.length
						thinking = true
						continue
					}

					const closeIndex = value.indexOf(THINK_CLOSE_TAG, cursor)
					if (closeIndex === -1) {
						break
					}
					cursor = closeIndex + THINK_CLOSE_TAG.length
					thinking = false
				}

				return {
					content,
					thinking
				}
			},
			buildPayload() {
				return {
					docType: this.form.docType,
					eventType: this.form.eventType,
					theme: String(this.form.theme || '').trim(),
					time: String(this.form.time || '').trim(),
					location: String(this.form.location || '').trim(),
					participants: String(this.form.participants || '').trim(),
					highlights: String(this.form.highlights || '').trim(),
					number: Number(this.form.number || 0) || 800,
					files: this.uploadedFiles.map((item) => ({
						id: item.id,
						name: item.name,
						path: item.path,
						url: item.url,
						downloadUrl: item.downloadUrl,
						contentType: item.contentType
					}))
				}
			},
			pickString() {
				for (let index = 0; index < arguments.length; index += 1) {
					const value = arguments[index]
					if (typeof value === 'string' && value.trim()) {
						return value.trim()
					}
				}
				return ''
			},
			pickRawStreamString() {
				for (let index = 0; index < arguments.length; index += 1) {
					const value = arguments[index]
					if (typeof value === 'string') {
						return value
					}
				}
				return ''
			},
			stringifyUnknown(value) {
				try {
					return JSON.stringify(value, null, 2)
				} catch (error) {
					return String(value || '')
				}
			},
			parseResult(payload) {
				if (typeof payload === 'string') {
					return {
						title: String(this.form.theme || '').trim(),
						content: payload,
						summary: '接口直接返回文本内容'
					}
				}

				const title = this.pickString(
					payload && payload.title,
					payload && payload.theme,
					payload && payload.name,
					payload && payload.subject,
					payload && payload.data && payload.data.title
				)
				const content = this.pickString(
					payload && payload.content,
					payload && payload.text,
					payload && payload.result,
					payload && payload.output,
					payload && payload.answer,
					payload && payload.manuscript,
					payload && payload.article,
					payload && payload.message,
					payload && payload.data && payload.data.content,
					payload && payload.data && payload.data.text,
					payload && payload.data && payload.data.result,
					payload && payload.data && payload.data.output,
					payload && payload.choices && payload.choices[0] && payload.choices[0].message && payload.choices[0].message.content
				)

				const keys = payload && typeof payload === 'object' ? Object.keys(payload) : []
				return {
					title: title || String(this.form.theme || '').trim(),
					content: content || this.stringifyUnknown(payload),
					summary: keys.length ? `已解析返回字段：${keys.join('、')}` : '返回对象为空'
				}
			},
			parseSerializedResult(payload) {
				const text = String(payload || '').trim()
				if (!text) {
					return payload
				}
				if (
					(text.startsWith('{') && text.endsWith('}')) ||
					(text.startsWith('[') && text.endsWith(']'))
				) {
					try {
						return JSON.parse(text)
					} catch (error) {
						return payload
					}
				}
				return payload
			},
			applyParsedResult(payload, defaultSummary) {
				const parsed = this.parseResult(payload)
				this.generatedTitle = parsed.title || this.generatedTitle || String(this.form.theme || '').trim()
				this.generatedContent = parsed.content
				this.responseSummary = parsed.summary || defaultSummary
			},
			shouldTreatAsDirectResult(payload) {
				if (!payload) {
					return false
				}
				if (typeof payload === 'string') {
					const text = payload.trim()
					if (!text) {
						return false
					}
					return text.startsWith('{') || text.startsWith('[') || text.includes('\n') || text.length > 80
				}
				return typeof payload === 'object'
			},
			clearStreamResolver() {
				this.streamResolver = null
			},
			resolveStream() {
				if (this.streamResolver && this.streamResolver.resolve) {
					this.streamResolver.resolve()
				}
				this.clearStreamResolver()
			},
			rejectStream(error) {
				if (this.streamResolver && this.streamResolver.reject) {
					this.streamResolver.reject(error)
				}
				this.clearStreamResolver()
			},
			createStreamWaiter() {
				this.streamStarted = false
				return new Promise((resolve, reject) => {
					this.streamResolver = {
						resolve,
						reject
					}
				})
			},
			disconnectWebSocket() {
				this.socketConnectPromise = null
				this.wsConnected = false
				const socketTask = this.socketTask
				this.socketTask = null
				if (socketTask && typeof socketTask.close === 'function') {
					try {
						socketTask.close({})
					} catch (error) {
						console.warn('socket close failed', error)
					}
				}
			},
			appendAssistantChunk(chunk, conversationId) {
				if (!chunk) {
					return
				}
				if (conversationId) {
					this.activeConversationId = conversationId
				}
				if (!this.streamStarted) {
					this.streamStarted = true
					this.generatedTitle = this.generatedTitle || String(this.form.theme || '').trim()
					this.generatedContent = ''
				}
				this.generatedContent += chunk
				this.responseSummary = '正在通过 WebSocket 接收流式内容'
			},
			finishStream(conversationId) {
				if (conversationId) {
					this.activeConversationId = conversationId
				}
				const payload = this.parseSerializedResult(this.generatedContent)
				this.applyParsedResult(payload, '已通过 WebSocket 接收生成结果')
				this.loading = false
				this.resolveStream()
				this.disconnectWebSocket()
			},
			handleSocketMessage(raw) {
				if (!raw) {
					return
				}
				try {
					const data = JSON.parse(raw)
					if (data && data.answer === STREAM_END_TEXT) {
						this.finishStream(data.conversationId)
						return
					}
					if (data && (data.loading === 'true' || data.loading === true)) {
						return
					}
					const chunk = this.pickRawStreamString(data && data.notice, data && data.answer)
					if (chunk.length > 0) {
						this.appendAssistantChunk(chunk, data && data.conversationId)
					}
				} catch (error) {
					this.appendAssistantChunk(raw)
				}
			},
			refreshUser() {
				this.currentUser = getCurrentUser()
				return this.currentUser
			},
			connectWebSocket() {
				const user = this.refreshUser()
				if (!user.userId) {
					return Promise.reject(new Error('未获取到当前登录用户'))
				}
				const currentSocket = this.socketTask
				if (currentSocket && this.wsConnected) {
					return Promise.resolve()
				}
				if (currentSocket && this.socketConnectPromise) {
					return this.socketConnectPromise
				}
				if (currentSocket) {
					this.disconnectWebSocket()
				}

				let cleanupPending = () => undefined
				const connectPromise = new Promise((resolve, reject) => {
					let settled = false
					const socketTask = uni.connectSocket({
						url: buildWebSocketUrl(user.userId),
						complete: () => {}
					})

					cleanupPending = () => {
						if (this.socketConnectPromise === connectPromise) {
							this.socketConnectPromise = null
						}
					}

					socketTask.onOpen(() => {
						this.wsConnected = false
					})
					socketTask.onMessage((event) => {
						const raw = String((event && event.data) || '')
						if (raw === SOCKET_CONNECTED_TEXT) {
							this.wsConnected = true
							if (!settled) {
								settled = true
								cleanupPending()
								resolve()
							}
							return
						}
						this.handleSocketMessage(raw)
					})
					socketTask.onClose(() => {
						this.wsConnected = false
						if (this.socketTask === socketTask) {
							this.socketTask = null
						}
						if (!settled) {
							settled = true
							cleanupPending()
							reject(new Error('稿件生成连接已关闭'))
							return
						}
						if (this.loading) {
							this.rejectStream(new Error('稿件生成连接已关闭'))
							this.loading = false
						}
						cleanupPending()
					})
					socketTask.onError(() => {
						this.wsConnected = false
						if (!settled) {
							settled = true
							cleanupPending()
							reject(new Error('稿件生成连接失败'))
							return
						}
						if (this.loading) {
							this.rejectStream(new Error('稿件生成连接失败'))
							this.loading = false
						}
					})
					this.socketTask = socketTask
				})
				this.socketConnectPromise = connectPromise
				return connectPromise
			},
			clearGenerated() {
				this.generatedTitle = ''
				this.generatedContent = ''
				this.responseSummary = ''
			},
			resetForm() {
				this.form = createDefaultForm()
				this.uploadedFiles = []
			},
			fillDemo(type) {
				if (type === 'news') {
					this.form.theme = '广西医科大学青年志愿者开展“清洁校园”志愿服务活动'
					this.form.docType = '新闻稿'
					this.form.eventType = '志愿服务'
					this.form.time = '2026年4月8日下午'
					this.form.location = '广西医科大学校园主干道及公共区域'
					this.form.participants = '校团委老师、青年志愿者代表共120人'
					this.form.highlights = '交代活动时间地点、参与人员、主要服务内容、活动成效和师生反馈，突出青年志愿者精神和劳动教育成效。'
					this.form.number = '900'
					return
				}
				if (type === 'wechat') {
					this.form.theme = '青春集结！这场校园科技文化节亮点抢先看'
					this.form.docType = '公众号推文'
					this.form.eventType = '校园活动'
					this.form.time = '2026年4月中旬'
					this.form.location = '大学生活动中心'
					this.form.participants = '全校青年学生、科技社团成员'
					this.form.highlights = '开头制造期待感，中间分三部分介绍科技互动展区、舞台展示和报名信息，结尾增加报名引导。'
					this.form.number = '1200'
					return
				}
				this.form.theme = '在学校共青团工作会议上的发言提纲'
				this.form.docType = '发言稿'
				this.form.eventType = '会议论坛'
				this.form.time = '2026年春季学期工作会议'
				this.form.location = '学校综合楼报告厅'
				this.form.participants = '各学院团委书记、团干部代表'
				this.form.highlights = '总结上阶段工作，分析问题不足，部署下一阶段思想引领、基层组织建设和实践育人重点任务。'
				this.form.number = '1500'
			},
			async chooseFiles() {
				if (this.uploading) {
					return
				}
				try {
					const result = await new Promise((resolve, reject) => {
						uni.chooseMessageFile({
							count: 5,
							type: 'all',
							success: resolve,
							fail: reject
						})
					})
					const tempFiles = (result && result.tempFiles) || []
					if (!tempFiles.length) {
						return
					}
					this.uploading = true
					uni.showLoading({
						title: '上传中',
						mask: true
					})
					for (let index = 0; index < tempFiles.length; index += 1) {
						const file = tempFiles[index]
						if (file.size && file.size / 1024 / 1024 > 100) {
							this.showToast('单个附件不能超过 100MB')
							continue
						}
						const uploaded = await uploadTempFile(file)
						this.uploadedFiles = [
							...this.uploadedFiles,
							{
								id: uploaded.id,
								name: uploaded.name || file.name,
								path: uploaded.path,
								url: uploaded.url,
								downloadUrl: uploaded.downloadUrl,
								contentType: uploaded.contentType
							}
						]
					}
					this.showToast('附件上传成功')
				} catch (error) {
					if (error && error.errMsg && String(error.errMsg).includes('cancel')) {
						return
					}
					this.showToast((error && error.message) || '上传失败')
				} finally {
					this.uploading = false
					uni.hideLoading()
				}
			},
			removeUploadedFile(index) {
				this.uploadedFiles = this.uploadedFiles.filter((item, current) => current !== index)
			},
			async handleGenerate() {
				if (this.loading) {
					return
				}
				if (!this.canSubmit) {
					this.showToast('请至少填写稿件标题、亮点内容或上传附件中的一项')
					return
				}
				this.loading = true
				this.clearGenerated()
				this.generatedTitle = String(this.form.theme || '').trim()
				this.responseSummary = '正在建立 WebSocket 连接...'
				try {
					await this.connectWebSocket()
					this.responseSummary = '连接成功，已提交生成任务，等待流式返回...'
					const waitStream = this.createStreamWaiter()
					const result = await manuscriptGen(this.buildPayload())
					if (!this.streamStarted && this.shouldTreatAsDirectResult(result)) {
						this.applyParsedResult(
							typeof result === 'string' ? this.parseSerializedResult(result) : result,
							'接口直接返回生成结果'
						)
						this.loading = false
						this.resolveStream()
						this.disconnectWebSocket()
					} else {
						await waitStream
					}
					this.showToast('内容生成完成')
				} catch (error) {
					this.disconnectWebSocket()
					this.clearStreamResolver()
					this.loading = false
					this.showToast((error && error.message) || '内容生成失败')
				}
			},
			copyGenerated() {
				if (!this.markdownContent) {
					this.showToast('暂无可复制的正文内容')
					return
				}
				uni.setClipboardData({
					data: this.markdownContent,
					success: () => {
						this.showToast('正文已复制')
					}
				})
			},
			copyGeneratedTitle() {
				if (!String(this.generatedTitle || '').trim()) {
					this.showToast('暂无可复制的标题')
					return
				}
				uni.setClipboardData({
					data: this.generatedTitle.trim(),
					success: () => {
						this.showToast('标题已复制')
					}
				})
			},
			goChatPage() {
				uni.switchTab({
					url: '/pages/assistant/index'
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
	.page {
		min-height: 100vh;
		background:
			radial-gradient(circle at top right, rgba(20, 150, 242, 0.14), transparent 24%),
			linear-gradient(180deg, #f5fbff 0%, #fff 62%);
	}

	.page-scroll {
		height: 100vh;
		padding: 28rpx 24rpx 40rpx;
	}

	.hero-card {
		padding: 34rpx 30rpx;
		border-radius: 32rpx;
		background: linear-gradient(135deg, #0c5f9f 0%, #1496f2 48%, #67c7ff 100%);
		box-shadow: 0 18rpx 44rpx rgba(20, 118, 194, 0.18);
		color: #f4fbff;
	}

	.hero-kicker {
		font-size: 22rpx;
		letter-spacing: 4rpx;
		color: rgba(255, 244, 233, 0.72);
	}

	.hero-title {
		margin-top: 18rpx;
		font-size: 42rpx;
		line-height: 1.35;
		font-weight: 700;
		color: #fffaf6;
	}

	.hero-desc {
		margin-top: 16rpx;
		font-size: 24rpx;
		line-height: 1.8;
		color: rgba(255, 244, 233, 0.88);
	}

	.hero-actions {
		display: flex;
		flex-wrap: wrap;
		gap: 14rpx;
		margin-top: 26rpx;
	}

	.hero-action {
		padding: 12rpx 20rpx;
		border-radius: 999rpx;
		background: rgba(255, 255, 255, 0.12);
		font-size: 22rpx;
		color: #fff7f1;
	}

	.hero-action--primary {
		background: rgba(255, 255, 255, 0.22);
		font-weight: 700;
	}

	.panel-card {
		margin-top: 24rpx;
		padding: 26rpx;
		border-radius: 30rpx;
		background: rgba(255, 255, 255, 0.96);
		border: 1rpx solid rgba(20, 150, 242, 0.08);
		box-shadow: 0 10rpx 28rpx rgba(34, 94, 142, 0.08);
	}

	.panel-title {
		font-size: 30rpx;
		font-weight: 700;
		color: #1f2329;
	}

	.field-list {
		display: flex;
		flex-direction: column;
		gap: 22rpx;
		margin-top: 20rpx;
	}

	.field-item {
		width: 100%;
	}

	.field-label {
		margin-bottom: 12rpx;
		font-size: 25rpx;
		font-weight: 600;
		color: #2b3037;
	}

	.field-picker,
	.field-textarea {
		width: 100%;
		box-sizing: border-box;
		padding: 22rpx 24rpx;
		border-radius: 22rpx;
		background: #f4faff;
		border: 1rpx solid #d6eafb;
		font-size: 24rpx;
		color: #2a2e35;
	}

	.field-textarea {
		min-height: 220rpx;
	}

	.field-value {
		color: #303133;
	}

	:deep(.field-input.uv-input) {
		width: 100%;
		min-height: 88rpx;
		padding: 0 24rpx;
		border-radius: 12rpx;
		border: 1px solid #dcdfe6;
		background: #ffffff;
		box-sizing: border-box;
	}

	:deep(.field-input.uv-input .uv-input__content__field-wrapper__field) {
		height: 88rpx;
		min-height: 88rpx;
		font-size: 28rpx;
		line-height: 88rpx;
		color: #303133;
	}

	.upload-bar {
		display: flex;
		flex-direction: column;
		gap: 12rpx;
	}

	.upload-btn {
		align-self: flex-start;
		padding: 14rpx 24rpx;
		border-radius: 20rpx;
		background: rgba(20, 150, 242, 0.1);
		color: #1496f2;
		font-size: 24rpx;
		font-weight: 600;
	}

	.upload-tip {
		font-size: 22rpx;
		line-height: 1.7;
		color: #8b817c;
	}

	.file-list {
		display: flex;
		flex-direction: column;
		gap: 14rpx;
		margin-top: 16rpx;
	}

	.file-item-card {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16rpx;
		padding: 18rpx 20rpx;
		border-radius: 22rpx;
		background: #f4faff;
	}

	.file-main {
		display: flex;
		align-items: center;
		gap: 14rpx;
		flex: 1;
		min-width: 0;
	}

	.file-badge {
		padding: 8rpx 14rpx;
		border-radius: 999rpx;
		background: #fff;
		color: #1496f2;
		font-size: 20rpx;
	}

	.file-text {
		flex: 1;
		min-width: 0;
	}

	.file-name {
		font-size: 24rpx;
		font-weight: 600;
		color: #2f3035;
	}

	.file-url {
		margin-top: 8rpx;
		font-size: 20rpx;
		color: #8e837d;
		word-break: break-all;
	}

	.file-remove {
		flex-shrink: 0;
		font-size: 22rpx;
		color: #1496f2;
	}

	.submit-bar,
	.result-actions {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 18rpx;
		margin-top: 24rpx;
	}

	.result-actions {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.ghost-btn,
	.primary-btn,
	.result-btn {
		height: 88rpx;
		line-height: 88rpx;
		border-radius: 22rpx;
		font-size: 28rpx;
		font-weight: 700;
		text-align: center;
	}

	.ghost-btn {
		background: #edf6ff;
		color: #0f7dd1;
		border: 1rpx solid #cfe7fb;
	}

	.primary-btn {
		background: linear-gradient(135deg, #0f7dd1 0%, #1496f2 100%);
		color: #fff;
	}

	.primary-btn--disabled {
		opacity: 0.6;
	}

	.result-btn {
		background: #edf6ff;
		color: #0f7dd1;
	}

	.hint-block,
	.response-meta {
		margin-top: 22rpx;
		padding: 22rpx 24rpx;
		border-radius: 24rpx;
		background: #f4faff;
		border: 1rpx solid rgba(20, 150, 242, 0.08);
	}

	.hint-title,
	.response-label {
		font-size: 24rpx;
		font-weight: 700;
		color: #8b4a12;
	}

	.hint-text,
	.response-value {
		margin-top: 10rpx;
		font-size: 22rpx;
		line-height: 1.75;
		color: #7b6d64;
	}

	.result-card {
		margin-bottom: 20rpx;
	}

	.result-head {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 16rpx;
	}

	.result-status {
		padding: 10rpx 16rpx;
		border-radius: 999rpx;
		background: #f3f0ed;
		color: #8b827d;
		font-size: 20rpx;
		font-weight: 600;
	}

	.result-status--loading {
		background: #edf5ff;
		color: #1554ad;
	}

	.result-status--done {
		background: #eef9f1;
		color: #1f8f49;
	}

	.result-shell {
		min-height: 360rpx;
		padding: 24rpx;
		border-radius: 24rpx;
		background: #fbf7f2;
		border: 1rpx solid #f0e1d5;
	}

	.result-streaming,
	.result-content,
	.result-empty {
		font-size: 24rpx;
		line-height: 1.85;
		white-space: pre-wrap;
		word-break: break-word;
	}

	.result-streaming,
	.result-content {
		color: #2b3037;
	}

	.result-empty {
		color: #9a8f89;
	}
</style>
