- 根目录加入 docker-compose、Caddyfile、文档等原本不在仓库里的文件 - 前后端两份 GitHub Actions 合并成根目录的一个 deploy workflow: 构建前端上传到 build/,再在服务器上 git pull 并重建容器 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.1 KiB
Chain Source Indicator Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: In the ChainModal prompt chain, show a small tag on each round indicating whether it originated from AI dialogue or a manual submission.
Architecture: The Message.source field already exists on the backend ("conversation" or "manual") but is not serialized. We expose it through the existing list_messages endpoint, propagate it through the frontend type, and render a tag badge in ChainModal.vue alongside the existing L1–L6 level badge.
Tech Stack: Django-ninja (Python), Vue 3, TypeScript, Naive UI
File Map
| File | Change |
|---|---|
api/prompt/schemas.py |
Add source: str to MessageOut |
api/prompt/api.py |
Add "source": m.source to list_messages response |
client/src/utils/type.ts |
Add source?: string to PromptMessage |
client/src/components/submissions/ChainModal.vue |
Carry source through rounds, render tag |
Task 1: Expose source from the backend messages endpoint
Files:
-
Modify:
api/prompt/schemas.py -
Modify:
api/prompt/api.py -
Step 1: Add
sourcetoMessageOutschema
Open api/prompt/schemas.py. Change:
class MessageOut(Schema):
id: int
role: str
content: str
code_html: Optional[str] = None
code_css: Optional[str] = None
code_js: Optional[str] = None
prompt_level: Optional[int] = None
created: str
to:
class MessageOut(Schema):
id: int
role: str
source: str
content: str
code_html: Optional[str] = None
code_css: Optional[str] = None
code_js: Optional[str] = None
prompt_level: Optional[int] = None
created: str
- Step 2: Include
sourcein thelist_messagesresponse dict
Open api/prompt/api.py. In list_messages, the return list comprehension currently builds dicts manually. Add "source": m.source:
return [
{
"id": m.id,
"role": m.role,
"source": m.source,
"content": m.content,
"code_html": m.code_html,
"code_css": m.code_css,
"code_js": m.code_js,
"prompt_level": m.prompt_level,
"created": m.created.isoformat(),
}
for m in messages
]
- Step 3: Verify the API returns
sourcemanually
With the dev server running (uv run manage.py runserver from api/), open the Django-ninja interactive docs at http://localhost:8000/api/docs and call the messages endpoint. Confirm the response includes "source": "conversation" or "source": "manual" on each message object.
- Step 4: Commit
git add api/prompt/schemas.py api/prompt/api.py
git commit -m "feat: expose message source field in messages API"
Task 2: Add source to the frontend type
Files:
-
Modify:
client/src/utils/type.ts -
Step 1: Add
sourcetoPromptMessage
Open client/src/utils/type.ts. Change:
export interface PromptMessage {
id: number
role: string
content: string
code_html: string | null
code_css: string | null
code_js: string | null
prompt_level?: number | null
created: string
}
to:
export interface PromptMessage {
id: number
role: string
source?: string
content: string
code_html: string | null
code_css: string | null
code_js: string | null
prompt_level?: number | null
created: string
}
- Step 2: Commit
git add client/src/utils/type.ts
git commit -m "feat: add source field to PromptMessage type"
Task 3: Render source indicator in ChainModal
Files:
-
Modify:
client/src/components/submissions/ChainModal.vue -
Step 1: Carry
sourcethrough the rounds computed property
In ChainModal.vue, the rounds computed property builds objects with { question, prompt_level, html, css, js }. Add source from the user message:
const rounds = computed(() => {
const result: {
question: string
source: string | null
prompt_level: number | null
html: string | null
css: string | null
js: string | null
}[] = []
for (const [i, msg] of messages.value.entries()) {
if (msg.role !== "user") continue
let html: string | null = null,
css: string | null = null,
js: string | null = null
for (const reply of messages.value.slice(i + 1)) {
if (reply.role === "user") break
if (reply.role === "assistant" && reply.code_html) {
html = reply.code_html
css = reply.code_css
js = reply.code_js
break
}
}
result.push({
question: msg.content,
source: msg.source ?? null,
prompt_level: msg.prompt_level ?? null,
html,
css,
js,
})
}
return result
})
- Step 2: Add the source tag to the round card template
Locate the <div> that renders the question and the L-level badge in the round card. It currently looks like:
<div
style="
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
"
>
<span>{{ round.question }}</span>
<span
v-if="round.prompt_level"
:style="{
flexShrink: 0,
fontSize: '11px',
fontWeight: 'bold',
color: levelColors[round.prompt_level],
marginTop: '2px',
}"
>L{{ round.prompt_level }}</span
>
</div>
Replace it with (source tag appears to the right of the L-level badge, in a small flex column):
<div
style="
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
"
>
<span>{{ round.question }}</span>
<div style="display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex-shrink: 0">
<span
v-if="round.prompt_level"
:style="{
fontSize: '11px',
fontWeight: 'bold',
color: levelColors[round.prompt_level],
marginTop: '2px',
}"
>L{{ round.prompt_level }}</span
>
<span
v-if="round.source"
:style="{
fontSize: '10px',
padding: '1px 5px',
borderRadius: '4px',
background: round.source === 'conversation' ? '#e8f0fe' : '#f0f0f0',
color: round.source === 'conversation' ? '#2060c0' : '#888',
fontWeight: 500,
whiteSpace: 'nowrap',
}"
>{{ round.source === "conversation" ? "AI" : "手动" }}</span>
</div>
</div>
- Step 3: Verify visually in the browser
Start the frontend dev server (npm run start from client/). Open the Submissions page, expand a row, click 查看 to open ChainModal. Confirm:
-
Rounds from AI chat show a blue
AIbadge -
Rounds from manual submissions show a gray
手动badge -
Rounds with no
source(old data) show no badge and don't break -
Step 4: Commit
git add client/src/components/submissions/ChainModal.vue
git commit -m "feat: show AI/manual source indicator on each chain round"