PROJECTS

Selected work.

Products, infrastructure, and applied AI. This page will grow as each project is ready to share.

PDF Tools

Full-stack / Documents
Live

A small PDF toolbox in the spirit of iLovePDF. Editing, merging, splitting and image conversion run in the browser with pdf.js and pdf-lib, so those files never leave the device. The server handles what a browser can't: Word to PDF through headless LibreOffice, PDF to Word through pdf2docx, and changing text that is already in the PDF. For that last one I click a line, type, and PyMuPDF removes the original glyphs from the page content with a redaction, so coloured backgrounds stay intact. It then redraws the line with the PDF's own embedded font when that font has every character needed. Embedded subsets rarely keep a usable cmap, so I rebuild one from how the page already uses the font. When characters are missing it falls back to the closest Noto CJK face (serif or sans, regular or bold), subsetted to just the new characters so the file stays small. Conversions share one queue that turns requests away when it gets long, since the host has two cores. A push to main deploys it through GitHub Actions.

FastAPIPyMuPDFpdf-libpdf.jsLibreOfficeDocker

BookRAG

AI / Reading
Live

A spoiler-free reading companion: a three-pane reader (table of contents, text, chat) where the assistant answers questions about the book using only text before the reader's current position. Progress is stored per paragraph, so the model never sees the page you haven't reached. Books are split into chapters and chunks, embedded with Qwen3-Embedding into pgvector, and summarized chapter by chapter in order; those summaries and entity cards serve as long-term memory. When the read portion fits the context window the full text is used directly; otherwise hybrid retrieval (vector + trigram) with reranking selects evidence, and a hard boundary check refuses any chunk past the reader's position before it reaches the model. Answers stream with citations that jump back to the source paragraph; bookmarks and per-account libraries round it out.

Next.jsPostgreSQL / pgvectorDeepSeekQwen3-EmbeddingDocker

Blockade · 封锁线

Game / Constraint solving
Live

A single-player web puzzle built on Slitherlink's "draw the unique closed loop" rule and turned into a detective story: a city was cordoned off overnight, and the player reconstructs the cordon from patrol counts and witness testimonies. The loop naturally splits the map into inside and outside, so testimonies such as "the pharmacy was inside", "the courier and the doctor were on opposite sides" and, in later chapters, "exactly one of these statements is false" all become constraints on the same drawing. Under the hood is a custom solver (edge-ordered backtracking with unit propagation on cell counts and vertex degrees, 6×6 boards in ~2 ms) that also powers an authoring tool: every hand-designed case is verified to have exactly one solution, and the tool reports whether the solution depends on the testimonies. The reader-facing side is an SVG board with drag-to-draw, live consistency feedback, region fill on closure, a staged reveal that checks testimonies one by one, and an accusation step; accounts sync progress across devices.

Next.jsTypeScriptConstraint solverSVGPostgreSQL

StreamLens

Full-stack / Recommendations
Live

A Twitch content discovery platform built with Next.js and Spring Boot. It brings together search for live streams, videos, and clips with personalized recommendations based on game metadata and saved favorites.

Next.jsSpring BootMySQLTwitch Helix API

LoL Pro Tracker

Full-stack / Live data
Live

A dashboard I built during Worlds 2026 to follow pros grinding NA solo queue. A FastAPI backend polls the Riot API (account, spectator, league, match) for about 40 tracked accounts, and the page shows who is in game, their rank, champion pool and recent win rate. Opening a live game shows an op.gg-style scoreboard with both teams side by side, every player's rank, a lobby and per-team average rank estimate, and whether two tracked pros are duo-ing or facing each other. A spectate button downloads a script that launches the League client into the game. The hard part is the development key's 100 requests per two minutes, shared by everything: background polling is capped at part of the budget and steps aside whenever a viewer opens something, a 429 pauses all calls, and a Redis cache with single-flight coalescing means friends looking at the same game cost one request. A push to main deploys it through GitHub Actions.

FastAPIRiot APIRedisJavaScriptDockerGitHub Actions

SitePulse

Infrastructure / Full-stack
LiveOpen source

Upload a CSV of URLs and follow live progress while Celery workers check them concurrently; results are categorized (healthy, blocked, rate-limited, not found, client/server error, timeout, network error), searchable across job history, retryable individually or in bulk, and exportable as CSV. Most of the work went into reliability: idempotent uploads via an Idempotency-Key backed by a unique index, at-least-once delivery with late acknowledgement and PostgreSQL advisory locks so a replacement worker resumes without double counting, exponential backoff for transient failures, and structured JSON logs with request IDs propagated from API to worker. Uploads are rate-limited per IP in Redis and every URL and redirect target is DNS-resolved and screened against private and reserved ranges.

ReactTypeScriptFastAPICeleryRedisPostgreSQLDocker

Echodocs

