Model or dataset
qianye60/XianTu avatar
qianye60/XianTu

XianTu ships as a static webpack bundle and a separate optional Python API

"Immortal Path" AI-driven immersive cultivation text adventure game, based on Vue 3 + TypeScript + Fastapi, supports multiple AI models such as Gemini/Claude/OpenAI

386 stars73 forksVueNOASSERTION

At a glance

What is it?
XianTu is an AI-driven Chinese cultivation text adventure built with Vue 3, TypeScript and webpack on the front and FastAPI on the back, with SillyTavern compatibility and several model providers behind it. Two facts to check before you plan around it: the recommended Docker command runs nginx with no backend at all, and the package manifest declares Apache-2.0 while the README says personal use only.
Who is it for?
XianTu suits someone who wants a finished text-adventure system with cultivation mechanics and an adjudication layer, and who is happy to bring their own model key. It does not suit anyone who needs a clear answer on licensing, because the README and the manifest disagree and the repository reports no recognised licence identifier.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Vue, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The one-command Docker path has no backend behind it

The recommended install is a single line:

bash
docker run -d -p 8080:80 qianye60/xiantu:latest

That gets you something on port 8080, but it is worth knowing what. The Dockerfile has two stages. The first builds the front end on node:18-alpine with npm ci and npm run build. The second is nginx:alpine, which copies an nginx configuration and copies the built dist directory into the web root, exposes port 80 and runs nginx in the foreground.

There is no Python in the final image. No FastAPI, no database, no uvicorn.

The backend is documented separately and explicitly as optional. It provides the account and save APIs, defaults to SQLite so it works without configuration, and runs on its own port:

bash
pip install -r server/requirements.txt
uvicorn server.main:app --reload --port 12345

Environment variables for it live in a separate example file under the server directory. So there are two deployments here, not one: the image for a read-and-play instance, and the API for anything that persists an account or a save.

That matters because the feature list advertises multiple characters, multiple save slots, import and export, and cloud sync. The last of those needs the backend, so a Docker-only install will not deliver it.

The package version is an empty string

The manifest declares no version at all:

json
"name": "XianTu",
"version": "",

The version field is an empty string rather than a placeholder like 0.0.0 or a maintained 1.0.0.

What that costs is worth spelling out. Anything that reads the version from the package to display it or to decide whether an update is needed gets nothing. There is no version in the user interface sourced from here, and no build artifact carries one either, because the release version lives in the git tag rather than in the manifest.

That is consistent with how releases actually work here, since the tag is what drives the pipeline. It also means there is no in-band version to fall back on when someone is running an old copy and cannot tell which one.

The rest of the manifest is more conventional: private is set, type is module, and there is a homepage pointing at the hosted demo rather than at the repository.

One other line is worth a second look for anyone planning to embed this. There is a build:single script that passes an environment flag to webpack, and the development dependencies include plugins for inlining CSS and inlining scripts into HTML. That combination is how a project produces one shareable HTML file, which is the form SillyTavern embedding needs.

The README says personal use; the manifest says Apache-2.0

Two documents in the same repository say different things about who may use this code.

The licence section of the README states that the project is free for personal study and research, and that commercial use requires contacting the author first. It names a contact route rather than granting a licence.

The package manifest declares the licence as Apache-2.0. The repository also carries a LICENSE file and a NOTICE file.

GitHub reports no recognised licence identifier for the repository, which usually means it could not classify the file rather than that no licence exists. That is a third data point and it does not settle the question.

For an open-source project this matters more than the wording suggests. Apache-2.0 is permissive and would let you ship this inside a commercial product. A personal-use-only statement does not. The README is the more restrictive of the two documents, so the safe reading is that the permissive declaration is a default in the manifest rather than a grant, and that anything beyond personal study needs the author to say yes.

There is a related detail in the description field, which is in Chinese and names SillyTavern and a custom API. Combined with the licence mismatch, the metadata here looks maintained by convention rather than by policy, so treat it as a question to raise with the maintainer rather than something to assume.

