# 画面設計書 116-メッセージ画面

## 概要

本ドキュメントは、Chatwootのチャットウィジェットにおけるメッセージ画面の設計書です。顧客がサポートエージェントとリアルタイムでメッセージをやり取りするチャット画面として機能します。

### 本画面の処理概要

**業務上の目的・背景**：メッセージ画面は、チャットウィジェットの中核となる画面であり、顧客とサポートエージェント間のリアルタイムコミュニケーションを実現します。テキストメッセージの送受信、添付ファイルの送信、エージェントの入力中表示、メッセージへの返信引用など、豊富なチャット機能を提供します。顧客満足度向上と迅速な問題解決を支援する重要な画面です。

**画面へのアクセス方法**：ホーム画面から「Start/Continue Conversation」ボタンをクリック、またはプリチャットフォーム送信後に自動遷移します。URLハッシュパスは `/messages` です。

**主要な操作・処理内容**：
1. メッセージ履歴の表示（日付区切り付き）
2. 新規メッセージの送信
3. ファイル・画像の添付送信
4. メッセージへの返信引用
5. エージェント入力中表示
6. 古いメッセージの自動読み込み（無限スクロール）
7. 最終閲覧時刻の更新
8. 会話解決時の新規会話開始

**画面遷移**：
- 遷移元：ホーム画面、プリチャットフォーム画面
- 遷移先：プリチャットフォーム画面（新規会話開始時）、記事ビューア画面

**権限による表示制御**：ウィジェットはWebサイト訪問者向けのため、特別な権限制御はありません。会話が解決済みで`allowMessagesAfterResolved`が無効の場合、返信ボックスが非表示になります。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 162 | Widgetメッセージ | 主機能 | チャットメッセージの送受信 |
| 161 | Widget会話 | 補助機能 | 会話の管理 |

## 画面種別

チャット画面

## URL/ルーティング

- パス: `#/messages`（ウィジェット内ハッシュルーティング）
- ルート名: `messages`
- コンポーネント: `Messages.vue`

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 型 | 必須 | 説明 |
|--------|--------|-----|------|------|
| メッセージ内容 | content | string | No | 送信するテキストメッセージ |
| 添付ファイル | attachment | File | No | 送信するファイル/画像 |
| 返信先メッセージ | replyTo | number | No | 引用返信時の元メッセージID |

### 出力項目

| 項目名 | 項目ID | 型 | 説明 |
|--------|--------|-----|------|
| グループ化メッセージ | groupedMessages | array | 日付別にグループ化されたメッセージ配列 |
| エージェント入力中フラグ | isAgentTyping | boolean | エージェントが入力中かどうか |

## 表示項目

### 会話エリア

| 項目名 | 説明 |
|--------|------|
| ローディングスピナー | メッセージ読み込み中のインジケータ |
| 日付区切り | メッセージの日付ごとの区切り線 |
| メッセージバブル | 各メッセージの表示（送信者アバター、内容、時刻） |
| エージェント入力中バブル | エージェントが入力中の表示 |

### フッターエリア

| 項目名 | 説明 |
|--------|------|
| 返信引用バー | 返信先メッセージの表示（返信時のみ） |
| メッセージ入力欄 | テキスト入力エリア |
| 添付ボタン | ファイル添付用ボタン |
| 送信ボタン | メッセージ送信ボタン |
| 新規会話ボタン | 解決済み会話時の新規開始ボタン |
| トランスクリプト送信ボタン | メールでのトランスクリプト送信 |

## イベント仕様

### 1-メッセージ送信

**トリガー**: 送信ボタンクリックまたはEnterキー押下

**処理フロー**:
1. `handleSendMessage`メソッド呼び出し
2. `conversation/sendMessage`アクションをdispatch
3. 一時メッセージをUIに即座に表示
4. API経由でメッセージ送信
5. 成功時: メッセージステータスを「sent」に更新
6. 失敗時: メッセージステータスを「failed」に更新
7. 返信引用をクリア
8. 会話サイズが0の場合、会話属性を取得

### 2-ファイル添付送信

**トリガー**: ファイル選択完了後

