feat(personal): export reviewed outlines to pptx
This commit is contained in:
@@ -82,15 +82,22 @@
|
||||
<button class="secondary-button" :disabled="outlineBusy" @click="saveOutline">保存修改</button>
|
||||
<button class="primary-button" :disabled="outlineBusy" @click="confirmOutline">确认大纲</button>
|
||||
</view>
|
||||
<view v-else class="outline-actions export-actions">
|
||||
<button v-if="outline.status === 'CONFIRMED' || outline.status === 'FAILED'" class="primary-button" :disabled="outlineBusy" @click="generatePpt">
|
||||
{{ outline.status === 'FAILED' ? '重新生成 PPT' : '生成 PPT' }}
|
||||
</button>
|
||||
<button v-if="outline.status === 'PENDING' || outline.status === 'RUNNING'" class="secondary-button" disabled>正在生成 PPT</button>
|
||||
<button v-if="outline.status === 'READY'" class="primary-button" :disabled="outlineBusy" @click="downloadPpt">下载 PPT</button>
|
||||
</view>
|
||||
<text v-if="outlineMessage" class="status-message">{{ outlineMessage }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onUnmounted, ref } from 'vue';
|
||||
import type { PersonalAskResponse, PersonalCitation, PersonalExportOutline } from '@/types/api';
|
||||
import { askPersonalAssistant, confirmPersonalOutline, createPersonalOutline, updatePersonalOutline, type KnowledgeScope } from '@/services/personal-assistant';
|
||||
import { askPersonalAssistant, confirmPersonalOutline, createPersonalOutline, downloadPersonalExport, enqueuePersonalPpt, loadPersonalExport, updatePersonalOutline, type KnowledgeScope } from '@/services/personal-assistant';
|
||||
import { personalItemRoute } from '@/services/personal-assistant-id';
|
||||
|
||||
const scopes = ref<KnowledgeScope[]>(['PERSONAL']);
|
||||
@@ -102,6 +109,7 @@ const outline = ref<PersonalExportOutline>();
|
||||
const outlineBusy = ref(false);
|
||||
const outlineMessage = ref('');
|
||||
let requestGeneration = 0;
|
||||
let exportPollTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
const enterpriseEnabled = computed(() => scopes.value.includes('ENTERPRISE'));
|
||||
|
||||
const scopeKey = (value: KnowledgeScope[]) => value.join(',');
|
||||
@@ -110,6 +118,7 @@ const changeScope = () => {
|
||||
requestGeneration += 1;
|
||||
answer.value = undefined;
|
||||
outline.value = undefined;
|
||||
stopExportPolling();
|
||||
message.value = '';
|
||||
scopes.value = enterpriseEnabled.value ? ['PERSONAL'] : ['PERSONAL', 'ENTERPRISE'];
|
||||
};
|
||||
@@ -122,6 +131,7 @@ const submitQuestion = async () => {
|
||||
asking.value = true;
|
||||
answer.value = undefined;
|
||||
outline.value = undefined;
|
||||
stopExportPolling();
|
||||
message.value = '';
|
||||
try {
|
||||
const response = await askPersonalAssistant({ queryText: question, scope: scopeSnapshot, outputFormat: 'ACTION_PLAN' });
|
||||
@@ -206,6 +216,74 @@ const confirmOutline = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const stopExportPolling = () => {
|
||||
if (exportPollTimer) clearTimeout(exportPollTimer);
|
||||
exportPollTimer = undefined;
|
||||
};
|
||||
|
||||
const pollExport = async (id: string, attempts = 0) => {
|
||||
stopExportPolling();
|
||||
try {
|
||||
const latest = await loadPersonalExport(id);
|
||||
if (!outline.value || outline.value.id !== id) return;
|
||||
outline.value = latest;
|
||||
if (latest.status === 'READY') {
|
||||
outlineMessage.value = 'PPT 已生成,可下载 5 分钟短链';
|
||||
return;
|
||||
}
|
||||
if (latest.status === 'FAILED') {
|
||||
outlineMessage.value = 'PPT 生成失败,可重新生成';
|
||||
return;
|
||||
}
|
||||
if (attempts >= 40) {
|
||||
outlineMessage.value = 'PPT 仍在后台生成,请稍后重试';
|
||||
return;
|
||||
}
|
||||
exportPollTimer = setTimeout(() => void pollExport(id, attempts + 1), 1500);
|
||||
} catch (error) {
|
||||
outlineMessage.value = error instanceof Error ? error.message : 'PPT 状态查询失败';
|
||||
}
|
||||
};
|
||||
|
||||
const generatePpt = async () => {
|
||||
if (!outline.value?.confirmed || outlineBusy.value) return;
|
||||
outlineBusy.value = true;
|
||||
outlineMessage.value = '';
|
||||
try {
|
||||
await enqueuePersonalPpt(outline.value.id);
|
||||
outline.value.status = 'PENDING';
|
||||
outlineMessage.value = 'PPT 已进入后台生成队列';
|
||||
void pollExport(outline.value.id);
|
||||
} catch (error) {
|
||||
outlineMessage.value = error instanceof Error ? error.message : 'PPT 生成请求失败';
|
||||
} finally {
|
||||
outlineBusy.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const downloadPpt = async () => {
|
||||
if (!outline.value || outline.value.status !== 'READY' || outlineBusy.value) return;
|
||||
outlineBusy.value = true;
|
||||
try {
|
||||
const download = await downloadPersonalExport(outline.value.id);
|
||||
uni.downloadFile({
|
||||
url: download.url,
|
||||
success: (result) => {
|
||||
if (result.statusCode >= 200 && result.statusCode < 300) {
|
||||
uni.openDocument({ filePath: result.tempFilePath, showMenu: true });
|
||||
} else {
|
||||
outlineMessage.value = 'PPT 下载失败,请重试';
|
||||
}
|
||||
},
|
||||
fail: () => { outlineMessage.value = 'PPT 下载失败,请重试'; }
|
||||
});
|
||||
} catch (error) {
|
||||
outlineMessage.value = error instanceof Error ? error.message : 'PPT 下载失败';
|
||||
} finally {
|
||||
outlineBusy.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const addOutlineSection = () => outline.value?.sections.push({ title: '', bullets: [''] });
|
||||
const addOutlineBullet = (sectionIndex: number) => outline.value?.sections[sectionIndex]?.bullets.push('');
|
||||
|
||||
@@ -223,6 +301,7 @@ const goBack = () => uni.navigateBack();
|
||||
const openLibrary = () => uni.navigateTo({ url: '/pages/user/assistant/library' });
|
||||
const openCapture = () => uni.navigateTo({ url: '/pages/user/assistant/capture' });
|
||||
const openSessions = () => uni.navigateTo({ url: '/pages/user/assistant/sessions' });
|
||||
onUnmounted(stopExportPolling);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -187,3 +187,19 @@ export const confirmPersonalOutline = (id: string) => apiRequest<void>({
|
||||
url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}/confirm`,
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
export const loadPersonalExport = (id: string) => apiRequest<PersonalExportOutline>({
|
||||
url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
export const enqueuePersonalPpt = (id: string) => apiRequest<void>({
|
||||
url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}/pptx`,
|
||||
method: 'POST',
|
||||
data: { templateCode: 'standard' }
|
||||
});
|
||||
|
||||
export const downloadPersonalExport = (id: string) => apiRequest<{ url: string; expiresAt?: string }>({
|
||||
url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}/download-url`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user