using System; using System.Collections; using System.Collections.Generic; using LLM; using Michsky.UI.Reach; using TMPro; using UnityEngine; using UnityEngine.Serialization; using UnityEngine.SceneManagement; using UnityEngine.UI; namespace UI.Ending { public class EndingUIScreenController : MonoBehaviour { [SerializeField] private bool debugLogs = true; public enum EndingOutcome { Escaped, Fainted } [Header("Scene")] [SerializeField] private int mainMenuSceneBuildIndex = 1; [SerializeField] private bool beginPreloadOnEnable = true; [Header("UI")] [FormerlySerializedAs("summaryText")] [SerializeField] private TMP_Text summarayText; [SerializeField] private TMP_Text informationText; [SerializeField] private string requestingText = "正在生成总结..."; [Header("Information Text")] [SerializeField] private EndingOutcome outcome = EndingOutcome.Escaped; [SerializeField] private bool applyOutcomeTextOnEnable = true; [TextArea] [SerializeField] private string escapedInformationString = "(成功逃离时的提示词,自己填写)"; [TextArea] [SerializeField] private string faintedInformationString = "(倒下/昏倒时的提示词,自己填写)"; [Header("Buttons")] [SerializeField] private ButtonManager returnToMenuButton; [SerializeField] private Button returnToMenuButtonUGUI; [Header("AI Summary")] [SerializeField] private bool requestAISummaryOnEnable = true; [SerializeField] private LLMChatManager chatManager; [SerializeField] private int maxHistoryMessages = 30; [SerializeField] private bool includeKnownFactsFromContextMemory = true; [TextArea] [SerializeField] private string summarySystemPrompt = "你是一个游戏结局总结助手。你需要根据对话历史与已知事实,生成一段简短的结局总结,面向玩家。"; [TextArea] [SerializeField] private string summaryUserPromptTemplate = "请为玩家生成结局总结。\n" + "要求:\n" + "- 80~160 字\n" + "- 只输出严格 JSON:{\"summary\":\"...\"}\n" + "- 不要输出代码块,不要多余字段\n\n" + "已知事实:\n{KNOWN_FACTS}\n\n" + "对话历史:\n{CHAT_HISTORY}\n"; [Header("AI Summary Debug")] [SerializeField] private ButtonManager debugSummaryButton; [SerializeField] private Button debugSummaryButtonUGUI; [TextArea] [SerializeField] private string debugSummaryString = "(这里是预设的 Debug 总结字符串)"; private AsyncOperation preloadOperation; private bool isWired; private Coroutine summaryCoroutine; [Serializable] private class SummaryResponse { public string summary; } private void Awake() { if (summarayText == null) { summarayText = FindTMPByNameHint("Summaray", "Summary"); } if (informationText == null) { informationText = FindTMPByNameHint("Information", "Info"); } if (chatManager == null) { chatManager = FindObjectOfType(); } WireOnce(); } private void OnEnable() { WireOnce(); if (beginPreloadOnEnable) { BeginPreloadMainMenu(); } if (applyOutcomeTextOnEnable) { ApplyOutcomeInformationText(); } if (requestAISummaryOnEnable) { GenerateSummaryWithAI(); } } public void BeginPreloadMainMenu() { if (preloadOperation != null) { return; } if (mainMenuSceneBuildIndex < 0 || mainMenuSceneBuildIndex >= SceneManager.sceneCountInBuildSettings) { if (debugLogs) Debug.Log($"[EndingUI] Invalid mainMenuSceneBuildIndex={mainMenuSceneBuildIndex}"); return; } StartCoroutine(PreloadRoutine()); } private IEnumerator PreloadRoutine() { preloadOperation = SceneManager.LoadSceneAsync(mainMenuSceneBuildIndex); if (preloadOperation == null) { yield break; } preloadOperation.allowSceneActivation = false; while (preloadOperation.progress < 0.9f) { yield return null; } if (debugLogs) Debug.Log("[EndingUI] Main menu preload ready (progress>=0.9)."); } private bool IsPreloadReady() { return preloadOperation != null && preloadOperation.progress >= 0.9f; } public void ReturnToMainMenu() { if (mainMenuSceneBuildIndex < 0 || mainMenuSceneBuildIndex >= SceneManager.sceneCountInBuildSettings) { if (debugLogs) Debug.Log($"[EndingUI] Invalid mainMenuSceneBuildIndex={mainMenuSceneBuildIndex}"); return; } if (preloadOperation != null) { preloadOperation.allowSceneActivation = true; return; } SceneManager.LoadSceneAsync(mainMenuSceneBuildIndex); } public void GenerateSummaryWithAI() { if (summarayText != null && !string.IsNullOrWhiteSpace(requestingText)) { summarayText.text = requestingText; } if (summaryCoroutine != null) { StopCoroutine(summaryCoroutine); summaryCoroutine = null; } summaryCoroutine = StartCoroutine(GenerateSummaryRoutine()); } private IEnumerator GenerateSummaryRoutine() { if (chatManager == null) { chatManager = FindObjectOfType(); } ILLMService service = GetActiveService(); if (service == null) { ApplySummaryText("AI 服务未连接。"); yield break; } string knownFacts = includeKnownFactsFromContextMemory ? BuildKnownFactsText(chatManager.contextMemory) : "无"; string historyText = BuildHistoryText(chatManager.fullChatLog, maxHistoryMessages); string userPrompt = summaryUserPromptTemplate.Replace("{KNOWN_FACTS}", knownFacts).Replace("{CHAT_HISTORY}", historyText); List messages = new List { new Message { role = "system", content = summarySystemPrompt }, new Message { role = "user", content = userPrompt } }; string result = null; bool ok = false; yield return service.SendStatelessMessage(messages, (reply, success) => { result = reply; ok = success; }); if (!ok) { ApplySummaryText(string.IsNullOrWhiteSpace(result) ? "AI 总结失败。" : result); yield break; } string summary = TryParseSummaryJson(result); ApplySummaryText(string.IsNullOrWhiteSpace(summary) ? result : summary); } public void ApplyDebugSummary() { ApplySummaryText(debugSummaryString); } public void SetOutcome(EndingOutcome newOutcome) { outcome = newOutcome; ApplyOutcomeInformationText(); } public void SetOutcomeEscaped() { SetOutcome(EndingOutcome.Escaped); } public void SetOutcomeFainted() { SetOutcome(EndingOutcome.Fainted); } public void ApplyOutcomeInformationText() { switch (outcome) { case EndingOutcome.Escaped: ApplyInformationText(escapedInformationString); break; case EndingOutcome.Fainted: ApplyInformationText(faintedInformationString); break; } } private void ApplySummaryText(string value) { if (summarayText == null) { return; } summarayText.text = value ?? string.Empty; } private void ApplyInformationText(string value) { if (informationText == null) { return; } informationText.text = value ?? string.Empty; } private ILLMService GetActiveService() { if (chatManager == null) { return null; } switch (chatManager.currentProvider) { case LLMChatManager.LLMProvider.DeepSeek: return chatManager.deepSeekService; case LLMChatManager.LLMProvider.Doubao: return chatManager.doubaoService; default: return null; } } private string BuildKnownFactsText(ContextMemoryManager memory) { if (memory == null || memory.facts == null || memory.facts.Count == 0) { return "无"; } System.Text.StringBuilder sb = new System.Text.StringBuilder(); int count = Mathf.Min(30, memory.facts.Count); for (int i = 0; i < count; i++) { var fact = memory.facts[i]; sb.Append(i + 1).Append(". [").Append(fact.key).Append("]: ").Append(fact.value).Append('\n'); } return sb.ToString(); } private string BuildHistoryText(List fullLog, int maxCount) { if (fullLog == null || fullLog.Count == 0) { return "无"; } int take = Mathf.Max(1, maxCount); int start = Mathf.Max(0, fullLog.Count - take); System.Text.StringBuilder sb = new System.Text.StringBuilder(); for (int i = start; i < fullLog.Count; i++) { var msg = fullLog[i]; if (msg == null) { continue; } if (msg.role != "user" && msg.role != "assistant") { continue; } sb.Append(msg.role).Append(": ").Append(msg.content).Append('\n'); } return sb.ToString(); } private string TryParseSummaryJson(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return null; } string json = raw.Trim(); json = json.Replace("```json", "").Replace("```", "").Trim(); SummaryResponse parsed = null; try { parsed = JsonUtility.FromJson(json); } catch { parsed = null; } if (parsed == null || string.IsNullOrWhiteSpace(parsed.summary)) { return null; } return parsed.summary.Trim(); } private TMP_Text FindTMPByNameHint(string hint1, string hint2) { TMP_Text[] all = GetComponentsInChildren(true); if (all == null || all.Length == 0) { return null; } for (int i = 0; i < all.Length; i++) { var t = all[i]; if (t == null) { continue; } string n = t.gameObject.name; if (!string.IsNullOrEmpty(n) && (n.IndexOf(hint1, StringComparison.OrdinalIgnoreCase) >= 0 || n.IndexOf(hint2, StringComparison.OrdinalIgnoreCase) >= 0)) { return t; } } return all[0]; } private void WireOnce() { if (isWired) { return; } if (returnToMenuButton != null) { returnToMenuButton.onClick.AddListener(ReturnToMainMenu); } if (returnToMenuButtonUGUI != null) { returnToMenuButtonUGUI.onClick.AddListener(ReturnToMainMenu); } if (debugSummaryButton != null) { debugSummaryButton.onClick.AddListener(ApplyDebugSummary); } if (debugSummaryButtonUGUI != null) { debugSummaryButtonUGUI.onClick.AddListener(ApplyDebugSummary); } isWired = true; } } }