**処理フロー**:
1. `handleSendAttachment`メソッド呼び出し
2. `conversation/sendAttachment`アクションをdispatch
3. 一時メッセージ（添付プレビュー付き）をUIに表示
4. API経由でファイルアップロード
5. 成功/失敗に応じてステータス更新
6. 返信引用をクリア

### 3-古いメッセージ読み込み

**トリガー**: スクロールがトップ付近に達した時

**処理フロー**:
1. `handleScroll`メソッドでスクロール位置を監視
2. `scrollTop < 100` かつ読み込み可能な場合
3. `conversation/fetchOldConversations`をdispatch
4. 以前のスクロール位置を保持して読み込み後に復元

### 4-返信引用

**トリガー**: メッセージの返信ボタンクリック

**処理フロー**:
1. `BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE`イベントを受信
2. `toggleReplyTo`でinReplyToステートを更新
3. FooterReplyToコンポーネントで引用元を表示

### 5-新規会話開始

**トリガー**: 解決済み会話で「Start New Conversation」ボタンクリック

**処理フロー**:
1. `startNewConversation`メソッド呼び出し
2. `prechat-form`へルーター遷移
3. SDKイベント `CHATWOOT_ON_START_CONVERSATION` を送信

### 6-トランスクリプト送信

**トリガー**: 「Email Transcript」ボタンクリック

**処理フロー**:
1. `sendTranscript`メソッド呼び出し
2. メールアドレスの有無を確認
3. `sendEmailTranscript` APIを呼び出し
4. 成功/失敗アラートを表示

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| メッセージ送信 | messages | INSERT | 新規メッセージ作成 |
| ファイル添付送信 | messages | INSERT | 添付付きメッセージ作成 |
| ファイル添付送信 | attachments | INSERT | 添付ファイル保存 |
| 最終閲覧更新 | conversation_participants | UPDATE | contact_last_seen_at更新 |

### テーブル別更新項目詳細

#### messages

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | conversation_id | 現在の会話ID | 会話関連付け |
| INSERT | message_type | 0 (incoming) | 顧客からのメッセージ |
| INSERT | content | 入力テキスト | メッセージ本文 |
| INSERT | content_attributes | { in_reply_to: replyTo } | 返信引用時 |
| INSERT | created_at | 現在時刻 | 作成日時 |

## メッセージ仕様

| 種別 | キー | メッセージ内容 | 表示タイミング |
|------|-----|---------------|---------------|
| ボタン | START_NEW_CONVERSATION | 新しい会話を開始 | 解決済み会話時 |
| ボタン | EMAIL_TRANSCRIPT.BUTTON_TEXT | トランスクリプトを送信 | 解決済み会話時 |
| 成功 | EMAIL_TRANSCRIPT.SEND_EMAIL_SUCCESS | トランスクリプトを送信しました | トランスクリプト送信成功時 |
| エラー | EMAIL_TRANSCRIPT.SEND_EMAIL_ERROR | 送信に失敗しました | トランスクリプト送信失敗時 |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| メッセージ送信失敗 | メッセージにfailedステータスを設定、再送可能 |
| ファイルアップロード失敗 | 一時メッセージにfailedステータスを設定 |
| 古いメッセージ取得失敗 | ローディング状態をリセット |

## 備考

- WebSocket経由でリアルタイムメッセージ受信
- ダークモード対応
- 無限スクロールによる過去メッセージ読み込み
- 一時メッセージによる即時UI反映（オプティミスティックUI）
- エージェント入力中はリアルタイム通知
- 会話解決後のメッセージ送信可否は設定に依存

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

メッセージデータ構造とストア管理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.js | `app/javascript/widget/store/modules/conversation/index.js` | state構造、conversations オブジェクト |
| 1-2 | getters.js | `app/javascript/widget/store/modules/conversation/getters.js` | getGroupedConversation getter |

**読解のコツ**: conversationsはオブジェクト形式（id -> message）で保存され、getGroupedConversationで日付グループ配列に変換される。

#### Step 2: エントリーポイントを理解する

ルーティングとビューコンポーネントの構成を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | router.js | `app/javascript/widget/router.js` | L33-36: messagesルート定義 |
| 2-2 | Messages.vue | `app/javascript/widget/views/Messages.vue` | ビューコンポーネント |

