feat(paper): UI 优化(卡片文字颜色/去模式按钮) + 模板下拉加论文选项并接线渲染
This commit is contained in:
@@ -35,8 +35,10 @@ async def get_paper(paper_id: str, format: str = "md") -> dict[str, Any]:
|
||||
{"n": c.get("final_n") or c.get("temp_n"), "text": c["ref_text"], "kind": c["kind"],
|
||||
"source_doc_id": c["source_doc_id"]} for c in citations
|
||||
]
|
||||
brief = loads_json(paper.get("brief_json"), {})
|
||||
return {
|
||||
"ok": True, "paper_id": paper_id, "title": paper["title"], "status": paper["status"],
|
||||
"template_key": brief.get("template_key") or "document",
|
||||
"sections": [
|
||||
{"section_id": s["section_id"], "title": s["title"], "status": s["status"],
|
||||
"word_count": s["word_count"]} for s in sections
|
||||
|
||||
@@ -33,6 +33,7 @@ async def propose_paper_outline(
|
||||
format_req: str = "",
|
||||
style: str = "",
|
||||
kb_hints: list[dict[str, Any]] | None = None,
|
||||
template_key: str = "",
|
||||
) -> dict[str, Any]:
|
||||
# 1. 知识库线索:优先用调用方传入的 kb_hints,否则现场 search_refs(失败静默降级)
|
||||
hints = list(kb_hints or [])
|
||||
@@ -83,7 +84,7 @@ async def propose_paper_outline(
|
||||
paper_id, title, paper_type, language, format_req, style,
|
||||
dumps_json({
|
||||
"topic": topic, "requirements": requirements, "length": length,
|
||||
"kb_hints": hints,
|
||||
"kb_hints": hints, "template_key": template_key,
|
||||
}),
|
||||
dumps_json({
|
||||
"title": title,
|
||||
|
||||
@@ -24,6 +24,7 @@ class PaperTurnRequest(BaseModel):
|
||||
session_id: str
|
||||
text: str
|
||||
paper_id: str | None = None
|
||||
template_key: str = ""
|
||||
|
||||
|
||||
class PaperConfirmRequest(BaseModel):
|
||||
@@ -50,7 +51,7 @@ async def paper_health() -> dict[str, Any]:
|
||||
|
||||
@router.post("/turns")
|
||||
async def paper_turn(payload: PaperTurnRequest, db: Database = Depends(get_db)) -> dict[str, Any]:
|
||||
blocks = await agent.handle_turn(db, payload.session_id, payload.text, payload.paper_id)
|
||||
blocks = await agent.handle_turn(db, payload.session_id, payload.text, payload.paper_id, payload.template_key)
|
||||
return {"ok": True, "blocks": blocks}
|
||||
|
||||
|
||||
|
||||
@@ -92,15 +92,15 @@ def _missing_brief_keys(brief: dict[str, str]) -> list[str]:
|
||||
# 状态机入口
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def handle_turn(db, session_id: str, text: str, paper_id: str | None = None) -> list[dict[str, Any]]:
|
||||
async def handle_turn(db, session_id: str, text: str, paper_id: str | None = None, template_key: str = "") -> list[dict[str, Any]]:
|
||||
"""一轮对话:推进状态机并落库助手消息,返回消息的 blocks 列表。"""
|
||||
client = _new_client()
|
||||
if not paper_id:
|
||||
return await _trigger(db, session_id, text, client)
|
||||
return await _followup(db, session_id, text, paper_id, client)
|
||||
return await _trigger(db, session_id, text, client, template_key)
|
||||
return await _followup(db, session_id, text, paper_id, client, template_key)
|
||||
|
||||
|
||||
async def _trigger(db, session_id: str, text: str, client: PaperMcpClient) -> list[dict[str, Any]]:
|
||||
async def _trigger(db, session_id: str, text: str, client: PaperMcpClient, template_key: str = "") -> list[dict[str, Any]]:
|
||||
brief = await extract_brief(text)
|
||||
missing = _missing_brief_keys(brief)
|
||||
if missing:
|
||||
@@ -112,7 +112,7 @@ async def _trigger(db, session_id: str, text: str, client: PaperMcpClient) -> li
|
||||
await _write_message(db, session_id, blocks=blocks, text="请补充论文信息")
|
||||
return blocks
|
||||
|
||||
outline = await client.call_tool("propose_paper_outline", brief)
|
||||
outline = await client.call_tool("propose_paper_outline", {**brief, "template_key": template_key})
|
||||
if not outline.get("ok"):
|
||||
blocks = [_text_block(f"论文大纲生成失败:{outline.get('message', '服务未就绪')},请重试。")]
|
||||
await _write_message(db, session_id, blocks=blocks, text=blocks[0]["html"])
|
||||
@@ -147,7 +147,7 @@ def _is_cancel_request(text: str) -> bool:
|
||||
return bool(s) and any(w in s for w in _CANCEL_WORDS)
|
||||
|
||||
|
||||
async def _followup(db, session_id: str, text: str, paper_id: str, client: PaperMcpClient) -> list[dict[str, Any]]:
|
||||
async def _followup(db, session_id: str, text: str, paper_id: str, client: PaperMcpClient, template_key: str = "") -> list[dict[str, Any]]:
|
||||
prog = await client.call_tool("get_paper_progress", {"paper_id": paper_id})
|
||||
if not prog.get("ok"):
|
||||
blocks = [_text_block(f"未找到论文({paper_id}),请重新发起撰写。")]
|
||||
@@ -172,7 +172,7 @@ async def _followup(db, session_id: str, text: str, paper_id: str, client: Paper
|
||||
if status in ("outline", "writing"):
|
||||
# 大纲/撰写阶段:把 text 视为补充要求,重新生成大纲(原大纲作废)
|
||||
brief = await extract_brief(text)
|
||||
outline = await client.call_tool("propose_paper_outline", {**brief, "topic": brief.get("topic") or text})
|
||||
outline = await client.call_tool("propose_paper_outline", {**brief, "topic": brief.get("topic") or text, "template_key": template_key})
|
||||
if outline.get("ok"):
|
||||
await client.call_tool("cancel_paper", {"paper_id": paper_id})
|
||||
return await _outline_message(db, session_id, outline)
|
||||
@@ -289,10 +289,10 @@ async def _artifact_message(db, session_id: str, paper: dict[str, Any], nl: str)
|
||||
refs = paper.get("references") or []
|
||||
title = paper.get("title", "")
|
||||
full_md = _build_full_markdown(paper)
|
||||
html, css = templating.render_html(full_md, title=title, key="document")
|
||||
html, css = templating.render_html(full_md, title=title, key=paper.get("template_key") or "document")
|
||||
artifact_id = await insert_artifact(
|
||||
db, session_id=session_id, title=title,
|
||||
markdown=full_md, template_name="论文", template_key="document",
|
||||
markdown=full_md, template_name="论文", template_key=paper.get("template_key") or "document",
|
||||
template_css=css, html=html,
|
||||
)
|
||||
blocks = [
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// qa → 原 emit('send') 路径(附件注记进消息文本)。
|
||||
// 路由收敛在本组件内,ChatPanel 等上游组件零改动。
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { MODES as MODE_LABELS, T } from '../../i18n/labels.js';
|
||||
import { T } from '../../i18n/labels.js';
|
||||
import { DG, BUILTIN_TEMPLATES } from '../../i18n/docgen.js';
|
||||
import { BF } from '../../i18n/bioflow.js';
|
||||
import { route, paperIntent } from '../../intent.js';
|
||||
@@ -315,7 +315,7 @@ const submit = () => {
|
||||
}
|
||||
if (paperRoute) {
|
||||
try {
|
||||
useChatStore().sendPaper(text).catch(() => {
|
||||
useChatStore().sendPaper(text, { templateId: selectedTemplateId.value || '' }).catch(() => {
|
||||
/* sendPaper 内部已按失败可见原则标注错误 */
|
||||
});
|
||||
} catch {
|
||||
@@ -390,18 +390,6 @@ defineExpose({ clear: () => { draft.value = ''; }, focus: () => textarea.value?.
|
||||
@drop="onDrop"
|
||||
>
|
||||
<div class="composer">
|
||||
<div class="mode-pills">
|
||||
<button
|
||||
v-for="pm in MODE_LABELS"
|
||||
:key="pm.key"
|
||||
type="button"
|
||||
:class="['mode-pill', { on: mode === pm.key }]"
|
||||
:disabled="busy || disabled"
|
||||
@click="emit('change-mode', pm.key)"
|
||||
>
|
||||
{{ pm.label }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- 缺陷 #42:docgen 模式模板下拉(「自动推荐」+ 内置模板 + as_template 资料,懒加载) -->
|
||||
<div v-if="isDocgenMode" class="template-row">
|
||||
<label class="tpl-label" for="pa-template-select">{{ DG.docPaneTemplateLabel }}</label>
|
||||
|
||||
@@ -54,10 +54,10 @@ function submit() {
|
||||
<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-hint { color: #374151; 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-label { min-width: 72px; font-size: 13px; color: #111827; }
|
||||
.pbc-input { flex: 1; padding: 4px 8px; border: 1px solid #d1d5db; border-radius: 4px; color: #111827; }
|
||||
.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; }
|
||||
|
||||
@@ -30,6 +30,8 @@ export const BUILTIN_TEMPLATES = {
|
||||
speech: '讲话稿',
|
||||
plan: '方案计划',
|
||||
document: '通用文档',
|
||||
paper_review: '综述论文',
|
||||
paper_research: '研究论文',
|
||||
};
|
||||
|
||||
export const DG = {
|
||||
|
||||
@@ -1580,7 +1580,7 @@ export const useChatStore = defineStore('chat', {
|
||||
// 写稿期由 confirmPaper 触发后台循环,前端 2s 轮询 paperProgress 更新进度卡,
|
||||
// complete 后重载会话取 agent 直写的 artifact 块并接管到文档 TAB。
|
||||
// ===============================================================
|
||||
async sendPaper(description, { userMsg } = {}) {
|
||||
async sendPaper(description, { userMsg, templateId } = {}) {
|
||||
const q = String(description == null ? '' : description).trim();
|
||||
if (!q || this.busy) return;
|
||||
const sessions = useSessionsStore();
|
||||
@@ -1628,7 +1628,7 @@ export const useChatStore = defineStore('chat', {
|
||||
// 2. POST turns(短同步)
|
||||
let payload;
|
||||
try {
|
||||
payload = await paperTurn({ session_id: sessionId, text: q });
|
||||
payload = await paperTurn({ session_id: sessionId, text: q, template_key: templateId || '' });
|
||||
} catch (err) {
|
||||
const marker = this._errorMarker(err.code);
|
||||
const text = err.message || (marker && marker.text) || T.upstreamError;
|
||||
|
||||
Reference in New Issue
Block a user