import { For, createSignal, createMemo } from "solid-js";
import { useNavigate, useParams } from "@tanstack/solid-router";
import { getEmailsForMailbox } from "~/data/emails";
import { MailListItem } from "~/components/MailListItem";
export function MailList() {
const params = useParams({ strict: false }) as {
mailbox?: string;
id?: string;
};
const navigate = useNavigate();
const [query, setQuery] = createSignal("");
const mailbox = () => params.mailbox || "inbox";
const list = createMemo(() => {
const q = query().toLowerCase();
return getEmailsForMailbox(mailbox()).filter(
(e) =>
!q ||
e.subject.toLowerCase().includes(q) ||
e.snippet.toLowerCase().includes(q)
);
});
function open(id: string) {
navigate({
to: "/mail/$mailbox/$id",
params: { mailbox: mailbox(), id },
search: (prev) => prev,
});
}
return (
<For each={list()}>
{(e) => (
<div
role="listitem"
tabindex={0}
onClick={() => open(e.id)}
onKeyDown={(ev) => ev.key === "Enter" && open(e.id)}
class="mail-item"
data-selected={params.id === e.id ? "true" : undefined}
aria-selected={params.id === e.id ? "true" : undefined}
>
<div>
<div class="mail-item-subject truncate">{e.subject}</div>
<div class="mail-item-snippet truncate">{e.snippet}</div>
</div>
<time
class="text-xs muted"
datetime={e.date}
title={new Date(e.date).toLocaleString()}
>
{new Date(e.date).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
})}
</time>
</div>
<MailListItem
email={e}
isSelected={params.id === e.id}
onOpen={open}
/>
)}
</For>
);
}
import type { Email } from "~/types";
interface MailListItemProps {
email: Email;
isSelected: boolean;
onOpen: (id: string) => void;
}
export function MailListItem(props: MailListItemProps) {
return (
<div
role="listitem"
tabindex={0}
onClick={() => props.onOpen(props.email.id)}
onKeyDown={(ev) => ev.key === "Enter" && props.onOpen(props.email.id)}
class="mail-item"
data-selected={props.isSelected ? "true" : undefined}
aria-selected={props.isSelected ? "true" : undefined}
>
<div>
<div class="mail-item-subject truncate">{props.email.subject}</div>
<div class="mail-item-snippet truncate">{props.email.snippet}</div>
</div>
<time
class="text-xs muted"
datetime={props.email.date}
title={new Date(props.email.date).toLocaleString()}
>
{new Date(props.email.date).toLocaleDateString(undefined, {
month: "short",
day: "numeric",
})}
</time>
</div>
);
- 建立新元件
MailListItem 處理個別郵件清單項目渲染的元件 - 從
MailList 擷取郵件項目 JSX 與邏輯到新的MailListItem 元件中 - 更新
MailList 以使用新的MailListItem 元件中 - 建置專案以驗證變更是否正確編譯
- 測試應用程式以確保功能保持正常
- 建立了一個用於渲染個別郵件清單項目的可重用元件
- 接受
email ,isSelected ,以及onSelect 等屬性 (props) - 維持所有無障礙功能 (ARIA 角色、鍵盤導覽)
- 保留樣式與互動行為
- 透過移除行內郵件項目渲染邏輯來簡化
- 現在使用
MailListItem 元件並搭配簡潔的屬性傳遞 - 降低了元件複雜度並提升可讀性
- ✅ 建置成功
- ✅ 所有功能正常運作 (篩選、選取、導覽)
- ✅ 保留鍵盤無障礙功能 (Enter 鍵導覽)
- ✅ 視覺選取狀態正常運作
為您建置開發工作的 Agent
將任務交給 AI Agent,讓它們自主規劃、修改程式碼、執行指令並持續迭代,直到工作完成。
例如,指派一個基於 CLI 的 Agent 在背景分類並修復錯誤,與另一個 Agent 互動以在整合式瀏覽器中使用即時驗證來實作功能,並將首頁重新設計委託給雲端 Agent,讓它開啟提取要求供您的團隊審核。
開始使用 Agentpackage http
import (
"io"
"log/slog"
"mime/multipart"
"net/http"
"strings"
)
type BatchItemResult struct {
Name string `json:"name"`
Metadata *struct {
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
Bytes int `json:"bytes"`
} `json:"metadata,omitempty"`
Error string `json:"error,omitempty"`
}
type BatchResponse struct {
Results []*BatchItemResult `json:"results"`
Count int `json:"count"`
Success int `json:"success"`
Failed int `json:"failed"`
}
// handleProcessBatch processes multiple uploaded images (multipart/form-data) under the field name "files".
// It returns metadata for each image or an error per item without failing the whole batch unless the request is malformed.
func (s *Server) handleProcessBatch(w http.ResponseWriter, r *http.Request) {
// Enforce max body size overall.
r.Body = http.MaxBytesReader(w, r.Body, s.cfg.MaxUploadBytes)
if ct := r.Header.Get("Content-Type"); !strings.HasPrefix(ct, "multipart/form-data") {
s.writeJSON(w, http.StatusBadRequest, map[string]string{"error": "content type must be multipart/form-data"})
return
}
if err := r.ParseMultipartForm(s.cfg.MaxUploadBytes); err != nil {
status := http.StatusBadRequest
if strings.Contains(err.Error(), "request body too large") {
status = http.StatusRequestEntityTooLarge
}
s.writeJSON(w, status, map[string]string{"error": "invalid multipart form: " + err.Error()})
return
}
// Accept files under the key "files". If absent, attempt to fallback to any file parts.
var fileHeaders []*multipart.FileHeader
if r.MultipartForm != nil && len(r.MultipartForm.File["files"]) > 0 {
fileHeaders = r.MultipartForm.File["files"]
} else if r.MultipartForm != nil {
// Fallback: gather all files across keys.
for _, fhs := range r.MultipartForm.File {
fileHeaders = append(fileHeaders, fhs...)
}
}
if len(fileHeaders) == 0 {
s.writeJSON(w, http.StatusBadRequest, map[string]string{"error": "no files provided (expect key 'files')"})
return
}
resp := &BatchResponse{Results: make([]*BatchItemResult, 0, len(fileHeaders))}
for _, fh := range fileHeaders {
item := &BatchItemResult{Name: fh.Filename}
f, err := fh.Open()
if err != nil {
item.Error = "open file: " + err.Error()
resp.Results = append(resp.Results, item)
resp.Failed++
continue
}
data, err := io.ReadAll(f)
_ = f.Close()
if err != nil {
item.Error = "read file: " + err.Error()
resp.Results = append(resp.Results, item)
resp.Failed++
continue
}
meta, err := s.proc.ExtractMetadata(data)
if err != nil {
item.Error = err.Error()
resp.Results = append(resp.Results, item)
resp.Failed++
continue
}
// Copy into anonymous struct to decouple from internal type if it changes.
item.Metadata = &struct {
Format string `json:"format"`
Width int `json:"width"`
Height int `json:"height"`
Bytes int `json:"bytes"`
}{Format: meta.Format, Width: meta.Width, Height: meta.Height, Bytes: meta.Bytes}
resp.Results = append(resp.Results, item)
resp.Success++
}
resp.Count = len(resp.Results)
s.log.LogAttrs(r.Context(), slog.LevelInfo, "batch processed", slog.Int("count", resp.Count), slog.Int("success", resp.Success), slog.Int("failed", resp.Failed))
s.writeJSON(w, http.StatusOK, resp)
}
建立待辦事項清單
我將從建立待辦事項清單開始,管理諸如讀取現有的伺服器和處理器程式碼、設計批次端點契約,以及實作一個包含處理常式的新檔案等任務。我希望將處理常式連接到伺服器路由中,並可能為批次端點添加一個基本的測試,雖然那是可選的。我還計劃在之後執行測試並建置所有內容。讓我們開始執行吧!正在改進檔案處理...
我可以修改 server.go,但他們要求使用新檔案。因此,我需要建立一個路由,暫且稱之為 /process/batch。我將強制執行最大 body 大小,並確保內容類型為 multipart/form-data。接著,我將解析表單並反覆運算 form.File["files"]。 對於每個 FileHeader,我會開啟它並讀取位元組,然後呼叫 ExtractMetadata 來收集結果。最後,我將回傳一個包含結果的結構化 JSON 回應,並處理無檔案和最大批次限制的極端情況。 我認為這可能不是絕對必要,但我正考慮使用 sync.WaitGroup 依序處理每個檔案,同時限制並行性。這將使初始提交更簡單。我需要新增程式碼檔案並匯入 internal/image 引用別名。 接著,我將查看 tests/image_processor_test.go 中現有的測試,了解其風格並決定是否應該為批次處理常式建立新的測試檔案。我將通讀該檔案,然後更新待辦事項狀態,將任務一和二標記為已完成。準備 HTTP 測試
我需要執行 HTTP 測試,但我注意到目前的測試套件位於 'tests' 中,而不是在主模組內。為了進行 HTTP 測試,我必須設定幾項內容,如設定、處理器、記錄器和伺服器,並發送一個多部分請求。我的下一步是檢查 config.go 中的設定,以了解我需要包含哪些必要欄位。讓我們開始深入研究吧!管理更新與修補程式
我需要使用待辦事項清單管理功能來保持一切井然有序,確保我不會遺漏任何任務。一旦完成,我就可以繼續套用修補程式。這將涉及新增一個新檔案並對 server.go 進行編輯。我希望確保在此過程中一切都清晰準確,以避免出現任何錯誤。讓我們繼續前進!實作更新與測試
我將設計契約標記為已完成,接著我需要實作更新。我將繼續管理待辦事項清單,並套用修補程式以新增檔案並更新 server.go。看來我可能需要在 server.go 中為 /process/batch 新增路由,並實作一個新檔案 internal/http/batch.go。撰寫程式碼後,我會新增測試檔案,使用 go test 執行測試,並使用 run_in_terminal 命令。讓我們開始吧!-
server.go :註冊了新路由 POST /process/batch。 batch.go :新的處理常式 handleProcessBatch,其功能為:-
- 接受 multipart/form-data。
- 讀取 files 鍵下的多個檔案 (如果缺少 files 則回退到任何檔案部分)。
- 回傳個別檔案的成功/錯誤資訊,而不會導致整個批次失敗。
- 彙總計數:總數、成功、失敗。
-
batch_endpoint_test.go :測試正常流程 (1 個有效 + 1 個無效檔案) 和缺少檔案的錯誤情況。
- 新增每批次最大檔案數的可設定限制 (例如:透過 config)。
- 針對極大型影像使用記憶體受限的串流處理 (處理 + 捨棄)。
- 使用工作池 (worker pool) 和內容取消 (context cancellation) 進行可選的並行處理。
- 在解碼前對每個檔案進行大小驗證。
- 為非多部分請求的用戶端支援基於 JSON 的批次 (URL 或 base64)。
任何 Agent,任何模型
使用符合您工作流程的 Agent 套件。在本地或雲端執行 Agent,並搭配 Copilot 或 Claude、OpenAI 等第三方供應商。
從各家供應商的數十種模型中進行選擇,從快速完成模型到進階推理模型應有盡有。或者自備金鑰以使用來自任何供應商的任何模型。
所有工作階段,一覽無遺
在多個 Agent 並行處理任務時保持高效率。從單一檢視畫面追蹤所有 Agent 工作階段,無論它們在哪裡執行。
快速篩選並監控工作階段,或深入了解個別 Agent 的互動,無需切換到不同的工具或終端機。
您的規則,您的 Agent
確保 Agent 遵循您的實踐經驗與團隊工作流程。定義自訂指令、新增 Agent 技能,或建置為您的專案量身打造的自訂 Agent。
透過 MCP 伺服器連接外部工具與服務,或安裝 Agent 外掛程式或延伸模組以擴展 Agent 的功能。
核心是世界級的程式碼編輯器
十多年來,VS Code 一直是數百萬開發者的首選編輯器。AI 驅動的行內建議、智慧補全和豐富的編輯體驗,讓您在親自撰寫程式碼時同樣強大。
在 Agent 協助工作與親自編寫程式碼之間無縫切換,一切都在同一個編輯器中完成。
開始使用 VS Codeimport numpy as np
import pandas as pd
iris_data = pd.read_csv("iris_dataset.csv")
def describe(species: str) -> pd.Series:
7
subset = data[data["species"] == species]
subset = iris_data[iris_data["species"] == species]
if subset.empty:
raise ValueError(f"{species} missing from sample")
return subset[["petal", "sepal"]].agg(["mean", "std"]).loc["mean"]
def summary():
13
for species in np.sort(data["species"].unique()):
for species in np.sort(iris_data["species"].unique()):
try:
stats = describe(species)
except ValueError:
print(f"{species}: no records")
continue
print(f"{species}: petal={stats['petal']:.2f} sepal={stats['sepal']:.2f}")
if __name__ == "__main__":
summary()
透過延伸模組編寫程式碼
使用來自延伸模組和模型內容協定 (MCP) 伺服器的工具來擴展您的 Agent。或者,建置您自己的延伸模組來支援您團隊的獨特情境。
支援任何語言
VS Code 支援幾乎所有主流程式語言。內建支援多種語言,如 JavaScript、TypeScript、CSS 和 HTML,而其他語言的延伸模組可在 VS Code 市集中找到。

JavaScript
TypeScript
Python
C#
C++
HTML
Java
JSON
PHP
Markdown
Powershell
YAML隨處編寫程式碼
無論您是連接到雲端、遠端存放庫,還是在瀏覽器中使用 VS Code 網頁版 (vscode.dev),都能在您生產力最高的地方編寫程式碼。
內建原始碼控制為您提供現成的 Git 支援。許多其他原始碼控制供應商也可透過延伸模組取得。
GitHub Codespaces 為任何活動提供雲端驅動的開發環境 — 無論是長期專案,還是像審查提取要求這樣的短期任務。
功能豐富的編碼體驗
編輯器的功能遠不止於此。無論是使用內建功能還是豐富的延伸模組,每個人都能找到適合自己的功能。