**主要処理フロー**:
1. **L10-12**: mapGettersでgroupedMessages取得
2. **L15**: マウント時にsetUserLastSeenを実行

#### Step 3: サブコンポーネントを理解する

チャット表示とフッター入力のコンポーネントを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ConversationWrap.vue | `app/javascript/widget/components/ConversationWrap.vue` | メッセージ表示、無限スクロール |
| 3-2 | ChatFooter.vue | `app/javascript/widget/components/ChatFooter.vue` | メッセージ入力、送信処理 |
| 3-3 | ChatMessage.vue | `app/javascript/widget/components/ChatMessage.vue` | 個別メッセージ表示 |

**ConversationWrap.vue主要処理**:
- **L77-95**: handleScroll - 無限スクロール実装
- **L64-67**: scrollToBottom - 新メッセージ時のスクロール

**ChatFooter.vue主要処理**:
- **L63-74**: handleSendMessage - メッセージ送信
- **L75-81**: handleSendAttachment - ファイル送信
- **L82-89**: startNewConversation - 新規会話開始

### プログラム呼び出し階層図

```
Messages.vue (ビュー)
    |
    +-- ConversationWrap.vue
    |       |
    |       +-- DateSeparator.vue (日付区切り)
    |       |
    |       +-- ChatMessage.vue (各メッセージ)
    |       |       |
    |       |       +-- @reply -> BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE
    |       |
    |       +-- AgentTypingBubble.vue (入力中表示)
    |       |
    |       +-- handleScroll()
    |               |
    |               +-- fetchOldConversations()
    |
    +-- ChatFooter.vue
            |
            +-- FooterReplyTo.vue (返信引用)
            |
            +-- ChatInputWrap.vue
            |       |
            |       +-- @sendMessage -> handleSendMessage()
            |       |       |
            |       |       +-- conversation/sendMessage
            |       |               |
            |       |               +-- sendMessageAPI
            |       |
            |       +-- @sendAttachment -> handleSendAttachment()
            |               |
            |               +-- conversation/sendAttachment
            |                       |
            |                       +-- sendAttachmentAPI
            |
            +-- CustomButton (新規会話/トランスクリプト)
```

### データフロー図

```
[入力]                    [処理]                         [出力]

テキスト入力 ---------> handleSendMessage() ------> sendMessageAPI
    |                       |                           |
    v                       v                           v
createTemporaryMessage --> pushMessageToConversation --> WebSocket通知
                                                           |
                                                           v
                                                    エージェント画面

スクロール -----------> handleScroll() -----------> fetchOldConversations
                           |                           |
                           v                           v
                   scrollTop < 100              getMessagesAPI
                                                       |
                                                       v
                                               setMessagesInConversation
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Messages.vue | `app/javascript/widget/views/Messages.vue` | ソース | ビューコンポーネント |
| ConversationWrap.vue | `app/javascript/widget/components/ConversationWrap.vue` | ソース | メッセージ一覧表示 |
| ChatFooter.vue | `app/javascript/widget/components/ChatFooter.vue` | ソース | 入力フッター |
| ChatMessage.vue | `app/javascript/widget/components/ChatMessage.vue` | ソース | 個別メッセージ |
| ChatInputWrap.vue | `app/javascript/widget/components/ChatInputWrap.vue` | ソース | 入力欄ラップ |
| FooterReplyTo.vue | `app/javascript/widget/components/FooterReplyTo.vue` | ソース | 返信引用表示 |
| AgentTypingBubble.vue | `app/javascript/widget/components/AgentTypingBubble.vue` | ソース | 入力中表示 |
| DateSeparator.vue | `app/javascript/shared/components/DateSeparator.vue` | ソース | 日付区切り |
| actions.js | `app/javascript/widget/store/modules/conversation/actions.js` | ソース | 会話アクション |
| getters.js | `app/javascript/widget/store/modules/conversation/getters.js` | ソース | 会話getter |
| conversation.js | `app/javascript/widget/api/conversation.js` | ソース | 会話API |