Webpack, an inliner, and two ESLint configurations

The build is webpack rather than Vite, which is visible in every script:

bash
# 安装依赖
npm install

# 开发模式
npm run serve

# 生产构建
npm run build

There is a separate watch script, a lint script that runs with --fix, a lint:check script without it, and a type-check script that is a plain tsc with no emit.

Two ESLint configurations sit in the repository root at once, one as .eslintrc.js and one as eslint.config.ts. Those are the legacy and flat-config formats respectively, and a project carrying both during a migration usually does so briefly. What it means in practice is that a lint run may be picking up whichever one the ESLint version resolves, so behaviour can differ from what the file suggests.

The dependency list carries two things that stand out in a Vue 3 project. jQuery is a runtime dependency rather than a dev dependency, and lodash appears among the development dependencies with its own type packages. Both are load-bearing assumptions from earlier code rather than additions.

The graphics stack is Chart.js plus Pixi.js, with the Pixi major version held at 7. Local persistence uses idb, the IndexedDB wrapper, and the component libraries are Pinia for state, Vue Router for routing and Tailwind merge with class-variance-authority for styling. Vue itself is at 3.5.

There is also a find-duplicates.cjs script at the repository root, which is not something most projects ship and suggests the asset bundle has grown enough for duplicate detection to be a routine task.

A git tag is the entire release

There is no changelog step and no version bump step. You push a tag and the pipeline does the work:

bash
git tag v3.7.0
git push origin v3.7.0

A tag matching the v-prefixed pattern triggers two outputs. The Docker image is built and pushed to Docker Hub, which is what makes the one-line install work. And a GitHub Release is created with the build artifact uploaded as a zip.

Two other workflows run alongside. The CI workflow fires on pushes and pull requests and runs type checking plus a build, which means a type error blocks the tag pipeline as well as the pull request. The Pages workflow deploys to GitHub Pages when master receives a push, which is where the project page and the game introduction are served.

That example tag is worth noticing. The README shows v3.7.0 while the current releases are v5.1.0, v5.1.1 and v5.1.2. The documentation has not been updated to match where the project actually is, which is a small signal about how much attention the prose gets compared to the code.

The release cadence itself is fast. v5.1.0 landed on 2026-09-30 with image generation channels and an image gallery, and v5.1.1 and v5.1.2 followed the next day.

Everything below the tag machinery is conventional: a CHANGELOG.md for the full history, CONTRIBUTING.md, a code of conduct, and an editor configuration.

SillyTavern compatibility is the part with no equivalent

The compatibility claim is specific: the game supports a SillyTavern embedded environment as well as a standalone web version.

That is the reason for the single-file build and for the self-contained static bundle. A SillyTavern installation embeds a page rather than hosting it, so the game has to arrive as something you can drop in without a server behind it. A separate Docker deployment serving nginx on port 80 would not work for that case, which is why the static path is the primary one and the API is the add-on.

The model side is provider-agnostic. Gemini, Claude, OpenAI and DeepSeek are all named, alongside SillyTavern itself as an interface. The backend, when you run it, adds JWT authentication and a WebSocket, which is what makes an interactive text game with a live adjudication system possible over the network rather than a single request and response.

Storage is SQLite by default with PostgreSQL as the alternative, and the Python side uses Tortoise ORM with Aerich managing migrations. The pyproject file configures Aerich to point at the database module, with migrations living under the server directory.

One detail about the two ends of the project. The repository root contains both English and Chinese filenames, including the game introduction page and a friends page, and it carries robots.txt and a sitemap.xml. That is a project that intends to be found by search engines, which is a choice worth noting for something distributed as a single HTML file.

Saves live in IndexedDB until you add the API

The save story splits across the two halves of the project, and the split is worth being explicit about.

