diff --git a/content.md b/content.md new file mode 100644 index 0000000..8437e7a --- /dev/null +++ b/content.md @@ -0,0 +1,519 @@ +# 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