forked from admin/PlantAI-rag-new
- 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 单测)
65 lines
2.8 KiB
Vue
65 lines
2.8 KiB
Vue
<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>
|