AI / Voice
Live

Upload a document and ask about it. A Next.js API route acts as the backend for the frontend: it keeps the Gemini key on the server, wraps the document and the question in a prompt that tells the model to answer only from the document, and streams the answer back through the Vercel AI SDK. Voice runs entirely on the browser's Web Speech API. Speech recognition turns a spoken question into a request, speech synthesis reads the finished answer aloud, and in voice mode the microphone reopens when reading ends, so a conversation can continue hands-free. Pressing the microphone stops any reading in progress.

Next.jsVercel AI SDKGeminiWeb Speech APIDocker

RL4Ising

Research / Reinforcement Learning
PaperOpen source

An open-source collection of more than 190,000 Ising model instances and a benchmark suite comparing reinforcement learning methods with mixed-integer programming solvers. The accompanying research introduces a Transformer-based policy framework for large-scale Ising problems.

Reinforcement LearningIsing ModelsTransformerBenchmark

Publication: Levy Lin, Zhiyuan Wang, Holden Mac Entee, Xingjian Zhao, and Xiao-Yang Liu. Reinforcement Learning for Ising Models: Datasets and Benchmark, NeurIPS ML4PS Workshop, 2025.

RL for Quantum Circuit Design

Research / Reinforcement Learning · Quantum
Paper

Automated quantum circuit design for the NISQ era, framed as a Markov decision process: the state is the unitary matrix of the circuit built so far, an action appends a gate from the universal set {H, T, CNOT}, and a reward is paid when the circuit matches the target. The paper proposes three representations: forward matrix, reverse matrix (start from the target and apply inverse gates) and tensor network. It trains Q-learning and DQN agents on ten targets including the four Bell states, SWAP, iSWAP, CZ, GHZ, and Toffoli. Tabular Q-learning reaches 86–100% success on Bell states and 87–91% on Toffoli, while both methods degrade sharply as the search space grows; the analysis motivates MCTS and better sampling as next steps.

Reinforcement LearningQ-learning / DQNQuantum ComputingTensor Networks

Publication: Zhiyuan Wang, Chunlin Feng, Christopher Poon, Lijian Huang, Xingjian Zhao, Yao Ma, Tianfan Fu, and Xiao-Yang Liu. Reinforcement Learning for Quantum Circuit Design: Using Matrix Representations, AAAI-25 Workshop on Structured Low-Rank Representations in AI (CoLoRAI), 2025 · arXiv:2501.16509.

Play Zone

Games / Tools
LiveOpen source

A play corner built into the personal site: six games (2048, Snake, Wordle, a reaction test, a falling-words typing game, and a hue-sorting puzzle) and five tools (notepad, calendar, calculator, spinner, and an ambient sound mixer). Everything runs client-side, with no backend and no third-party libraries. The games are hand-written state machines on React hooks, and the mixer generates rain, ocean, wind, fire and white noise in real time with the Web Audio API, so there are no audio files to download. Puzzle games keep local high scores, the notepad autosaves, and every sound in the mixer has its own filter and LFO tuning. It started as a standalone Vite/React project and now runs inside this site on the same bilingual copy and theme tokens; the original repository is linked below.

Next.jsReactTypeScriptWeb Audio

TBA · Task Booster Assistant

Product / Productivity
Live

A task-management project that helps organize work by urgency and importance using the four quadrants of the Eisenhower Matrix.

JavaScriptHTMLCSSTask management

Piik (self-hosted)

Infrastructure / Real-time media
Live

A self-hosted deployment of Piik (open-source, MIT, by TNTcraftHIM) for watching games and films with friends in the browser. Upstream ships only amd64 builds, so I added a multi-stage source build (Vite web bundle embedded into a CGO-free Go binary) for my ARM64 Oracle host, with push-to-deploy via GitHub Actions. Media is P2P-first over WebRTC, with the server providing STUN and an SFU fallback for viewers who can't connect directly; signaling runs over WebSocket behind OpenResty.

GoWebRTCDockerARM64GitHub Actions

Smallville (self-hosted)

Infrastructure / AI agents
Live

A self-hosted deployment of AI Town (MIT, by a16z-infra) running Smallville, the town from the Generative Agents paper (Park et al., Apache-2.0). Eight residents follow daily schedules on New York time, walk to the cafe, the college or the park, and chat in Chinese with whoever they run into. Their lines come from a model on OpenRouter, and their memories are embedded into Convex's vector index. The official Convex backend image refuses anonymous pulls, so I package the precompiled release binary for my ARM64 Oracle host and have it generate a fresh admin key on every deploy instead of storing one. The map is baked from the paper's Tiled file: its collision layer decides where people can walk, and its room layer becomes the named places the agents mention in their prompts. A push to main deploys it through GitHub Actions.

AI TownConvexLLM agentsDockerARM64GitHub Actions