Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .commandcode/taste/frontend/taste.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@
- z-index must always be controlled through semantic variables (named layer roles, e.g. column-local layers vs overlay layers with stacking-context audit notes), never bare numeric literals. Confidence: 0.95
- Cares that finished styling modules (e.g. the `.tc-prose` typography module) are reusable in other Tailwind projects — asked about cross-project portability before confirming: values self-contained, zero-runtime, purely CSS-variable-driven forms (vendor copy → npm package → Tailwind `@plugin`) over build-tool-coupled ones (Sass mixins would become dead code once copied out). Confidence: 0.65
- Styling-refactor acceptance runs on two tracks that must never be mixed: value-preserving steps (token renames, same-value re-pointing like `#b07d2e` → `--tc-depth-2`, deleting dead fallbacks) must prove computed-style equivalence (grep zero-hit lists + build-output comparison), while any value-altering fix (e.g. re-targeting the unsaved-state gold to depth-1) is logged as a registered visual change and moved to the screenshot-baseline track — ambiguous historical intent gets logged as a decision, never silently fixed. Confidence: 0.7
- For fallible user-input flows such as attachment uploads, prefers graceful recovery over pretending errors can be eliminated: show failures explicitly, preserve the user's input, and let them remove or retry it. Confidence: 0.9
- Expects attachment inputs to support common plaintext and source-code extensions (including Markdown and JavaScript) rather than limiting acceptance to files the browser labels `text/plain`. Confidence: 0.9
- In chat message bubbles, wants image previews width-constrained so they do not stretch the bubble, and wants non-image files represented as visible attachment cards rather than bare links or omitted UI. Confidence: 0.9
- Wants user-facing UI copy stripped of development-process text, implementation details, and engineering jargon; labels, states, and errors should be phrased around what ordinary users need to understand and do. Confidence: 0.95
- For pasted or voice-transcribed text, prefers a character-count-based hybrid UX: shorter content should be inserted directly into the composer rather than uploaded and shown as an attachment, while genuinely long content can remain an attachment. Confidence: 0.9
1 change: 1 addition & 0 deletions .commandcode/taste/taste.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

## Communication
- Communicates in Simplified Chinese; respond in Chinese. Confidence: 0.7
- For project handoffs/status recaps, prefers a concise, immediately reusable summary structured as: brief requirement context, completed work, and remaining work. Confidence: 0.9
- When asked to react to external reviews (e.g. a GPT critique of its own spec artifacts), first fact-checks the review's claims against the codebase, then delivers a structured verdict — verified-valid points, points held with reservations, plus findings the review missed — and proposes concrete revisions instead of blindly accepting or dismissing. Confidence: 0.8
- Also commissions adversarial re-audits of the assistant's own recent replies, via a second model ("@codex 你来阅读下这最近2条回复看下是否有不对的地方") — so every factual/numeric claim written into replies or artifacts may be re-checked later; on such an audit pass, re-verify each claim against the code (grep counts, file:line evidence, exact numbers not approximations), report findings classified by severity (substantive error vs inconsistency/omission), own mistakes plainly, propose concrete fixes to the artifacts, and apply corrections only after confirmation. Confidence: 0.75
6 changes: 6 additions & 0 deletions .commandcode/taste/workflow/taste.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
- Operates as a principles-then-review loop: states a few inviolable principles up front, delegates the remaining design judgment ("其他的你再看看"), and then wants the written/revised artifacts summarized and presented for his explicit confirmation before implementation/apply begins ("给我再看一眼变更,我来确认") — end the authoring phase by waiting, don't proceed into apply unbidden. Confidence: 0.85
- When confirming a design before apply, wants concrete end-state previews — the finished module's form factor, the planned directory/file structure (e.g. the `tokens/` tree), and sample code — not prose summaries alone ("可以先把 tokens 的结构给我看吗"). Confidence: 0.7
- When a batch of edits lands on the same artifact in parallel, don't trust the per-edit result snapshots (they can show stale/inconsistent state) — re-verify the file's final on-disk state (grep for each inserted marker/phrase, spot-read the long lines) before running validation or reporting completion; caught this twice (design.md, tasks.md) and corrected course. Confidence: 0.65
- For a small PR that is only one slice of a larger initiative, create a long-lived tracking Issue that records the original roadmap, current completed/uncompleted status, and ongoing effectiveness follow-up; link the PR as a phase without closing the umbrella Issue. Confidence: 0.9
- Allows project `.commandcode` configuration and Taste preference files to be committed to the Git repository rather than kept local-only. Confidence: 1.0
- Once implementation is accepted, expects the assistant to complete the delivery workflow by committing and pushing the code, then synchronizing the PR description and tracking Issue rather than stopping at local changes. Confidence: 0.8
- Before extracting helpers, modules, or hooks during a simplification refactor, first assess whether the abstraction is actually necessary; avoid merely relocating complexity, and prefer the smallest local extraction only when concrete repetition or maintenance risk justifies it. Confidence: 0.85
- Dislikes crowded catch-all directories and leans toward grouping cohesive domain logic into a dedicated subdirectory when several related files accumulate. Confidence: 0.7
- For a proposed module/directory migration, wants a clear necessity judgment first; if it is warranted, prefers completing the full migration and reference updates in one pass rather than splitting it into partial follow-ups. Confidence: 0.8
60 changes: 60 additions & 0 deletions app/api/attachments/[id]/content/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { and, eq } from "drizzle-orm"
import { getCurrentUserId } from "@/lib/auth/server"
import { db } from "@/lib/db"
import { attachments } from "@/lib/db/schema"
import { getObjectBytes, isR2Configured } from "@/lib/storage/r2"

