137 lines
6.2 KiB
Markdown
137 lines
6.2 KiB
Markdown
<p align="center">
|
|
<img src="piti-icon.png" alt="tsjetpiti icon" width="132">
|
|
</p>
|
|
|
|
<p align="center">
|
|
<img src="piti-logo.png" alt="tsjetpiti" width="360">
|
|
</p>
|
|
|
|
<p align="center">
|
|
<strong>Tiny app. Big model. Absolutely no idea what it's talking about.</strong>
|
|
</p>
|
|
|
|
<p align="center">
|
|
<sub>ANDROID 11+ • ARM64 • FULLY ON-DEVICE • CONFIDENTLY WRONG</sub>
|
|
</p>
|
|
|
|
---
|
|
|
|
## Meet tsjet
|
|
|
|
**tsjetpiti** is a dead-simple Android chat app running an uncensored
|
|
**Qwen3.5-2B** model entirely on your phone through embedded
|
|
[llama.cpp](https://github.com/ggml-org/llama.cpp). No cloud inference, no
|
|
account, and no conversation leaving the device.
|
|
|
|
tsjet has one special talent: it **always answers**, sounds completely
|
|
confident, and is **hilariously, deliberately wrong**. Hit **new tsjet** whenever
|
|
you want to erase the evidence and start fresh.
|
|
|
|
---
|
|
|
|
## How it works
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ MainActivity (Kotlin) │
|
|
│ • full-screen WebView ── UI ──────────┼──> assets/web/{index.html,app.js,styles.css}
|
|
│ • JS bridge "TsjetNative" │ (the whole chat UI, ~200 lines)
|
|
│ • model download + prompt formatting │
|
|
└──────────────┬──────────────────────────┘
|
|
│ JNI (LlamaBridge)
|
|
▼
|
|
┌─────────────────────────────────────────┐
|
|
│ cpp/llama-jni.cpp → libtsjet.so │
|
|
│ talks to llama.cpp C API (pinned) │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
- **Inference:** llama.cpp compiled from source (pinned tag `b10333`) through the
|
|
NDK. Pulled automatically at build time via CMake `FetchContent` — no submodule
|
|
to init.
|
|
- **Model:**
|
|
[`Qwen3.5-2B-Uncensored-HauhauCS-Aggressive`](https://huggingface.co/HauhauCS/Qwen3.5-2B-Uncensored-HauhauCS-Aggressive)
|
|
in Q6_K format (~1.5 GB) is **downloaded on first launch** from Hugging Face
|
|
into the app's private storage. It is *not* bundled in the APK. (Swap the
|
|
quant in `ModelDownloader.kt`.)
|
|
- **Persona:** set via `SYSTEM_PROMPT` in `MainActivity.kt`; sampling is a little
|
|
hot (temp 0.9) for playful answers. The model is a Qwen3 "thinking" model, so the
|
|
prompt disables reasoning (empty `<think></think>` prefill + `/no_think`) to keep
|
|
replies fast and punchy instead of burning the token budget on hidden thoughts.
|
|
- **Conversation:** the web layer holds the full history and sends it each turn;
|
|
native rebuilds the ChatML prompt and clears the KV cache before every reply, so
|
|
"new tsjet" is just: clear JS state + reset cache.
|
|
|
|
## Requirements
|
|
|
|
- **A build machine** with the Android SDK + NDK. Easiest path: open the project
|
|
in **Android Studio** (it will offer to install the matching NDK `27.2.12479018`
|
|
and CMake `3.22.1`).
|
|
- **A phone:** 64-bit ARM (`arm64-v8a`), Android 11+ (minSdk 30), and enough free
|
|
RAM to hold a ~1.5 GB Q6 model (~4 GB RAM device recommended).
|
|
- Network on first launch to download the model.
|
|
|
|
## Build
|
|
|
|
```bash
|
|
./gradlew assembleDebug
|
|
```
|
|
|
|
The APK lands in `app/build/outputs/apk/debug/`. Or just Run ▶ from Android Studio.
|
|
|
|
> First build compiles llama.cpp from source, so it takes a while and needs
|
|
> network (CMake fetches the pinned llama.cpp).
|
|
|
|
## Install (sideload)
|
|
|
|
A prebuilt `app-debug.apk` is attached to every [Release](../../releases).
|
|
|
|
The debug APK is signed with the debug key, so you can sideload it directly:
|
|
copy `app-debug.apk` to the phone, enable "install unknown apps" for your file
|
|
manager, and tap it. First launch downloads the ~1.5 GB model over Wi-Fi.
|
|
|
|
## Where things live
|
|
|
|
| What | Where |
|
|
|------|-------|
|
|
| Chat UI (HTML/CSS/JS) | `app/src/main/assets/web/` |
|
|
| Android glue + model download | `app/src/main/java/monster/autisme/tsjetpiti/` |
|
|
| Native llama.cpp bridge | `app/src/main/cpp/llama-jni.cpp` |
|
|
| Pinned llama.cpp version | `app/src/main/cpp/CMakeLists.txt` (`GIT_TAG b10333`) |
|
|
| Model URL / filename | `ModelDownloader.kt` |
|
|
| Generation params (ctx, temp, tokens) | `MainActivity.kt` + `llama-jni.cpp` |
|
|
|
|
## Credits
|
|
|
|
tsjetpiti is small because it stands on the shoulders of some decidedly
|
|
not-small projects:
|
|
|
|
- [Qwen3.5-2B](https://huggingface.co/Qwen/Qwen3.5-2B) by the Qwen team is the
|
|
upstream base model.
|
|
- [Qwen3.5-2B-Uncensored-HauhauCS-Aggressive](https://huggingface.co/HauhauCS/Qwen3.5-2B-Uncensored-HauhauCS-Aggressive)
|
|
by [HauhauCS](https://huggingface.co/HauhauCS) is the model variant and GGUF
|
|
quantization used by the app.
|
|
- [llama.cpp](https://github.com/ggml-org/llama.cpp) by the ggml-org community
|
|
provides the on-device inference engine and GGUF runtime.
|
|
- [Android](https://developer.android.com/) and
|
|
[Kotlin](https://kotlinlang.org/) provide the application platform and native
|
|
app layer.
|
|
- [Gradle](https://gradle.org/) and [CMake](https://cmake.org/) power the Kotlin
|
|
and C++ build.
|
|
|
|
## TODO / notes
|
|
|
|
- **Branding:** launcher icon (adaptive + legacy densities) is generated from
|
|
`piti-icon.png` via `python3 tools/gen_launcher_icons.py`; the header wordmark is
|
|
`piti-logo.png` (copied to `assets/web/logo.png`). Source art lives at the repo root.
|
|
- The model may emit `<think>…</think>` blocks; the UI strips them from the
|
|
display and from history (`stripThink` in `app.js`).
|
|
- Bumping the llama.cpp tag? Re-check the C API calls in `llama-jni.cpp` against
|
|
that tag's `include/llama.h` — it uses the raw C API directly.
|
|
- **Native is always built optimized.** AGP compiles the debug variant's C/C++ at
|
|
`-O0` by default, which makes llama.cpp ~10x too slow; `CMakeLists.txt` forces
|
|
`Release`/`-O3` regardless of variant. Native libs are also 16 KB page-aligned.
|
|
- **Verified on-device** (Pixel 7, Android, 4 KB pages): downloads the model, loads
|
|
it (`n_ctx=4096`, 6 threads), streams a reply, and "new tsjet" clears the chat.
|
|
`./gradlew assembleDebug` → ~13 MB `arm64-v8a` APK (model downloads on first launch).
|