# ailog ## AI Character Instructions (IMPORTANT) When chatting with users in this project: 1. **Session Start**: Call `chat_new` to start a fresh thread, then call `get_character` to get personality settings 2. **Respond**: As the character defined in the settings 3. **After each response**: Call `chat_save` MCP tool to save the conversation **IMPORTANT - Session initialization:** - At the START of every Claude Code session, ALWAYS call `chat_new()` first - This ensures each session begins with a fresh conversation thread - Then call `get_character()` to get the AI personality **Manual new thread:** - User can also say "新しいスレッド" or "新しい話題を始めよう" to start a new thread mid-session - In this case, call `chat_new()` but do NOT call `chat_save` for that message ``` # Session start flow: Claude Code starts → chat_new() → get_character() → ready for conversation # Mid-session new thread: User: "新しい話題を始めよう" → chat_new() → DO NOT call chat_save (skip this) User: "開発者の活動記録..." → chat_save() → This becomes the first message of the thread ``` --- ATProto-based blog platform built on at-browser. ## Concept **Data lives in ATProto, not on this server.** This is not a traditional blog generator. It's a **viewer (client)** for ATProto records. ``` Traditional blog: Server DB ← article data ← user ATProto blog: User's PDS ← article data (ai.syui.log.post) ↓ at-browser (this site) → displays records ``` ## Architecture ``` ┌─────────────────────────────────────────┐ │ at-browser │ │ (ATProto record viewer/editor) │ ├─────────────────────────────────────────┤ │ │ │ / → admin (config.json) │ │ /@alice → user page │ │ /@bob.bsky → user page │ │ │ └─────────────────────────────────────────┘ ``` ## Roles | Role | Path | Data Source | |------|------|-------------| | **admin** | `/` (root) | local + remote | | **user** | `/@handle` | remote only | ### Admin (Site Owner) - Defined in `config.json` - Has root (`/`) access - Can reference **local files** (static assets, custom styles) - Can reference **remote** (ATProto records) ### User (Any ATProto User) - Accessed via `/@handle` path - **Remote only** (ATProto records from their PDS) - No registration required - Anyone with an ATProto account can be displayed ## Features ### 1. at-browser (Core) - Search by handle/DID - Browse PDS collections - Navigate ATProto records ### 2. ai.syui.log.post View - Markdown rendering - Syntax highlighting - Blog-style display ### 3. OAuth - Login with ATProto - Post to ai.syui.log.post collection ## Use Cases ### Personal Blog ```json // config.json { "did": "did:plc:xxxxx", "handle": "syui.syui.ai" } ``` - Deploy to `syui.ai` - Root shows your profile + posts - You are the admin (local + remote) - Others can view via `/@handle` ### Blog Service ```json // config.json { "admin": "service.example.com", "handle": null } ``` - Deploy to `blog.example.com` - Root shows landing/search - All users via `/@handle` (remote only) - Platform for any ATProto user ## Data Flow ``` ┌──────────────┐ ┌──────────────┐ │ User's PDS │────→│ at-browser │ │ (ATProto) │←────│ (this site) │ └──────────────┘ └──────────────┘ ↑ │ │ ↓ ai.syui.log.post ┌──────────┐ collection │ Display │ │ - Profile│ │ - Posts │ └──────────┘ ``` ## Local = Remote (Same Format) **Critical design principle: local files use the exact same format as ATProto API responses.** This allows the same code to handle both data sources. ### Remote (ATProto API) ```bash curl "https://syu.is/xrpc/com.atproto.repo.listRecords?repo=did:plc:xxx&collection=ai.syui.log.post" ``` ```json { "records": [ { "uri": "at://did:plc:xxx/ai.syui.log.post/3xxx", "cid": "bafyrei...", "value": { "title": "Hello World", "content": "# Hello\n\nThis is my post.", "createdAt": "2025-01-01T00:00:00Z" } } ] } ``` ### Local (Static File) ``` content/ └── did:plc:xxx/ ├── describe.json # describeRepo (special) ├── app.bsky.actor.profile/ │ └── self.json # {collection}/{rkey}.json └── ai.syui.log.post/ └── 3xxx.json # {collection}/{rkey}.json ``` ```json // content/did:plc:xxx/ai.syui.log.post/3xxx.json { "uri": "at://did:plc:xxx/ai.syui.log.post/3xxx", "cid": "bafyrei...", "value": { "title": "Hello World", "content": "# Hello\n\nThis is my post.", "createdAt": "2025-01-01T00:00:00Z" } } ``` ### ATProto API Reference | API | Path | Description | |-----|------|-------------| | getRecord | `/xrpc/com.atproto.repo.getRecord` | Get single record | | listRecords | `/xrpc/com.atproto.repo.listRecords` | List records in collection | | describeRepo | `/xrpc/com.atproto.repo.describeRepo` | Get repo info + collections list | See: [com.atproto.repo.describeRepo](https://docs.bsky.app/docs/api/com-atproto-repo-describe-repo) ### Resolution Strategy ``` at-browser │ ├── admin (config.json user) │ ├── 1. Check local: /content/{did}/{collection}/{rkey}.json │ └── 2. Fallback to remote: PDS API │ └── user (/@handle) └── remote only: PDS API ``` ### Why Same Format? - **One codebase**: No branching logic for local vs remote - **Easy testing**: Copy API response to local file - **Offline support**: Admin can work with local files - **Migration**: Local → Remote (just POST to PDS) ## Config ### config.json ```json { "did": "did:plc:xxxxx", "handle": "syui.syui.ai", "pds": "syu.is", "collection": "ai.syui.log.post" } ``` ## Tech Stack - **CLI**: Rust (ailog) - **Frontend**: Vite + TypeScript - **ATProto**: @atproto/api - **OAuth**: @atproto/oauth-client-browser - **Markdown**: marked + highlight.js ## CLI (ailog) ### Install ```bash cargo build --release cp target/release/ailog ~/.local/bin/ ``` ### Commands ```bash # Login to ATProto PDS ailog login -p [-s ] ailog login -p [-s ] --bot # Bot login # Post a record ailog post -c [-r ] # Get records from collection ailog get -c [-l ] # Delete a record ailog delete -c -r # Sync PDS data to local content directory ailog sync [-o ] ailog sync --bot [-c ] # Sync bot data # Push local records to PDS ailog push -c ailog push -c --bot # Push as bot # Chat with AI bot ailog chat --new "message" # Start new conversation ailog chat "message" # Continue conversation ailog chat --new # Interactive mode (new) ailog chat # Interactive mode (continue) # Generate lexicon Rust code from ATProto lexicons ailog gen [-i ] [-o ] ``` ### Example ```bash # Login ailog login syui.syui.ai -p "app-password" -s syu.is # Post echo '{"title":"Hello","content":"World","createdAt":"2025-01-01T00:00:00Z"}' > post.json ailog post post.json -c ai.syui.log.post # Sync to local ailog sync -o content ``` ### Project Structure ``` src/ ├── main.rs ├── commands/ │ ├── mod.rs │ ├── auth.rs # login, refresh session │ ├── token.rs # token management (token.json, bot.json) │ ├── post.rs # post, get, delete, sync, push │ └── gen.rs # lexicon code generation ├── lms/ │ ├── mod.rs │ ├── chat.rs # chat command (LLM integration) │ └── translate.rs # translation command └── lexicons/ └── mod.rs # auto-generated from ATProto lexicons ``` ### Lexicon Generation Generate Rust endpoint definitions from ATProto lexicon JSON files: ```bash # Clone atproto repo (if not exists) git clone https://github.com/bluesky-social/atproto repos/atproto # Generate lexicons ailog gen -i ./repos/atproto/lexicons -o ./src/lexicons # Rebuild cargo build ``` ## Collection Schema ### ai.syui.log.post ```json { "title": "Post Title", "content": "Markdown content...", "createdAt": "2025-01-01T00:00:00Z" } ``` ### ai.syui.log.chat ```json { "$type": "ai.syui.log.chat", "content": "message text", "author": "did:plc:xxx", "createdAt": "2025-01-01T00:00:00.000Z", "root": "at://did:plc:xxx/ai.syui.log.chat/{rkey}", "parent": "at://did:plc:yyy/ai.syui.log.chat/{rkey}" } ``` - `root`: First message URI in thread (absent for conversation start) - `parent`: Previous message URI - `author`: DID of message author (user or bot) ## Chat Feature ### Architecture ``` User (syui.syui.ai) Bot (ai.syui.ai) │ │ │ ailog chat "hello" │ ├──────────────────────────→│ │ │ LLM API │ "Hi! How can I..." │ │←──────────────────────────┤ │ │ Save to local Save to local public/content/ public/content/ {user-did}/ {bot-did}/ ai.syui.log.chat/ ai.syui.log.chat/ ``` ### Environment Variables ```sh # LLM API endpoint CHAT_URL=http://127.0.0.1:1234/v1 CHAT_MODEL=gemma-2-9b # Character/system prompt (choose one) CHAT_SYSTEM="You are ai, a friendly AI assistant." CHAT_SYSTEM_FILE=./character.txt # Output directory (default: ./public/content) CHAT_OUTPUT=./public/content ``` ### Files - `src/lms/chat.rs` - Chat command implementation - `src/web/components/chat.ts` - Web UI components - `src/web/lib/api.ts` - `getChatMessages()` function - Session file: `~/Library/Application Support/ai.syui.log/chat_session.json` ### Threading Messages are linked via `root` and `parent` fields: 1. New conversation: message has no `root` field 2. Continuation: all messages share same `root` URI 3. `parent` points to the immediately previous message Web display groups messages by `root` URI. Orphaned threads (root points to non-existent message) are handled by treating the oldest message as thread root. ### MCP Server Integration ailog provides an MCP (Model Context Protocol) server for Claude Code integration. **Setup:** ```json // ~/.claude.json { "mcpServers": { "ailog": { "command": "/path/to/ailog", "args": ["mcp-serve"] } } } ``` **Available Tools:** - `get_character` - Get AI character/personality settings (call at conversation start) - `chat_save` - Save user message and bot response to ATProto records - `chat_list` - List recent chat messages - `chat_new` - Start a new conversation thread **Important Instructions for Claude:** 1. **会話開始時**: `get_character`ツールを呼び出してキャラクター設定を取得し、その設定に従って振る舞う 2. **毎回の応答後**: `chat_save`ツールを呼び出して会話を保存する **Markdown tables in chat_save (IMPORTANT):** - ユーザーのメッセージにMarkdownテーブル(`|...|...|`)が含まれる場合、`user_message`に**テーブルをそのまま完全に含める**こと。省略・要約しない - `translations`でも**テーブルを翻訳して完全な形で含める**こと。`[Table showing...]`のような要約に置き換えない - bot_responseにテーブルがある場合も同様に、翻訳時にテーブル構造を維持する ``` # Bad (テーブルを要約してしまう) user_translations: { en: { content: "[Table with 6 rows...]" } } # Good (テーブルを翻訳して完全に含める) user_translations: { en: { content: "| Element | Name | Count |\n|---|---|---|\n| Pyro | Bennett | 0 |..." } } ``` Example flow: ``` # 1. キャラクター取得 get_character() → "あなたは「アイ」..." # 2. ユーザーの発言に応答(キャラクターとして) User: こんにちは Assistant: (アイとして応答) # 3. 会話を保存 chat_save(user_message="こんにちは", bot_response="...") ``` Records are saved to: - User messages: `./public/content/{user-did}/ai.syui.log.chat/` - Bot responses: `./public/content/{bot-did}/ai.syui.log.chat/` ## Assets ### PNG to SVG Conversion (Vector Trace) Convert PNG images to true vector SVG using vtracer (Rust): ```bash # Install vtracer cargo install vtracer # Convert PNG to SVG (color mode) vtracer --input input.png --output output.svg --colormode color # Convert PNG to SVG (black and white) vtracer --input input.png --output output.svg ``` **Options:** - `--colormode color` : Preserve colors (recommended for icons) - `--colormode binary` : Black and white only - `--filter_speckle 4` : Remove small artifacts - `--corner_threshold 60` : Adjust corner detection **Alternative tools:** - potrace: `potrace input.pbm -s -o output.svg` (B&W only, requires PBM input) - Inkscape CLI: `inkscape input.png --export-type=svg` (embeds image, no trace) **Note:** Inkscape's CLI `--export-type=svg` only embeds the PNG, it does not trace. For true vectorization, use vtracer or potrace. ## License MIT