type RouteContext = { params: Promise<{ id: string }> }

export async function GET(_req: Request, { params }: RouteContext) {
const userId = await getCurrentUserId()
if (!userId) return Response.json({ error: "未登录" }, { status: 401 })
if (!isR2Configured()) {
return Response.json({ error: "文件服务暂不可用" }, { status: 503 })
}

const { id } = await params
const [row] = await db
.select()
.from(attachments)
.where(and(eq(attachments.id, id), eq(attachments.userId, userId)))
.limit(1)

if (!row) return Response.json({ error: "附件不存在" }, { status: 404 })
if (row.status !== "ready") {
return Response.json(
{
error:
row.status === "failed"
? "这个文件暂时无法预览"
: "文件尚未准备好",
},
{ status: row.status === "failed" ? 422 : 409 }
)
}
if (row.mimeType !== "text/plain") {
return Response.json({ error: "暂不支持预览这个文件" }, { status: 415 })
}

let bytes: Uint8Array
try {
bytes = await getObjectBytes(row.key)
} catch {
return Response.json({ error: "文件读取失败,请稍后重试" }, { status: 502 })
}

let content: string
try {
content = new TextDecoder("utf-8", { fatal: true }).decode(bytes)
} catch {
return Response.json({ error: "这个文件暂时无法预览" }, { status: 422 })
}

return new Response(content, {
headers: {
"Cache-Control": "private, no-store",
"Content-Type": "text/plain; charset=utf-8",
"X-Content-Type-Options": "nosniff",
},
})
}
12 changes: 11 additions & 1 deletion app/api/attachments/[id]/ingest/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,17 @@ export async function POST(_req: Request, { params }: RouteContext) {
return Response.json({ status: "ready", pageCount: extraction.pageCount })
}

// 非 PDF 类型:仅确认对象存在即就绪(图片/压缩包/视频一期只存储、不解析内容)
if (row.mimeType === "text/plain") {
try {
new TextDecoder("utf-8", { fatal: true }).decode(
await getObjectBytes(row.key)
)
} catch {
return markFailed(userId, id, row.key, "文件内容不是有效的 UTF-8 文本")
}
}

// 其他非 PDF 类型:仅确认对象存在即就绪(图片/压缩包/视频一期只存储、不解析内容)
await db
.update(attachments)
.set({ status: "ready", size: actualSize })
Expand Down
8 changes: 6 additions & 2 deletions app/api/attachments/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type RouteContext = { params: Promise<{ id: string }> }
* 附件的稳定读取入口:302 到短时效 presigned GET。
* 消息 parts 里持久化的是本路由的相对路径,presigned URL 每次请求现签,天然不过期。
*/
export async function GET(_req: Request, { params }: RouteContext) {
export async function GET(req: Request, { params }: RouteContext) {
const userId = await getCurrentUserId()
if (!userId) return Response.json({ error: "未登录" }, { status: 401 })
if (!isR2Configured()) {
Expand All @@ -24,7 +24,11 @@ export async function GET(_req: Request, { params }: RouteContext) {
.limit(1)
if (!row) return Response.json({ error: "附件不存在" }, { status: 404 })

return Response.redirect(await presignDownload(row.key), 302)
const download = new URL(req.url).searchParams.get("download") === "1"
return Response.redirect(
await presignDownload(row.key, download ? row.filename : undefined),
302
)
}

/** composer 里移除附件时清理 R2 对象与 DB 行 */
Expand Down
3 changes: 2 additions & 1 deletion app/thread-chat/branching/branchable-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { MessageArtifacts } from "../orchestration/artifacts/message-artifacts"
import { AnchoredAssistantBody } from "./assistant/anchored-assistant-body"
import type { MessageActionViewState } from "../chat/actions/message-action-types"
import type { ThreadMessageActionCommands } from "../chat/actions/message-action-commands"
import type { CommandFileReference } from "../net/commands/conversation-commands"

export interface BranchableChatProps {
state: ThreadTreeState
Expand Down Expand Up @@ -55,7 +56,7 @@ export interface BranchableChatProps {
composerPrefill?: string
/** 根 Thread 模型切换意图;分支 selector 仍由本层锁定。 */
onModelChange: (modelId: string) => void
onSend: (text: string) => void
onSend: (text: string, files: CommandFileReference[]) => void
messageActionState?: MessageActionViewState
messageCommands?: ThreadMessageActionCommands
}
Expand Down
3 changes: 2 additions & 1 deletion app/thread-chat/chat/chat-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ConversationComposer } from "./composer/conversation-composer"
import { ConversationMessage } from "./message/conversation-message"
import type { MessageActionViewState } from "./actions/message-action-types"
import type { ThreadMessageActionCommands } from "./actions/message-action-commands"
import type { CommandFileReference } from "../net/commands/conversation-commands"

export interface ChatViewProps {
/** 会话 id:写到 .msg-list 的 data-list 上(划选气泡靠它反查消息) */
Expand Down Expand Up @@ -48,7 +49,7 @@ export interface ChatViewProps {
/** 分支锁定时显示模型切换限制说明;生成期间仅禁用。 */
modelSelectorDisabledReason?: "branch" | "busy"
onModelChange: (modelId: string) => void
onSend: (text: string) => void
onSend: (text: string, files: CommandFileReference[]) => void
messageActionState?: MessageActionViewState
messageCommands?: ThreadMessageActionCommands
editableUserMessageId?: string
Expand Down
Loading
Loading