├── .env.example
├── .gitignore
├── LICENSE
├── README.ja.md
├── README.md
├── app.py
├── docs
├── CLAUDE.md
├── architecture.md
├── assets
│ └── avatar-ui_demo.gif
├── spec.md
└── todo.md
├── requirements.txt
├── settings.py
├── static
├── css
│ └── style.css
├── images
│ ├── idle.png
│ └── talk.png
└── js
│ ├── animation.js
│ ├── app.js
│ ├── chat.js
│ ├── settings.js
│ └── sound.js
└── templates
└── index.html
/.env.example:
--------------------------------------------------------------------------------
1 | # ===========================================
2 | # 必須設定(これらがないと起動しません)
3 | # ===========================================
4 |
5 | # Google AI Studio APIキー
6 | # 取得先: https://aistudio.google.com/app/apikey
7 | GEMINI_API_KEY=your_api_key_here
8 |
9 | # 使用するGeminiモデル
10 | MODEL_NAME=gemini-2.0-flash
11 |
12 | # ===========================================
13 | # 任意設定(デフォルト値あり)
14 | # ===========================================
15 |
16 | # ===== サーバー設定 =====
17 | # サーバーポート番号
18 | SERVER_PORT=5000
19 |
20 | # デバッグモード(開発時: True, 本番: False)
21 | DEBUG_MODE=True
22 |
23 | # ===== アバター設定 =====
24 | # AIアシスタントの名前
25 | AVATAR_NAME=Spectra
26 |
27 | # AIアシスタントのフルネーム
28 | AVATAR_FULL_NAME=Spectra Communicator
29 |
30 | # アバター画像ファイル名(static/images/内のファイル)
31 | AVATAR_IMAGE_IDLE=idle.png
32 | AVATAR_IMAGE_TALK=talk.png
33 |
34 | # ===== AIシステムプロンプト設定 =====
35 | # AIの人格や応答スタイル
36 | SYSTEM_INSTRUCTION=あなたはSpectraというAIアシスタントです。技術的で直接的なスタイルで簡潔に応答してください。回答は短く要点を押さえたものにしてください。
37 |
38 | # ===== UI設定 =====
39 | # タイプライター効果の文字表示速度(ミリ秒)
40 | # 小さいほど高速
41 | TYPEWRITER_DELAY_MS=50
42 |
43 | # 口パクアニメーションの切替間隔(ミリ秒)
44 | MOUTH_ANIMATION_INTERVAL_MS=150
45 |
46 | # ===== サウンド設定 =====
47 | # タイピング音の周波数(Hz)
48 | BEEP_FREQUENCY_HZ=800
49 |
50 | # タイピング音の長さ(ミリ秒)
51 | BEEP_DURATION_MS=50
52 |
53 | # タイピング音の音量(0.0-1.0)
54 | BEEP_VOLUME=0.05
55 |
56 | # タイピング音終了時の音量(フェードアウト用)
57 | BEEP_VOLUME_END=0.01
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Python
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | venv/
6 | env/
7 | .env
8 |
9 | # IDE
10 | .vscode/
11 | .idea/
12 |
13 | # OS
14 | .DS_Store
15 | Thumbs.db
16 |
17 | # Temporary files
18 | *.tmp
19 | *.swp
20 | *~
21 | .env.backup
22 |
23 | # Project specific
24 | .claude/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Sito Sikino
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.ja.md:
--------------------------------------------------------------------------------
1 | # Avatar UI Core
2 |
3 | クラシックなターミナル調のUIコア。チャットUIからCLI統合まで拡張可能なプロジェクト基盤を提供します。
4 |
5 | 
6 | 
7 | 
8 |
9 | 
10 |
11 | ## 特徴
12 |
13 | - **ターミナルUI** - グリーンオンブラックの古典的ターミナルインターフェース
14 | - **AIアバター** - 発話同期型のピクセルアートアバター表示
15 | - **タイプライター効果** - 文字単位のリアルタイム表示アニメーション
16 | - **サウンドエフェクト** - Web Audio APIによるタイピング音生成
17 | - **完全な設定管理** - すべての動作パラメータを`.env`ファイルで一元管理
18 |
19 | ## 基本操作
20 |
21 | 1. **メッセージ送信**: 画面下部の入力欄にテキストを入力してEnterキー
22 | 2. **会話履歴**: 自動的にスクロールされる会話履歴を確認
23 | 3. **アバター**: AIの応答中はアバターが応答アニメーション
24 |
25 | ## クイックスタート
26 |
27 | ### 必要要件
28 |
29 | - Python 3.8以上
30 | - Google AI Studio APIキー([取得はこちら](https://aistudio.google.com/app/apikey))
31 |
32 | ### インストール手順
33 |
34 | #### 1. プロジェクトの取得
35 |
36 | ```bash
37 | # リポジトリのクローン(またはZIPダウンロード後に解凍)
38 | git clone https://github.com/yourusername/avatar-ui-core.git
39 | cd avatar-ui-core
40 | ```
41 |
42 | #### 2. Python仮想環境の作成
43 |
44 | 仮想環境を使用することで、システムのPython環境を汚さずにプロジェクトを実行できます。
45 |
46 | ```bash
47 | # 仮想環境の作成
48 | python -m venv venv
49 |
50 | # 仮想環境の有効化
51 | # Linux/Mac:
52 | source venv/bin/activate
53 | # Windows (コマンドプロンプト):
54 | venv\Scripts\activate
55 | # Windows (PowerShell):
56 | venv\Scripts\Activate.ps1
57 | ```
58 |
59 | 仮想環境が有効化されると、ターミナルのプロンプトに`(venv)`が表示されます。
60 |
61 | #### 3. 必要なパッケージのインストール
62 |
63 | ```bash
64 | # requirements.txtに記載されたパッケージを一括インストール
65 | pip install -r requirements.txt
66 | ```
67 |
68 | ### 設定
69 |
70 | #### 1. 環境変数ファイルの準備
71 |
72 | ```bash
73 | # テンプレートファイルをコピーして.envファイルを作成
74 | cp .env.example .env
75 | # Windows: copy .env.example .env
76 | ```
77 |
78 | #### 2. APIキーの設定
79 |
80 | テキストエディタで`.env`ファイルを開き、必須項目を設定:
81 |
82 | ```bash
83 | # 必須項目のみ変更が必要(他の項目はデフォルト値で動作)
84 | GEMINI_API_KEY=ここに取得したAPIキーを貼り付け
85 | MODEL_NAME=gemini-2.0-flash # または gemini-2.5-pro など
86 | ```
87 |
88 | **重要**: `.env`ファイルには機密情報が含まれるため、絶対にGitにコミットしないでください。
89 |
90 | ### 起動
91 |
92 | ```bash
93 | # アプリケーションの起動
94 | python app.py
95 | ```
96 |
97 | 起動に成功すると以下のようなメッセージが表示されます:
98 | ```
99 | * Running on http://127.0.0.1:5000
100 | ```
101 |
102 | ブラウザで `http://localhost:5000` にアクセスしてください。
103 |
104 | ## プロジェクト構造
105 |
106 | ```
107 | avatar-ui-core/
108 | ├── app.py # Flaskアプリケーション本体
109 | ├── settings.py # 設定管理モジュール
110 | ├── requirements.txt # Python依存関係
111 | ├── .env.example # 環境変数テンプレート
112 | ├── static/
113 | │ ├── css/
114 | │ │ └── style.css # UIスタイル定義
115 | │ ├── js/
116 | │ │ ├── app.js # メインエントリーポイント
117 | │ │ ├── chat.js # チャット機能
118 | │ │ ├── animation.js # アニメーション制御
119 | │ │ ├── sound.js # 音響効果
120 | │ │ └── settings.js # フロントエンド設定
121 | │ └── images/
122 | │ ├── idle.png # アバター(静止)
123 | │ └── talk.png # アバター(発話)
124 | └── templates/
125 | └── index.html # HTMLテンプレート
126 | ```
127 |
128 | **注意**: `docs/`フォルダには開発時のメモやアセットが含まれており、アプリケーション動作には影響しません。
129 |
130 | ## カスタマイズ方法
131 |
132 | すべての設定は`.env`ファイルで調整できます。
133 |
134 | ### 1. アバターの変更
135 |
136 | 画像ファイルを差し替える
137 | - `static/images/idle.png`: 静止時のアバター(推奨: 140x140px)
138 | - `static/images/talk.png`: 発話時のアバター(推奨: 140x140px)
139 |
140 | ### 2. AIの人格設定
141 | `.env`ファイルで以下の項目を編集:
142 | ```bash
143 | AVATAR_NAME=Spectra
144 | AVATAR_FULL_NAME=Spectra Communicator
145 | SYSTEM_INSTRUCTION=あなたはSpectraというAIアシスタントです。技術的で直接的なスタイルで簡潔に応答してください。回答は短く要点を押さえたものにしてください。
146 | ```
147 |
148 | ### 3. UI動作の調整
149 | `.env`ファイルで各種速度を調整:
150 | ```bash
151 | # タイピング速度(ミリ秒、小さいほど高速)
152 | TYPEWRITER_DELAY_MS=30
153 |
154 | # 口パクアニメーション間隔(ミリ秒)
155 | MOUTH_ANIMATION_INTERVAL_MS=100
156 | ```
157 |
158 | ### 4. サウンド設定
159 | `.env`ファイルで音響効果をカスタマイズ:
160 | ```bash
161 | BEEP_FREQUENCY_HZ=600 # 音の高さ(Hz)
162 | BEEP_VOLUME=0.1 # 音量(0.0-1.0)
163 | BEEP_DURATION_MS=30 # 音の長さ(ミリ秒)
164 | ```
165 |
166 | **注意**: 設定変更後はアプリケーションの再起動が必要です。
167 |
168 | ## 環境変数一覧
169 |
170 | | 変数名 | 説明 | デフォルト値 | 必須 |
171 | |--------|------|-------------|------|
172 | | `GEMINI_API_KEY` | Google Gemini APIキー | - | ✅ |
173 | | `MODEL_NAME` | 使用するGeminiモデル | gemini-2.0-flash | ✅ |
174 | | **サーバー設定** | | | |
175 | | `SERVER_PORT` | サーバーポート番号 | 5000 | |
176 | | `DEBUG_MODE` | デバッグモード有効化 | True | |
177 | | **アバター設定** | | | |
178 | | `AVATAR_NAME` | AIアシスタントの名前 | Spectra | |
179 | | `AVATAR_FULL_NAME` | AIアシスタントのフルネーム | Spectra Communicator | |
180 | | `AVATAR_IMAGE_IDLE` | 静止時のアバター画像 | idle.png | |
181 | | `AVATAR_IMAGE_TALK` | 発話時のアバター画像 | talk.png | |
182 | | **AI性格設定** | | | |
183 | | `SYSTEM_INSTRUCTION` | AIの人格や応答スタイル | 技術的で簡潔な応答 | |
184 | | **UI設定** | | | |
185 | | `TYPEWRITER_DELAY_MS` | タイプライター効果の速度(ミリ秒) | 50 | |
186 | | `MOUTH_ANIMATION_INTERVAL_MS` | 口パクアニメーション間隔(ミリ秒) | 150 | |
187 | | **サウンド設定** | | | |
188 | | `BEEP_FREQUENCY_HZ` | ビープ音の周波数(Hz) | 800 | |
189 | | `BEEP_DURATION_MS` | ビープ音の長さ(ミリ秒) | 50 | |
190 | | `BEEP_VOLUME` | ビープ音の音量(0.0-1.0) | 0.05 | |
191 | | `BEEP_VOLUME_END` | ビープ音終了時の音量 | 0.01 | |
192 |
193 | ## 技術スタック
194 |
195 | ### バックエンド
196 | - **Flask 3.0.0** - Webアプリケーションフレームワーク
197 | - **google-generativeai 0.8.3** - Gemini API統合
198 | - **python-dotenv 1.0.0** - 環境変数管理
199 |
200 | ### フロントエンド
201 | - **ES6 Modules** - モジュール化されたJavaScript
202 | - **Web Audio API** - ブラウザネイティブ音響生成
203 | - **CSS3** - モダンなスタイリング
204 | - **Fira Code** - プログラミング用等幅フォント
205 |
206 | ## ⚠️ 注意事項
207 |
208 | - 本プロジェクトはネイキッド版UI基盤として提供されており、デフォルト実装は単一ユーザー利用を前提としています。
209 | - APIキーなどの秘密情報は `.env` に保存され、サーバー内でのみ利用されます。
210 | - 個人利用・学習用途ではそのままご利用いただけますが、不特定多数に公開する場合は
211 | - ユーザーごとの設定保存
212 | - 認証機構の追加
213 |
214 | が必須となります。
215 |
216 | ## ライセンス
217 |
218 | MIT License - 詳細は[LICENSE](LICENSE)ファイルを参照
219 |
220 | ## クレジット
221 |
222 | Developed by Sito Sikino
223 |
224 | ### 使用技術
225 | - Google Gemini API
226 | - Flask Framework
227 | - Fira Code Font
228 |
229 | ---
230 |
231 | **注意**: このプロジェクトはエンタメ・創作目的で作成されています。本番環境での使用時は適切なセキュリティ対策を実施してください。
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Avatar UI Core
2 |
3 |
4 |
5 | **[📖 日本語版はこちら](README.ja.md)**
6 |
7 |
8 |
9 | A classic terminal-style UI core. Provides an extensible project foundation from chat UI to CLI integration.
10 |
11 | 
12 | 
13 | 
14 |
15 | 
16 |
17 | ## Features
18 |
19 | - **Terminal UI** - Classic green-on-black terminal interface
20 | - **AI Avatar** - Pixel art avatar with synchronized speech animation
21 | - **Typewriter Effect** - Real-time character-by-character display animation
22 | - **Sound Effects** - Typing sound generation using Web Audio API
23 | - **Complete Configuration Management** - All parameters managed via `.env` file
24 |
25 | ## Quick Start
26 |
27 | ### Requirements
28 |
29 | - Python 3.8 or higher (Check version: `python --version` or `python3 --version`)
30 | - pip (Python package manager, usually included with Python)
31 | - Google AI Studio API Key ([Get it here](https://aistudio.google.com/app/apikey))
32 |
33 | ### Installation
34 |
35 | #### 1. Clone the Repository
36 |
37 | ```bash
38 | # Clone repository (or download and extract ZIP)
39 | git clone https://github.com/yourusername/avatar-ui-core.git
40 | cd avatar-ui-core
41 | ```
42 |
43 | #### 2. Create Python Virtual Environment
44 |
45 | Using a virtual environment keeps your system Python clean.
46 |
47 | ```bash
48 | # Create virtual environment
49 | python -m venv venv
50 |
51 | # Activate virtual environment
52 | # Linux/Mac:
53 | source venv/bin/activate
54 | # Windows (Command Prompt):
55 | venv\Scripts\activate
56 | # Windows (PowerShell):
57 | venv\Scripts\Activate.ps1
58 | ```
59 |
60 | When activated, you'll see `(venv)` in your terminal prompt.
61 |
62 | #### 3. Install Required Packages
63 |
64 | ```bash
65 | # Install packages from requirements.txt
66 | pip install -r requirements.txt
67 | ```
68 |
69 | ### Configuration
70 |
71 | #### 1. Prepare Environment File
72 |
73 | ```bash
74 | # Copy template to create .env file
75 | cp .env.example .env
76 | # Windows: copy .env.example .env
77 | ```
78 |
79 | #### 2. Set API Key
80 |
81 | Open `.env` file in a text editor and configure required settings:
82 |
83 | ```bash
84 | # Only required items need to be changed (others use default values)
85 | GEMINI_API_KEY=paste_your_api_key_here
86 | MODEL_NAME=gemini-2.0-flash # or gemini-1.5-pro etc.
87 | ```
88 |
89 | Other settings (avatar name, UI speed, sound effects) have default values and work out of the box. You can customize them later as needed.
90 |
91 | **Important**: `.env` file contains sensitive information. Never commit it to Git.
92 |
93 | ### Launch
94 |
95 | ```bash
96 | # Start the application
97 | python app.py
98 | ```
99 |
100 | On successful launch, you'll see:
101 | ```
102 | * Running on http://127.0.0.1:5000
103 | ```
104 |
105 | Access the application at `http://localhost:5000` in your browser.
106 |
107 | ## Usage
108 |
109 | ### Basic Operations
110 |
111 | 1. **Send Message**: Type text in the input field at the bottom and press Enter
112 | 2. **Chat History**: View automatically scrolling conversation history
113 | 3. **Avatar**: Avatar animates while AI is responding
114 |
115 | ## Developer Information
116 |
117 | ### Project Structure
118 |
119 | ```
120 | avatar-ui-core/
121 | ├── app.py # Flask application
122 | ├── settings.py # Configuration management
123 | ├── requirements.txt # Python dependencies
124 | ├── .env.example # Environment template
125 | ├── static/
126 | │ ├── css/
127 | │ │ └── style.css # UI styles
128 | │ ├── js/
129 | │ │ ├── app.js # Main entry point
130 | │ │ ├── chat.js # Chat functionality
131 | │ │ ├── animation.js # Animation control
132 | │ │ ├── sound.js # Sound effects
133 | │ │ └── settings.js # Frontend configuration
134 | │ └── images/
135 | │ ├── idle.png # Avatar (idle)
136 | │ └── talk.png # Avatar (talking)
137 | └── templates/
138 | └── index.html # HTML template
139 | ```
140 |
141 | **Note**: The `docs/` folder contains development notes and assets, and does not affect application functionality.
142 |
143 | ### API Endpoints
144 |
145 | | Endpoint | Method | Description | Parameters | Response |
146 | |----------|--------|-------------|------------|----------|
147 | | `/` | GET | Display main page | None | HTML |
148 | | `/api/chat` | POST | Chat with AI | `{message: string}` | `{response: string}` or `{error: string}` |
149 |
150 | ### Customization
151 |
152 | All settings can be adjusted in the `.env` file.
153 |
154 | #### 1. Change Avatar
155 |
156 | Replace image files:
157 | - `static/images/idle.png`: Idle avatar (recommended: 140x140px)
158 | - `static/images/talk.png`: Talking avatar (recommended: 140x140px)
159 |
160 | #### 2. AI Personality
161 |
162 | Edit these items in `.env` file:
163 | ```bash
164 | AVATAR_NAME=Spectra
165 | AVATAR_FULL_NAME=Spectra Communicator
166 | SYSTEM_INSTRUCTION=You are Spectra, an AI assistant. Respond in a technical and direct style with concise answers.
167 | ```
168 |
169 | #### 3. UI Behavior
170 |
171 | Adjust various speeds in `.env` file:
172 | ```bash
173 | # Typing speed (milliseconds, smaller = faster)
174 | TYPEWRITER_DELAY_MS=30
175 |
176 | # Mouth animation interval (milliseconds)
177 | MOUTH_ANIMATION_INTERVAL_MS=100
178 | ```
179 |
180 | #### 4. Sound Settings
181 |
182 | Customize sound effects in `.env` file:
183 | ```bash
184 | BEEP_FREQUENCY_HZ=600 # Pitch (Hz)
185 | BEEP_VOLUME=0.1 # Volume (0.0-1.0)
186 | BEEP_DURATION_MS=30 # Duration (milliseconds)
187 | ```
188 |
189 | **Note**: Application restart required after configuration changes.
190 |
191 | ## Environment Variables
192 |
193 | | Variable | Description | Default | Required |
194 | |----------|-------------|---------|----------|
195 | | `GEMINI_API_KEY` | Google Gemini API Key | - | ✅ |
196 | | `MODEL_NAME` | Gemini model to use | gemini-2.0-flash | ✅ |
197 | | **Server Settings** | | | |
198 | | `SERVER_PORT` | Server port number | 5000 | |
199 | | `DEBUG_MODE` | Enable debug mode | True | |
200 | | **Avatar Settings** | | | |
201 | | `AVATAR_NAME` | AI assistant name | Spectra | |
202 | | `AVATAR_FULL_NAME` | AI assistant full name | Spectra Communicator | |
203 | | `AVATAR_IMAGE_IDLE` | Idle avatar image | idle.png | |
204 | | `AVATAR_IMAGE_TALK` | Talking avatar image | talk.png | |
205 | | **AI Personality** | | | |
206 | | `SYSTEM_INSTRUCTION` | AI personality and response style | Technical and concise responses | |
207 | | **UI Settings** | | | |
208 | | `TYPEWRITER_DELAY_MS` | Typewriter effect speed (ms) | 50 | |
209 | | `MOUTH_ANIMATION_INTERVAL_MS` | Mouth animation interval (ms) | 150 | |
210 | | **Sound Settings** | | | |
211 | | `BEEP_FREQUENCY_HZ` | Beep frequency (Hz) | 800 | |
212 | | `BEEP_DURATION_MS` | Beep duration (ms) | 50 | |
213 | | `BEEP_VOLUME` | Beep volume (0.0-1.0) | 0.05 | |
214 | | `BEEP_VOLUME_END` | Beep end volume | 0.01 | |
215 |
216 | ## Tech Stack
217 |
218 | ### Backend
219 | - **Flask 3.0.0** - Web application framework
220 | - **google-generativeai 0.8.3** - Gemini API integration
221 | - **python-dotenv 1.0.0** - Environment variable management
222 |
223 | ### Frontend
224 | - **ES6 Modules** - Modular JavaScript
225 | - **Web Audio API** - Native browser sound generation
226 | - **CSS3** - Modern styling
227 | - **Fira Code** - Programming font
228 |
229 | ## ⚠️ Important Notes
230 |
231 | - This project is provided as a naked UI foundation, with default implementation designed for single-user use.
232 | - Sensitive information such as API keys is stored in `.env` and used only on the server side.
233 | - Suitable for personal use and learning purposes as-is, but when deploying for public access:
234 | - User-specific configuration storage required
235 | - Authentication mechanisms must be added
236 |
237 | ## License
238 |
239 | MIT License - See [LICENSE](LICENSE) file for details
240 |
241 | ## Credits
242 |
243 | Developed by Sito Sikino
244 |
245 | ### Technologies Used
246 | - Google Gemini API
247 | - Flask Framework
248 | - Fira Code Font
249 |
250 | ---
251 |
252 | **Note**: This project is created for entertainment and creative purposes. Please implement appropriate security measures when using in production environments.
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | """Webアプリケーション"""
2 | from flask import Flask, render_template, request, jsonify
3 | import google.generativeai as genai
4 | import settings
5 |
6 | # Gemini API接続
7 | genai.configure(api_key=settings.GEMINI_API_KEY)
8 | model = genai.GenerativeModel(
9 | model_name=settings.MODEL_NAME,
10 | system_instruction=settings.SYSTEM_INSTRUCTION
11 | )
12 | chat = model.start_chat() # チャットセッション開始
13 |
14 | app = Flask(__name__)
15 |
16 | @app.route('/')
17 | def index():
18 | """メインページ表示"""
19 | config = {
20 | 'typewriter_delay': settings.TYPEWRITER_DELAY_MS,
21 | 'avatar_name': settings.AVATAR_NAME,
22 | 'avatar_full_name': settings.AVATAR_FULL_NAME,
23 | 'mouth_animation_interval': settings.MOUTH_ANIMATION_INTERVAL_MS,
24 | 'beep_frequency': settings.BEEP_FREQUENCY_HZ,
25 | 'beep_duration': settings.BEEP_DURATION_MS,
26 | 'beep_volume': settings.BEEP_VOLUME,
27 | 'beep_volume_end': settings.BEEP_VOLUME_END,
28 | 'avatar_image_idle': settings.AVATAR_IMAGE_IDLE,
29 | 'avatar_image_talk': settings.AVATAR_IMAGE_TALK
30 | }
31 | return render_template('index.html', config=config)
32 |
33 | @app.route('/api/chat', methods=['POST'])
34 | def api_chat():
35 | """ユーザー入力を受信しAI応答を返す"""
36 | message = request.json['message']
37 | response = chat.send_message(message)
38 | return jsonify({'response': response.text})
39 |
40 | if __name__ == '__main__':
41 | app.run(debug=settings.DEBUG_MODE, port=settings.SERVER_PORT)
--------------------------------------------------------------------------------
/docs/CLAUDE.md:
--------------------------------------------------------------------------------
1 | # CLAUDE.md - avatar-ui
2 |
3 | このファイルは、[Claude Code](https://www.anthropic.com/claude-code) がこのリポジトリのコードを扱う際のガイダンスを提供します。
4 |
5 | ## ドキュメント構成
6 |
7 | - `spec.md` - 要件定義
8 | - `architecture.md` - アーキテクチャ詳細
9 |
10 | ## プロジェクト概要
11 |
12 | 「avatar-ui」は、クラシックなターミナルスタイルのUIコア。チャットUIからCLI統合まで拡張可能なプロジェクト基盤。
13 |
14 | ## 🚨 **最重要事項**
15 |
16 | Claude Code は次の原則を**絶対基準**として実装に従う。コード・設定・命名・タスク処理すべてが、この基準を背骨として段階的に進む。
17 |
18 | ### 設定管理原則
19 | - **一元管理**:設定値は意味ごとに構造化し `settings.py` で一元管理
20 | - **環境対応**:環境依存値や機密情報はすべて `.env` に置き、`settings.py` で動的読み込み
21 | - **意味的実装**:コードは具体値ではなく意味名(例 `SPEECH_INTERVAL`)で制御
22 | - **ハードコード禁止**:具体値はすべて `.env` と `settings.py` 経由で注入することで動的に制御する
23 | - **venv必須**:Python仮想環境は必ず `venv` を使用し、依存関係を分離管理する
24 |
25 | ### 開発原則
26 | - **Fail-Fast**:すべてのエラーは即時に停止し、原因を表面化させる(Fail-Fast)。**すべての処理は例外時にもフォールバックせず、明示的に失敗させる設計**とする。
27 | - **最小実装**:要求機能だけを実装し、余分なコードやファイルを加えない
28 | - **TDD採用**:t-wada式 Red→Green→Refactor→Commit を徹底
29 |
30 | ### コード品質
31 | - **本質性**:不要ファイル・冗長コードを排除
32 | - **日本語コメント**:本質的な意図のみを簡潔に日本語で記述
33 | - **命名**:Uncle Bob の Intention-Revealing Names に従う
34 |
35 | ---
36 |
37 | ## Phase 1 EXPLORE(調査)
38 |
39 | **目的**
40 | - 文脈と依存関係を把握し、実装前に全体像を理解する
41 |
42 | **行動**
43 | 1. Brave Search、公式ドキュメント、GitHub を一次情報源として調査
44 | 2. `.env`, `Dockerfile`, CI 設定、依存ライブラリ、`settings.py` を確認
45 | 3. Context7(mcp) に調査内容を蓄積・整理し、Claude(Use Subagent)で役割分担
46 | 4. Serena (mcp) を駆使し、文脈を正確に保持し続ける
47 | 5. 過去ログ `docs/dev-log/`(`yyyy-mm-dd_hh-mm_機能名.md`)を全読み込みし、既存の設計意図・副作用を把握
48 |
49 | ---
50 |
51 | ## Phase 2 PLAN(計画)
52 |
53 | **目的**
54 | - 実装を最小タスクへ分割し、受け入れ条件と運用ルールを明確にする
55 |
56 | **行動**
57 | 1. `todo.md` にマイクロタスクを列挙し、各タスクに受け入れ条件を付与
58 | 2. 各タスクを t-wada式 TDD サイクル 1回分として設計
59 | 3. `settings.py` に置く定数と `.env` の環境値を棚卸し
60 | 4. Context7 + Claude(Use Subagent) でタスク粒度・設計整合性をレビュー
61 | 5. Serena (mcp) を駆使し、文脈を正確に保持し続ける
62 |
63 | ---
64 |
65 | ## Phase 3 IMPLEMENT(実装)
66 |
67 | **ルール**
68 | - **ハードコード禁止**:具体値はすべて `.env` と `settings.py` 経由で注入することで動的に制御する
69 | - **Fail-Fast** を最上位原則とし、異常時は即停止
70 | - **フォールバック禁止**:いかなる例外も認めず、本来の目的コード以外の例外措置は施さない
71 | - **Serena (mcp)** を駆使し、文脈を正確に保持し続ける
72 |
73 | ### 🔴 Red — 失敗するテストを書く
74 | - 未実装状態で必ず失敗するユニットテストを 1 件追加し、仕様を固定
75 |
76 | ### 🟢 Green — 最小実装でテストを通す
77 | - 余計なロジックを加えず最小コードでテストを通過
78 | - 必要な箇所には簡潔な日本語コメントで本質的な意図のみを記述
79 |
80 | ### 🟡 Refactor — 品質と構造の改善
81 | - DRY、命名整理、単純化、`black`・`flake8`・`mypy` 準拠
82 | - テストは常に Green を維持
83 |
84 | ### ⚪ Commit — 意味単位で保存
85 |
86 | 1. **todo.md を更新**
87 | - 完了タスクを ✅ にチェック
88 | - タスク全文(箇条書き内容を含む)を `docs/dev-log/` に移動して履歴化
89 |
90 | 2. **実装ログを作成**
91 | - `docs/dev-log/yyyy-mm-dd_hh-mm_機能名.md` を新規作成(※現在時刻を確認)
92 | - 以下を簡潔に記載
93 | - 完了タスク全文
94 | - 実装の背景
95 | - 設計意図
96 | - 副作用 / 注意点
97 | - 関連ファイル・関数
98 |
99 | 3. **Git 操作**
100 | - `git add .` でコード・todo.md・ログをステージング
101 | - `pytest` で最終テスト確認
102 | - 意図が伝わるコミットメッセージで `git commit`
103 | - 変更をリモートへ `git push`
104 |
105 | > こうすることで **コード・タスク管理・実装ログ** が同一コミットに揃い、
106 | > 履歴と進捗が完全に同期される。
107 |
108 | ---
109 |
110 | ## Phase 4 VERIFY(統合検証・記録・コミット)
111 |
112 | **目的**
113 | - 要件・品質基準への最終適合と成果物固定
114 |
115 | **行動**
116 | 1. すべてのタスクについて統合テスト (`pytest` 全体) を実行
117 | 2. 全コードに `black --check`, `flake8`, `mypy` を適用し合格を確認
118 | 3. 合格したタスクを `todo.md` で ✅ に更新し、該当タスク全文を
119 | `docs/dev-log/yyyy-mm-dd_hh-mm_機能名.md` に移動して以下を記録:
120 | - タスク全文
121 | - 実装の背景
122 | - 設計意図
123 | - 副作用 / 注意点
124 | - 関連ファイル・関数
125 | 4. 変更一式(コード・todo.md・ログ)を `git add .` でステージングし、
126 | 意図が伝わるコミットメッセージで `git commit && git push`
127 | 5. **意味ある単位として完結**し、次フェーズへ進める状態になっている
128 |
129 | ---
130 |
--------------------------------------------------------------------------------
/docs/architecture.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sito-sikino/avatar-ui-core/332765ab57b9942b80c8f1f0395b648047963514/docs/architecture.md
--------------------------------------------------------------------------------
/docs/assets/avatar-ui_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sito-sikino/avatar-ui-core/332765ab57b9942b80c8f1f0395b648047963514/docs/assets/avatar-ui_demo.gif
--------------------------------------------------------------------------------
/docs/spec.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sito-sikino/avatar-ui-core/332765ab57b9942b80c8f1f0395b648047963514/docs/spec.md
--------------------------------------------------------------------------------
/docs/todo.md:
--------------------------------------------------------------------------------
1 | # Phase 2 PLAN — todo.md(マイクロタスク+受け入れ条件)
2 |
3 | > 準拠: spec.md / architecture.md / CLAUDE.md
4 |
5 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | flask==3.0.0
2 | google-generativeai==0.8.3
3 | python-dotenv==1.0.0
--------------------------------------------------------------------------------
/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | 設定管理モジュール - .envファイルから全設定を読み込み
3 | """
4 | import os
5 | from dotenv import load_dotenv
6 |
7 | # .envファイルを読み込み
8 | load_dotenv()
9 |
10 | # ===========================================
11 | # 必須設定(環境変数が設定されていない場合はエラー)
12 | # ===========================================
13 |
14 | # AI設定
15 | GEMINI_API_KEY = os.environ['GEMINI_API_KEY'] # Gemini APIキー
16 | MODEL_NAME = os.environ['MODEL_NAME'] # 使用するGeminiモデル
17 |
18 | # ===========================================
19 | # 任意設定(デフォルト値あり)
20 | # ===========================================
21 |
22 | # アバター設定
23 | AVATAR_NAME = os.getenv('AVATAR_NAME', 'Spectra')
24 | AVATAR_FULL_NAME = os.getenv('AVATAR_FULL_NAME', 'Spectra Communicator')
25 | AVATAR_IMAGE_IDLE = os.getenv('AVATAR_IMAGE_IDLE', 'idle.png')
26 | AVATAR_IMAGE_TALK = os.getenv('AVATAR_IMAGE_TALK', 'talk.png')
27 |
28 | # AI性格設定(AVATAR_NAMEに依存)
29 | SYSTEM_INSTRUCTION = os.getenv(
30 | 'SYSTEM_INSTRUCTION',
31 | f'あなたは{AVATAR_NAME}というAIアシスタントです。技術的で直接的なスタイルで簡潔に応答してください。回答は短く要点を押さえたものにしてください。'
32 | )
33 |
34 | # サーバー設定
35 | SERVER_PORT = int(os.getenv('SERVER_PORT', '5000'))
36 | DEBUG_MODE = os.getenv('DEBUG_MODE', 'True').lower() == 'true'
37 |
38 | # UI設定
39 | TYPEWRITER_DELAY_MS = int(os.getenv('TYPEWRITER_DELAY_MS', '50'))
40 | MOUTH_ANIMATION_INTERVAL_MS = int(os.getenv('MOUTH_ANIMATION_INTERVAL_MS', '150'))
41 |
42 | # サウンド設定
43 | BEEP_FREQUENCY_HZ = int(os.getenv('BEEP_FREQUENCY_HZ', '800'))
44 | BEEP_DURATION_MS = int(os.getenv('BEEP_DURATION_MS', '50'))
45 | BEEP_VOLUME = float(os.getenv('BEEP_VOLUME', '0.05'))
46 | BEEP_VOLUME_END = float(os.getenv('BEEP_VOLUME_END', '0.01'))
--------------------------------------------------------------------------------
/static/css/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap');
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | background: #000;
11 | color: #0f0;
12 | font-family: 'Fira Code', monospace;
13 | font-size: 14px;
14 | height: 100vh;
15 | overflow: hidden;
16 | }
17 |
18 | .terminal {
19 | height: 100vh;
20 | display: flex;
21 | flex-direction: column;
22 | max-width: 1200px;
23 | margin: 0 auto;
24 | padding: 20px;
25 | }
26 |
27 |
28 | .chat-area {
29 | width: 100%;
30 | max-width: 1160px;
31 | height: 300px;
32 | display: flex;
33 | flex-direction: column;
34 | border-top: 2px solid #0f0;
35 | padding-top: 15px;
36 | }
37 |
38 | .chat-container {
39 | width: 100%;
40 | flex: 1;
41 | margin-bottom: 10px;
42 | display: flex;
43 | gap: 10px;
44 | min-height: 0;
45 | }
46 |
47 | .avatar-area {
48 | width: 160px;
49 | border: 1px solid #0f0;
50 | background: rgba(0, 20, 0, 0.1);
51 | display: flex;
52 | flex-direction: column;
53 | align-items: center;
54 | justify-content: center;
55 | padding: 15px;
56 | }
57 |
58 | #avatar-img {
59 | width: 140px;
60 | height: 140px;
61 | image-rendering: pixelated;
62 | margin-bottom: 10px;
63 | }
64 |
65 | .avatar-label {
66 | color: #0f0;
67 | font-size: 12px;
68 | text-align: center;
69 | }
70 |
71 |
72 |
73 | .output {
74 | flex: 1;
75 | overflow-y: auto;
76 | padding: 15px;
77 | border: 1px solid #0f0;
78 | background: rgba(0, 20, 0, 0.1);
79 | height: 100%;
80 | max-height: 100%;
81 | }
82 |
83 |
84 | .line {
85 | margin: 8px 0;
86 | padding: 4px;
87 | word-wrap: break-word;
88 | }
89 |
90 |
91 |
92 | .line.system {
93 | color: #0f0;
94 | opacity: 0.7;
95 | }
96 |
97 | .line.user {
98 | color: #0ff;
99 | border-left: 2px solid #0ff;
100 | padding-left: 10px;
101 | }
102 |
103 | .line.ai {
104 | color: #0f0;
105 | border-left: 2px solid #0f0;
106 | padding-left: 10px;
107 | }
108 |
109 | .line.error {
110 | color: #f00;
111 | border-left: 2px solid #f00;
112 | padding-left: 10px;
113 | }
114 |
115 | .user-prompt {
116 | color: #0ff;
117 | font-weight: 500;
118 | }
119 |
120 | .ai-prompt {
121 | color: #0f0;
122 | font-weight: 500;
123 | }
124 |
125 | .error-prompt {
126 | color: #f00;
127 | font-weight: 500;
128 | }
129 |
130 | .input-line {
131 | width: 100%;
132 | display: flex;
133 | align-items: center;
134 | padding: 10px;
135 | border: 1px solid #0f0;
136 | background: rgba(0, 20, 0, 0.1);
137 | }
138 |
139 | .prompt {
140 | color: #0f0;
141 | margin-right: 10px;
142 | }
143 |
144 | #input {
145 | flex: 1;
146 | background: transparent;
147 | border: none;
148 | color: #0f0;
149 | font-family: inherit;
150 | font-size: inherit;
151 | outline: none;
152 | }
153 |
154 |
155 |
--------------------------------------------------------------------------------
/static/images/idle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sito-sikino/avatar-ui-core/332765ab57b9942b80c8f1f0395b648047963514/static/images/idle.png
--------------------------------------------------------------------------------
/static/images/talk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sito-sikino/avatar-ui-core/332765ab57b9942b80c8f1f0395b648047963514/static/images/talk.png
--------------------------------------------------------------------------------
/static/js/animation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * アニメーション管理モジュール
3 | * タイプライター効果と口パクアニメーションを管理
4 | */
5 | export class AnimationManager {
6 | constructor(settings, soundManager) {
7 | this.settings = settings;
8 | this.soundManager = soundManager;
9 | this.talkingInterval = null;
10 | this.avatarImg = document.getElementById('avatar-img');
11 | this.output = document.getElementById('output');
12 | }
13 |
14 | // 口パクアニメーション開始
15 | startMouthAnimation() {
16 | if (this.talkingInterval) {
17 | this.stopMouthAnimation();
18 | }
19 |
20 | let mouthOpen = false;
21 | this.talkingInterval = setInterval(() => {
22 | const imagePath = this.settings.getAvatarImagePath(!mouthOpen);
23 | this.avatarImg.src = imagePath;
24 | mouthOpen = !mouthOpen;
25 | }, this.settings.mouthAnimationInterval);
26 | }
27 |
28 | // 口パクアニメーション停止
29 | stopMouthAnimation() {
30 | if (this.talkingInterval) {
31 | clearInterval(this.talkingInterval);
32 | this.talkingInterval = null;
33 | }
34 | // アイドル状態に戻す
35 | this.avatarImg.src = this.settings.getAvatarImagePath(true);
36 | }
37 |
38 | // タイプライター効果
39 | typeWriter(element, text) {
40 | return new Promise((resolve) => {
41 | let i = 0;
42 |
43 | // 口パクアニメーション開始
44 | this.startMouthAnimation();
45 |
46 | const type = () => {
47 | if (i < text.length) {
48 | element.textContent += text.charAt(i++);
49 | this.output.scrollTop = this.output.scrollHeight;
50 |
51 | // スペース以外で音を鳴らす
52 | if (text.charAt(i-1) !== ' ') {
53 | this.soundManager.playTypeSound();
54 | }
55 |
56 | setTimeout(type, this.settings.typewriterDelay);
57 | } else {
58 | // 完了時:口を閉じる
59 | this.stopMouthAnimation();
60 | resolve();
61 | }
62 | };
63 |
64 | type();
65 | });
66 | }
67 | }
--------------------------------------------------------------------------------
/static/js/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * メインアプリケーションエントリーポイント
3 | * 各モジュールを初期化し、アプリケーションを起動
4 | */
5 | import { createSettings } from './settings.js';
6 | import { SoundManager } from './sound.js';
7 | import { AnimationManager } from './animation.js';
8 | import { ChatManager } from './chat.js';
9 |
10 | // DOMロード完了後にアプリケーション初期化
11 | document.addEventListener('DOMContentLoaded', () => {
12 | // グローバル変数appConfigはindex.htmlで定義される
13 | if (typeof appConfig !== 'undefined') {
14 | const settings = createSettings(appConfig);
15 | const soundManager = new SoundManager(settings);
16 | const animationManager = new AnimationManager(settings, soundManager);
17 | window.chatManager = new ChatManager(settings, animationManager);
18 | } else {
19 | console.error('App config not found');
20 | }
21 | });
--------------------------------------------------------------------------------
/static/js/chat.js:
--------------------------------------------------------------------------------
1 | /**
2 | * チャット機能モジュール
3 | * メッセージの送受信とUI更新を管理
4 | */
5 | export class ChatManager {
6 | constructor(settings, animationManager) {
7 | this.settings = settings;
8 | this.animationManager = animationManager;
9 | this.output = document.getElementById('output');
10 | this.input = document.getElementById('input');
11 |
12 | this.initEventListeners();
13 | }
14 |
15 | // イベントリスナー初期化
16 | initEventListeners() {
17 | this.input.addEventListener('keypress', async (e) => {
18 | if (e.key === 'Enter' && this.input.value.trim()) {
19 | await this.sendMessage(this.input.value);
20 | this.input.value = '';
21 | }
22 | });
23 | }
24 |
25 | // メッセージ送信
26 | async sendMessage(message) {
27 | // ユーザーメッセージを表示
28 | this.addLine(message, 'user');
29 |
30 | try {
31 | // AIに送信
32 | const response = await fetch('/api/chat', {
33 | method: 'POST',
34 | headers: {'Content-Type': 'application/json'},
35 | body: JSON.stringify({message})
36 | });
37 |
38 | const data = await response.json();
39 |
40 | // AIレスポンスをタイプライター効果で表示
41 | await this.addLine(data.response, 'ai');
42 | } catch (error) {
43 | console.error('Chat error:', error);
44 | this.addLine('エラーが発生しました。再試行してください。', 'system');
45 | }
46 | }
47 |
48 | // メッセージを画面に追加
49 | async addLine(text, type) {
50 | const line = document.createElement('div');
51 | line.className = 'line ' + type;
52 |
53 | if (type === 'user') {
54 | line.innerHTML = `USER> ${text}`;
55 | this.output.appendChild(line);
56 | this.scrollToBottom();
57 | } else if (type === 'ai') {
58 | // AIメッセージはタイプライター演出
59 | line.innerHTML = `${this.settings.avatarName}> `;
60 | this.output.appendChild(line);
61 |
62 | const aiTextElement = line.querySelector('.ai-text');
63 | await this.animationManager.typeWriter(aiTextElement, text);
64 | } else {
65 | // system メッセージなど
66 | line.textContent = text;
67 | this.output.appendChild(line);
68 | this.scrollToBottom();
69 | }
70 | }
71 |
72 | // チャットエリアを最下部にスクロール
73 | scrollToBottom() {
74 | this.output.scrollTop = this.output.scrollHeight;
75 | }
76 | }
--------------------------------------------------------------------------------
/static/js/settings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * アプリケーション設定モジュール
3 | * サーバーサイドから渡される設定値を管理
4 | */
5 | export const createSettings = (config) => ({
6 | ...config,
7 | // アバター画像のパスを生成
8 | getAvatarImagePath: (isIdle = true) =>
9 | `/static/images/${isIdle ? config.avatarImageIdle : config.avatarImageTalk}`
10 | });
--------------------------------------------------------------------------------
/static/js/sound.js:
--------------------------------------------------------------------------------
1 | /**
2 | * サウンド生成・再生モジュール
3 | * タイプライター効果音の生成と再生を管理
4 | */
5 | export class SoundManager {
6 | constructor(settings) {
7 | this.settings = settings;
8 | this.audioContext = null;
9 | this.initAudioContext();
10 | }
11 |
12 | // Web Audio Context初期化
13 | initAudioContext() {
14 | try {
15 | this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
16 | } catch (error) {
17 | // Audio未対応環境では無音で続行
18 | }
19 | }
20 |
21 | // タイプライター効果音を再生
22 | playTypeSound() {
23 | if (!this.audioContext) return;
24 |
25 | try {
26 | const oscillator = this.audioContext.createOscillator();
27 | const gainNode = this.audioContext.createGain();
28 |
29 | oscillator.connect(gainNode);
30 | gainNode.connect(this.audioContext.destination);
31 |
32 | oscillator.type = 'square'; // 矩形波
33 | oscillator.frequency.setValueAtTime(
34 | this.settings.beepFrequency,
35 | this.audioContext.currentTime
36 | );
37 |
38 | gainNode.gain.setValueAtTime(
39 | this.settings.beepVolume,
40 | this.audioContext.currentTime
41 | );
42 | const durationInSeconds = this.settings.beepDuration / 1000; // ms→秒変換
43 | gainNode.gain.exponentialRampToValueAtTime(
44 | this.settings.beepVolumeEnd,
45 | this.audioContext.currentTime + durationInSeconds
46 | );
47 |
48 | oscillator.start(this.audioContext.currentTime);
49 | oscillator.stop(this.audioContext.currentTime + durationInSeconds);
50 | } catch (e) {
51 | // 音声再生エラーは無視
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ config.avatar_full_name }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
> SYSTEM: {{ config.avatar_full_name }} Online
18 |
19 |
20 |
21 |
22 |

23 |
{{ config.avatar_name|upper }}
24 |
25 |
26 |
27 |
28 |
29 | >
30 |
31 |
32 |
33 |
34 |
35 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------