On the client side the feature list promises multiple characters, multiple save slots, and import plus export. Local persistence uses IndexedDB through the idb wrapper, which means a save exists in the browser profile of the machine that made it. Nothing about that survives a different browser, a different machine, or clearing site data.

That is also why cloud sync appears alongside the local saves in the same feature bullet. Cloud sync is not a client-side capability; it needs the optional FastAPI backend, with its account APIs, JWT authentication and a database behind it. So the honest description is that the static build gives you local saves and export, and the backend gives you accounts and sync.

The optional backend's environment file is under the server directory rather than at the root, which is a small signal that it is maintained as a separate concern rather than as the primary deployment.

The rest of the client is a fairly conventional Vue application: Pinia for state, Vue Router for navigation, Chart.js for the statistics-style views and Pixi.js for anything canvas-drawn, with light and dark themes.

For the open world itself, the feature list describes free exploration of a named continent, random encounter events, and a character relationship network that builds up over play.

Against running SillyTavern yourself

The realistic alternative is the host application. SillyTavern is an open-source interface for language models with a large extension ecosystem, character cards and personas, and it is where a lot of this kind of roleplay actually lives.

The difference in approach is vertical versus general. SillyTavern gives you a character and a conversation and whatever the community has built on top. XianTu gives you a finished system instead: cultivation-tier breakthroughs, a set of techniques to learn, equipment refining, NPC interaction, and an adjudication layer that computes outcomes from tier, attributes, gear and techniques rather than leaving it to the model's judgement.

That adjudication system is the substantive difference. In a general interface the model narrates whatever happens. Here something is being calculated, which is what makes a progression game feel like a game rather than a story you are reading.

The cost is the flip side of verticality. You get one game rather than a platform, so there is no plugin marketplace to draw from and no way to add a second system without forking. You also inherit a specific aesthetic and a specific content domain, which is either exactly what you wanted or not.

Practically, XianTu runs against several providers including SillyTavern itself, so you are not locked out of the general tool; you are choosing to add a game layer on top of the model interface.

Editorial conclusion

XianTu suits someone who wants a finished text-adventure system with cultivation mechanics and an adjudication layer, and who is happy to bring their own model key. It does not suit anyone who needs a clear answer on licensing, because the README and the manifest disagree and the repository reports no recognised licence identifier. Verify two things first: what your deployment actually needs, since the one-command Docker path serves static files with no account or save API behind it, and whether the licence terms permit you, since the stated position is free for personal study and research with commercial use requiring the author's agreement. The current release is v5.1.2 and the last push was on 2026-10-01.

Frequently asked questions

How do I run XianTu?

With one command, docker run -d -p 8080:80 qianye60/xiantu:latest, then open http://localhost:8080. For local development use npm install followed by npm run serve, and npm run build for a production bundle. The Python backend is optional and runs separately on port 12345.

Does the Docker image of XianTu include a backend?

No. The final image is nginx:alpine serving the built front-end files, with no Python and no database. The FastAPI backend that provides account and save APIs is installed separately with pip and run with uvicorn, defaulting to SQLite.

Which AI models does XianTu support?

Gemini, Claude, OpenAI and DeepSeek are named, alongside SillyTavern as an interface. The design also includes a standalone web version, and it supports a SillyTavern embedded environment.

What licence is XianTu under?

The documents conflict. The README states the project is free for personal study and research and that commercial use requires contacting the author first, while the package manifest declares Apache-2.0. GitHub reports no recognised licence identifier for the repository, so treat the README's more restrictive terms as the ones to plan against.

How are XianTu releases published?

By pushing a tag matching the v-prefixed pattern. That triggers a Docker image build and push to Docker Hub plus a GitHub Release with the build artifact as a zip. A separate CI workflow runs type checking and a build on pushes and pull requests, and pushes to master deploy to GitHub Pages.

Official sources

  1. Issues
  2. Project website
  3. qianye60/XianTu on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/qianye60-xiantu.svg)](https://hysenlabs.com/projects/qianye60-xiantu)