add content.md
This commit is contained in:
519
content.md
Normal file
519
content.md
Normal file
@@ -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 <handle> -p <password> [-s <server>]
|
||||
ailog login <handle> -p <password> [-s <server>] --bot # Bot login
|
||||
|
||||
# Post a record
|
||||
ailog post <file.json> -c <collection> [-r <rkey>]
|
||||
|
||||
# Get records from collection
|
||||
ailog get -c <collection> [-l <limit>]
|
||||
|
||||
# Delete a record
|
||||
ailog delete -c <collection> -r <rkey>
|
||||
|
||||
# Sync PDS data to local content directory
|
||||
ailog sync [-o <output>]
|
||||
ailog sync --bot [-c <collection>] # Sync bot data
|
||||
|
||||
# Push local records to PDS
|
||||
ailog push -c <collection>
|
||||
ailog push -c <collection> --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 <input>] [-o <output>]
|
||||
```
|
||||
|
||||
### 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
|
||||
Reference in New Issue
Block a user