feat(voice): add async selectable summaries

This commit is contained in:
zhangheng
2026-07-22 19:13:55 +08:00
parent 108e280b3f
commit 0a2509d923
17 changed files with 1530 additions and 159 deletions

View File

@@ -62,60 +62,103 @@
<view class="voice-state {{voiceActionable ? 'voice-state-actionable' : ''}}" bindtap="toggleVoiceDetails">
<text class="voice-status">{{voiceStatus}}</text>
<text wx:if="{{recording}}" class="recording-time">{{recordingTime}}</text>
<text wx:elif="{{voiceActionable}}" class="voice-state-arrow">{{voiceDetailsExpanded ? '' : ''}}</text>
<view wx:elif="{{voiceActionable}}" class="voice-state-arrow {{voiceDetailsExpanded ? 'voice-state-arrow-expanded' : ''}}"></view>
</view>
<view wx:if="{{voiceDetailsExpanded}}" class="voice-record-list">
<view class="voice-record-list-heading">
<text>语音记录</text>
<text>{{voiceLessonViews.length}}节</text>
</view>
<view
wx:for="{{voiceLessonViews}}"
wx:key="id"
wx:for-item="lesson"
class="voice-record-item"
>
<view class="voice-record-header">
<view>
<text class="voice-record-title">第{{lesson.sequence}}节</text>
<text class="voice-record-duration">{{lesson.durationText}} · {{lesson.transcriptLength}}字</text>
</view>
<text class="voice-record-status">{{lesson.statusText}}</text>
</view>
<text
wx:if="{{lesson.transcript}}"
selectable
class="voice-record-transcript {{expandedVoiceLessonId === lesson.id ? 'voice-record-transcript-expanded' : ''}}"
>{{lesson.transcript}}</text>
<text wx:else class="voice-record-empty">{{lesson.emptyText}}</text>
<view wx:if="{{lesson.transcript}}" class="voice-record-actions">
<view class="voice-summary-selection-heading">
<text>已选 {{selectedSummaryLessonCount}} / {{eligibleSummaryLessonCount}}</text>
<button
wx:if="{{lesson.transcriptExpandable}}"
wx:if="{{eligibleSummaryLessonCount > 0}}"
class="voice-record-text-button"
data-lesson-id="{{lesson.id}}"
bindtap="toggleVoiceLessonTranscript"
>{{expandedVoiceLessonId === lesson.id ? '收起' : '展开'}}</button>
<button
class="voice-record-add-button"
data-lesson-id="{{lesson.id}}"
disabled="{{!lesson.canAdd || voiceBusy}}"
bindtap="addVoiceLessonToDraft"
>{{lesson.addActionText}}</button>
disabled="{{recording || voiceBusy || summaryBusy || (summaryRun && (summaryRun.status === 'queued' || summaryRun.status === 'processing'))}}"
bindtap="toggleAllSummaryLessons"
>{{allSummaryLessonsSelected ? '取消全选' : '全选'}}</button>
</view>
</view>
<checkbox-group bindchange="onSummarySelectionChange">
<view
wx:for="{{voiceLessonViews}}"
wx:key="id"
wx:for-item="lesson"
class="voice-record-item"
>
<view class="voice-record-header">
<view class="voice-record-title-row">
<checkbox
wx:if="{{lesson.summaryEligible}}"
class="voice-summary-checkbox"
value="{{lesson.id}}"
checked="{{lesson.summarySelected}}"
disabled="{{recording || voiceBusy || summaryBusy || (summaryRun && (summaryRun.status === 'queued' || summaryRun.status === 'processing'))}}"
color="#007aff"
/>
<view>
<text class="voice-record-title">第{{lesson.sequence}}节</text>
<text class="voice-record-duration">{{lesson.durationText}} · {{lesson.transcriptLength}}字</text>
</view>
</view>
<text class="voice-record-status">{{lesson.statusText}}</text>
</view>
<text
wx:if="{{lesson.transcript}}"
selectable
class="voice-record-transcript {{expandedVoiceLessonId === lesson.id ? 'voice-record-transcript-expanded' : ''}}"
>{{lesson.transcript}}</text>
<text wx:else class="voice-record-empty">{{lesson.emptyText}}</text>
<view wx:if="{{lesson.transcript}}" class="voice-record-actions">
<button
wx:if="{{lesson.transcriptExpandable}}"
class="voice-record-text-button"
data-lesson-id="{{lesson.id}}"
bindtap="toggleVoiceLessonTranscript"
>{{expandedVoiceLessonId === lesson.id ? '收起' : '展开'}}</button>
<button
class="voice-record-add-button"
data-lesson-id="{{lesson.id}}"
disabled="{{!lesson.canAdd || voiceBusy}}"
bindtap="addVoiceLessonToDraft"
>{{lesson.addActionText}}</button>
</view>
</view>
</checkbox-group>
<view wx:if="{{eligibleSummaryLessonCount > 0}}" class="voice-summary-controls">
<button
class="voice-summary-button"
loading="{{summaryBusy}}"
disabled="{{recording || voiceBusy || summaryBusy || selectedSummaryLessonCount === 0 || (summaryRun && (summaryRun.status === 'queued' || summaryRun.status === 'processing'))}}"
bindtap="generateFeedbackSummary"
>{{summaryActionText}}{{selectedSummaryLessonCount}}节)</button>
</view>
<view wx:if="{{summaryRun}}" class="voice-summary-preview {{summaryRun.status === 'failed' ? 'voice-summary-preview-failed' : ''}}">
<view class="voice-summary-preview-heading">
<text>总结预览</text>
<text>{{summaryStatusText}}</text>
</view>
<text wx:if="{{summaryRun.content}}" selectable class="voice-summary-preview-content">{{summaryRun.content}}</text>
<text wx:elif="{{summaryRun.status === 'queued' || summaryRun.status === 'processing'}}" class="voice-summary-preview-note">已选择 {{summaryRun.lessonIds.length}} 节录音</text>
<button
wx:if="{{summaryRun.status === 'ready'}}"
class="voice-summary-apply-button"
loading="{{summaryBusy}}"
disabled="{{summaryBusy}}"
bindtap="applyGeneratedSummary"
>替换输入框</button>
</view>
</view>
<view wx:if="{{session && session.failedSegmentCount > 0}}" class="voice-failure-recovery">
<text class="voice-failure-hint">{{voiceFailureHint}}</text>
<button
class="voice-discard-button"
disabled="{{saving || generating || recording || voiceBusy}}"
disabled="{{saving || summaryBusy || recording || voiceBusy}}"
bindtap="discardFailedSegments"
>放弃失败录音</button>
</view>
</view>
</view>
<button class="save-feedback primary-button" loading="{{saving || generating}}" disabled="{{saving || generating || recording || voiceBusy || (session && session.processingSegmentCount > 0 && session.failedSegmentCount === 0)}}" bindtap="saveFeedback">{{primaryActionText}}</button>
<button class="save-feedback primary-button" loading="{{saving}}" disabled="{{saving || summaryBusy || recording || voiceBusy || (summaryRun && (summaryRun.status === 'queued' || summaryRun.status === 'processing')) || (session && session.processingSegmentCount > 0 && session.failedSegmentCount === 0)}}" bindtap="saveFeedback">{{primaryActionText}}</button>
</view>
<view wx:if="{{!loading && profiles.length === 0}}" class="profiles-hint">