Files
PlantAI-rag-new/ui/src/components/chat/PaperBriefCard.vue
T
admin 91f6a1c242 feat: 新增论文撰写功能 paper-writer(独立 MCP 服务 + BFF 大脑 + 前端三卡)
- paper_writer_service(8097):FastAPI + MCP streamable-http 单端点,9 工具
  (propose_paper_outline/write_paper_section/get_paper_progress/search_refs/
  revise_paper_section/revise_paper/get_paper/complete_paper/cancel_paper),
  SQLite 三表(papers/paper_sections/citations,WAL),GB/T 7714 引用协议
- BFF:services/paper/{mcp_client,agent}.py(裸 JSON-RPC 客户端 + 规则状态机)、
  routers/paper.py、schemas.py 4 个 Paper*Block + ArtifactBlock 加 paper_id/citation_count、
  config.py 开关、main.py 条件注册、docgen.py 抽 insert_artifact 公共 helper
- 前端:intent.js paperIntent 纯函数、Composer docgen 分支 sub-route、sendPaper/轮询、
  PaperBriefCard/PaperOutlineCard/PaperProgressCard、DocPane cite-chip
- deploy/paper_restart.sh + toggle_paper.sh;compile_sdk.sh 白名单追加 paper 文件
- tests/paper 26 条(工具逻辑 + MCP 协议 + BFF 状态机 + 块 schema + 前端 intent 单测)
2026-09-30 22:29:22 +08:00

65 lines
2.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
// PaperBriefCard.vue —— 论文引导表单卡(预填已抽取 brief,缺失字段待补)
// 卡片辅助、NL 主导:提交时把已填字段拼成一句 NL,回 Composer 走 sendPaper 提交。
import { ref, computed } from 'vue';
import { PP } from '../../i18n/paper.js';
import { useChatStore } from '../../stores/chat.js';
const props = defineProps({ block: { type: Object, required: true } });
const fields = computed(() => (Array.isArray(props.block.fields) ? props.block.fields : []));
const submitting = ref(false);
const values = ref({});
for (const f of fields.value) {
if (!(f.key in values.value)) values.value[f.key] = f.value || '';
}
function submit() {
if (submitting.value || props.block.submitted) return;
submitting.value = true;
const parts = fields.value
.filter((f) => values.value[f.key] && String(values.value[f.key]).trim())
.map((f) => `${f.label}:${String(values.value[f.key]).trim()}`);
const text = `帮我写一篇论文:${parts.join(';')}`;
props.block.submitted = true;
try {
useChatStore().sendPaper(text).catch(() => { /* 内部已标注错误 */ });
} catch { /* chat store 未就绪:降级 */ }
submitting.value = false;
}
</script>
<template>
<div class="paper-brief-card">
<div class="pbc-title">{{ PP.paperBriefTitle }}</div>
<div class="pbc-hint">{{ PP.paperBriefHint }}</div>
<div v-for="f in fields" :key="f.key" class="pbc-field">
<label class="pbc-label">{{ f.label }}</label>
<select v-if="Array.isArray(f.options) && f.options.length" v-model="values[f.key]" class="pbc-input">
<option value=""></option>
<option v-for="o in f.options" :key="o" :value="o">{{ o }}</option>
</select>
<input v-else v-model="values[f.key]" class="pbc-input" type="text" />
</div>
<div class="pbc-footer">
<button v-if="!props.block.submitted" class="pbc-submit" type="button" :disabled="submitting" @click="submit">
{{ submitting ? PP.paperBriefSubmitting : PP.paperBriefSubmit }}
</button>
<span v-else class="pbc-done">✓</span>
</div>
</div>
</template>
<style scoped>
.paper-brief-card { border: 1px solid var(--border, #e5e7eb); border-radius: 8px; padding: 12px; margin: 8px 0; }
.pbc-title { font-weight: 600; margin-bottom: 4px; }
.pbc-hint { color: #6b7280; font-size: 12px; margin-bottom: 8px; }
.pbc-field { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.pbc-label { min-width: 72px; font-size: 13px; color: #374151; }
.pbc-input { flex: 1; padding: 4px 8px; border: 1px solid #d1d5db; border-radius: 4px; }
.pbc-footer { margin-top: 8px; }
.pbc-submit { padding: 6px 14px; border: none; border-radius: 6px; background: #2563eb; color: #fff; cursor: pointer; }
.pbc-done { color: #16a34a; }
</style>