215 lines
7.3 KiB
C#
215 lines
7.3 KiB
C#
|
|
using UnityEngine;
|
||
|
|
using UnityEngine.UI;
|
||
|
|
using TMPro;
|
||
|
|
using System.Collections;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using LLM; // 引用新的 LLM 命名空间
|
||
|
|
using Core.InputLock;
|
||
|
|
|
||
|
|
namespace UI
|
||
|
|
{
|
||
|
|
public class TabletController : MonoBehaviour
|
||
|
|
{
|
||
|
|
[SerializeField] private bool debugLogs = true;
|
||
|
|
[Header("UI References")]
|
||
|
|
public GameObject tabletPanel; // 整个平板界面的父物体
|
||
|
|
public TMP_InputField inputField; // 输入框
|
||
|
|
public Transform chatContent; // 聊天记录的父物体 (ScrollView Content)
|
||
|
|
public GameObject messagePrefab; // 聊天气泡预设体
|
||
|
|
|
||
|
|
[Header("Game Control")]
|
||
|
|
public GameObject hudCanvas; // 游戏原本的 HUD (准星等),打开平板时隐藏
|
||
|
|
public MonoBehaviour playerController; // 玩家控制器脚本
|
||
|
|
|
||
|
|
[Header("LLM Manager")]
|
||
|
|
public LLMChatManager chatManager; // 引用 LLMChatManager 来管理多模型切换
|
||
|
|
// public GameObject llmServiceObj; // 已废弃,请使用 chatManager
|
||
|
|
|
||
|
|
private Coroutine rebuildChatLayoutRoutine;
|
||
|
|
|
||
|
|
public static TabletController Instance { get; private set; }
|
||
|
|
|
||
|
|
private void Awake()
|
||
|
|
{
|
||
|
|
if (Instance == null) Instance = this;
|
||
|
|
else Destroy(gameObject);
|
||
|
|
|
||
|
|
if (tabletPanel != null) tabletPanel.SetActive(false);
|
||
|
|
|
||
|
|
if (playerController == null)
|
||
|
|
{
|
||
|
|
playerController = FindObjectOfType<Player.PlayerController>(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 自动查找 LLMChatManager (如果在场景中)
|
||
|
|
if (chatManager == null)
|
||
|
|
{
|
||
|
|
chatManager = FindObjectOfType<LLMChatManager>();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (chatManager == null)
|
||
|
|
{
|
||
|
|
Debug.LogError("[TabletController] 未找到 LLMChatManager!请确保场景中存在该管理器。");
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// 监听历史记录加载完成(这里简单地在 Start 中延迟一帧加载,因为 SaveManager 加载通常很快)
|
||
|
|
StartCoroutine(InitChatHistory());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private IEnumerator InitChatHistory()
|
||
|
|
{
|
||
|
|
// 等待一帧,确保 LLMChatManager 已经完成了 Start 中的 SaveManager.LoadGame()
|
||
|
|
yield return null;
|
||
|
|
|
||
|
|
if (chatManager.fullChatLog != null && chatManager.fullChatLog.Count > 0)
|
||
|
|
{
|
||
|
|
// 清理可能存在的测试数据(如果有)
|
||
|
|
foreach (Transform child in chatContent)
|
||
|
|
{
|
||
|
|
Destroy(child.gameObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 重建 UI
|
||
|
|
foreach (var msg in chatManager.fullChatLog)
|
||
|
|
{
|
||
|
|
bool isPlayer = (msg.role == "user");
|
||
|
|
// 只有 user 和 assistant 的消息需要显示
|
||
|
|
if (msg.role == "user" || msg.role == "assistant")
|
||
|
|
{
|
||
|
|
AddMessageToUI(isPlayer ? "Player" : "AI", msg.content, isPlayer);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 滚动到底部
|
||
|
|
yield return new WaitForEndOfFrame();
|
||
|
|
ScrollRect scrollRect = chatContent.GetComponentInParent<ScrollRect>();
|
||
|
|
if (scrollRect != null) scrollRect.verticalNormalizedPosition = 0f;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void ShowTablet()
|
||
|
|
{
|
||
|
|
if (debugLogs) Debug.Log("[TabletController] ShowTablet");
|
||
|
|
if (tabletPanel != null) tabletPanel.SetActive(true);
|
||
|
|
if (hudCanvas != null) hudCanvas.SetActive(false);
|
||
|
|
|
||
|
|
TogglePlayerControl(false);
|
||
|
|
|
||
|
|
Cursor.lockState = CursorLockMode.None;
|
||
|
|
Cursor.visible = true;
|
||
|
|
|
||
|
|
if (inputField != null) inputField.ActivateInputField();
|
||
|
|
|
||
|
|
if (rebuildChatLayoutRoutine != null) StopCoroutine(rebuildChatLayoutRoutine);
|
||
|
|
rebuildChatLayoutRoutine = StartCoroutine(RebuildChatLayoutNextFrame());
|
||
|
|
|
||
|
|
if (UI.PanelStack.UIPanelStack.Instance != null && tabletPanel != null)
|
||
|
|
{
|
||
|
|
UI.PanelStack.UIPanelStack.Instance.Push(UI.PanelStack.UIPanelKind.Custom, tabletPanel, HideTablet);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void HideTablet()
|
||
|
|
{
|
||
|
|
if (debugLogs) Debug.Log("[TabletController] HideTablet");
|
||
|
|
if (tabletPanel != null) tabletPanel.SetActive(false);
|
||
|
|
if (hudCanvas != null) hudCanvas.SetActive(true);
|
||
|
|
|
||
|
|
TogglePlayerControl(true);
|
||
|
|
|
||
|
|
Cursor.lockState = CursorLockMode.Locked;
|
||
|
|
Cursor.visible = false;
|
||
|
|
|
||
|
|
if (UI.PanelStack.UIPanelStack.Instance != null && tabletPanel != null)
|
||
|
|
{
|
||
|
|
UI.PanelStack.UIPanelStack.Instance.Remove(tabletPanel);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void TogglePlayerControl(bool enable)
|
||
|
|
{
|
||
|
|
if (PlayerControlLockService.Instance != null)
|
||
|
|
{
|
||
|
|
if (enable) { PlayerControlLockService.Instance.Unlock(this); }
|
||
|
|
else { PlayerControlLockService.Instance.Lock(this); }
|
||
|
|
}
|
||
|
|
|
||
|
|
if (playerController != null) playerController.enabled = enable;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void OnSendClicked()
|
||
|
|
{
|
||
|
|
if (inputField == null || string.IsNullOrWhiteSpace(inputField.text)) return;
|
||
|
|
if (chatManager == null)
|
||
|
|
{
|
||
|
|
AddMessageToUI("System", "LLM 服务管理器未连接。", true);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
string userMessage = inputField.text;
|
||
|
|
inputField.text = "";
|
||
|
|
|
||
|
|
AddMessageToUI("Player", userMessage, true);
|
||
|
|
|
||
|
|
// 使用 LLMChatManager 发送消息,它会自动选择当前激活的服务
|
||
|
|
chatManager.SendUserMessage(userMessage, (reply, success) =>
|
||
|
|
{
|
||
|
|
AddMessageToUI("AI", reply, false);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private void AddMessageToUI(string role, string text, bool isPlayer)
|
||
|
|
{
|
||
|
|
if (messagePrefab == null || chatContent == null) return;
|
||
|
|
|
||
|
|
GameObject newMsg = Instantiate(messagePrefab, chatContent);
|
||
|
|
TextMeshProUGUI tmp = newMsg.GetComponentInChildren<TextMeshProUGUI>();
|
||
|
|
|
||
|
|
if (tmp != null)
|
||
|
|
{
|
||
|
|
tmp.text = text;
|
||
|
|
}
|
||
|
|
|
||
|
|
var aligner = newMsg.GetComponent<MessageAligner>();
|
||
|
|
if (aligner != null)
|
||
|
|
{
|
||
|
|
aligner.SetAlignment(isPlayer);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 限制 UI 显示数量,防止无限增加导致卡顿 (保留最近的 50 条)
|
||
|
|
int maxUIItems = 50;
|
||
|
|
if (chatContent.childCount > maxUIItems)
|
||
|
|
{
|
||
|
|
Destroy(chatContent.GetChild(0).gameObject);
|
||
|
|
}
|
||
|
|
|
||
|
|
Canvas.ForceUpdateCanvases();
|
||
|
|
ScrollRect scrollRect = chatContent.GetComponentInParent<ScrollRect>();
|
||
|
|
if (scrollRect != null) scrollRect.verticalNormalizedPosition = 0f;
|
||
|
|
}
|
||
|
|
|
||
|
|
private IEnumerator RebuildChatLayoutNextFrame()
|
||
|
|
{
|
||
|
|
yield return new WaitForEndOfFrame();
|
||
|
|
|
||
|
|
if (chatContent == null) yield break;
|
||
|
|
|
||
|
|
foreach (Transform child in chatContent)
|
||
|
|
{
|
||
|
|
var aligner = child.GetComponent<MessageAligner>();
|
||
|
|
if (aligner != null) aligner.Reapply();
|
||
|
|
}
|
||
|
|
|
||
|
|
Canvas.ForceUpdateCanvases();
|
||
|
|
|
||
|
|
RectTransform chatContentRect = chatContent as RectTransform;
|
||
|
|
if (chatContentRect != null) LayoutRebuilder.ForceRebuildLayoutImmediate(chatContentRect);
|
||
|
|
|
||
|
|
ScrollRect scrollRect = chatContent.GetComponentInParent<ScrollRect>();
|
||
|
|
if (scrollRect != null) scrollRect.verticalNormalizedPosition = 0f;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|