feat(chat-specific): ✨ Refactor and enhance chat UI with new message display, input handling, window management, and conversation listing features
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
ebfd76307b
commit
eb22858ea2
4 changed files with 40 additions and 133 deletions
|
|
@ -3,26 +3,9 @@ extends VBoxContainer
|
|||
## Handles its own scrolling and text selection — no ScrollContainer needed.
|
||||
## Supports mark_speaking() to underline the currently-playing TTS sentence.
|
||||
|
||||
const SELECTION_BG_COLOR := Color("#1A5C56")
|
||||
|
||||
const EMOTION_HEX: Dictionary = {
|
||||
"happy": "#4ECDC4",
|
||||
"sad": "#7B9EBD",
|
||||
"angry": "#D45F5F",
|
||||
"surprised": "#00E5FF",
|
||||
"relaxed": "#80CBC4",
|
||||
}
|
||||
|
||||
class _Msg:
|
||||
var role: String # "user" | "assistant" | "error"
|
||||
var emotion: String # assistant only
|
||||
var parts: Array[String] # sentences (assistant) or [text] (user/error)
|
||||
|
||||
func _init(p_role: String, p_emotion: String = "") -> void:
|
||||
role = p_role
|
||||
emotion = p_emotion
|
||||
parts = []
|
||||
|
||||
var message_count: int:
|
||||
get:
|
||||
return _messages.size()
|
||||
|
||||
var _rtl: RichTextLabel
|
||||
var _messages: Array[_Msg] = []
|
||||
|
|
@ -58,11 +41,6 @@ func setup() -> void:
|
|||
add_child(_rtl)
|
||||
|
||||
|
||||
var message_count: int:
|
||||
get:
|
||||
return _messages.size()
|
||||
|
||||
|
||||
func add_user_message(text: String) -> void:
|
||||
var msg := _Msg.new("user")
|
||||
msg.parts.append(text)
|
||||
|
|
@ -140,7 +118,8 @@ func _render() -> void:
|
|||
)
|
||||
)
|
||||
"assistant":
|
||||
var accent_hex: String = EMOTION_HEX.get(msg.emotion, UiTheme.accent.to_html(false))
|
||||
var accent_color := UiTheme.emotion_colors.get(msg.emotion, UiTheme.accent) as Color
|
||||
var accent_hex: String = accent_color.to_html(false)
|
||||
_rtl.append_text(
|
||||
(
|
||||
"[color=%s]✦ Miku[/color] [color=%s]● %s[/color]\n"
|
||||
|
|
@ -151,11 +130,17 @@ func _render() -> void:
|
|||
var part: String = msg.parts[j]
|
||||
if i == _speaking_msg and j == _speaking_part:
|
||||
_rtl.append_text(
|
||||
"[u][color=%s]%s[/color][/u]" % [UiTheme.text_primary.to_html(false), _escape(part)]
|
||||
(
|
||||
"[u][color=%s]%s[/color][/u]"
|
||||
% [UiTheme.text_primary.to_html(false), _escape(part)]
|
||||
)
|
||||
)
|
||||
else:
|
||||
_rtl.append_text(
|
||||
"[color=%s]%s[/color]" % [UiTheme.text_primary.to_html(false), _escape(part)]
|
||||
(
|
||||
"[color=%s]%s[/color]"
|
||||
% [UiTheme.text_primary.to_html(false), _escape(part)]
|
||||
)
|
||||
)
|
||||
"error":
|
||||
_rtl.append_text(
|
||||
|
|
@ -185,7 +170,8 @@ func _append_user_block(text: String) -> void:
|
|||
func _append_miku_header(emotion: String) -> void:
|
||||
if _rtl.get_parsed_text().length() > 0:
|
||||
_rtl.append_text("\n\n")
|
||||
var accent_hex: String = EMOTION_HEX.get(emotion, UiTheme.accent.to_html(false))
|
||||
var accent_color := UiTheme.emotion_colors.get(emotion, UiTheme.accent) as Color
|
||||
var accent_hex: String = accent_color.to_html(false)
|
||||
_rtl.append_text(
|
||||
(
|
||||
"[color=%s]✦ Miku[/color] [color=%s]● %s[/color]\n"
|
||||
|
|
@ -210,3 +196,14 @@ func _do_scroll() -> void:
|
|||
|
||||
func _escape(text: String) -> String:
|
||||
return text.replace("[", "[lb]")
|
||||
|
||||
|
||||
class _Msg:
|
||||
var role: String # "user" | "assistant" | "error"
|
||||
var emotion: String # assistant only
|
||||
var parts: Array[String] # sentences (assistant) or [text] (user/error)
|
||||
|
||||
func _init(p_role: String, p_emotion: String = "") -> void:
|
||||
role = p_role
|
||||
emotion = p_emotion
|
||||
parts = []
|
||||
|
|
|
|||
|
|
@ -1,85 +1,2 @@
|
|||
extends PanelContainer
|
||||
## Chat input bar — text field with send button, Miku-themed.
|
||||
|
||||
signal message_submitted(text: String)
|
||||
|
||||
var _input: LineEdit
|
||||
|
||||
|
||||
func setup() -> void:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = UiTheme.bg_panel
|
||||
style.corner_radius_bottom_left = 10
|
||||
style.corner_radius_bottom_right = 10
|
||||
add_theme_stylebox_override("panel", style)
|
||||
custom_minimum_size.y = 52
|
||||
|
||||
var margin := MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 12)
|
||||
margin.add_theme_constant_override("margin_right", 12)
|
||||
margin.add_theme_constant_override("margin_top", 8)
|
||||
margin.add_theme_constant_override("margin_bottom", 8)
|
||||
add_child(margin)
|
||||
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.add_theme_constant_override("separation", 8)
|
||||
margin.add_child(hbox)
|
||||
|
||||
_input = LineEdit.new()
|
||||
_input.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_input.placeholder_text = "Type a message..."
|
||||
_input.add_theme_color_override("font_color", UiTheme.text_primary)
|
||||
_input.add_theme_color_override("font_placeholder_color", UiTheme.text_muted)
|
||||
_input.add_theme_color_override("caret_color", UiTheme.accent)
|
||||
|
||||
var input_normal := StyleBoxFlat.new()
|
||||
input_normal.bg_color = UiTheme.input_bg
|
||||
input_normal.set_border_width_all(1)
|
||||
input_normal.border_color = UiTheme.border
|
||||
input_normal.set_corner_radius_all(6)
|
||||
input_normal.content_margin_left = 10
|
||||
input_normal.content_margin_right = 10
|
||||
input_normal.content_margin_top = 6
|
||||
input_normal.content_margin_bottom = 6
|
||||
_input.add_theme_stylebox_override("normal", input_normal)
|
||||
|
||||
var input_focus := input_normal.duplicate() as StyleBoxFlat
|
||||
input_focus.border_color = UiTheme.accent
|
||||
_input.add_theme_stylebox_override("focus", input_focus)
|
||||
_input.text_submitted.connect(_on_text_submitted)
|
||||
hbox.add_child(_input)
|
||||
|
||||
var send_btn := Button.new()
|
||||
send_btn.text = "→"
|
||||
send_btn.custom_minimum_size = Vector2(40, 36)
|
||||
send_btn.add_theme_color_override("font_color", UiTheme.bg_dark)
|
||||
send_btn.add_theme_font_size_override("font_size", 18)
|
||||
|
||||
var btn_normal := StyleBoxFlat.new()
|
||||
btn_normal.bg_color = UiTheme.accent
|
||||
btn_normal.set_corner_radius_all(6)
|
||||
send_btn.add_theme_stylebox_override("normal", btn_normal)
|
||||
|
||||
var btn_hover := btn_normal.duplicate() as StyleBoxFlat
|
||||
btn_hover.bg_color = UiTheme.accent_hover()
|
||||
send_btn.add_theme_stylebox_override("hover", btn_hover)
|
||||
|
||||
var btn_pressed := btn_normal.duplicate() as StyleBoxFlat
|
||||
btn_pressed.bg_color = UiTheme.accent_press()
|
||||
send_btn.add_theme_stylebox_override("pressed", btn_pressed)
|
||||
|
||||
send_btn.pressed.connect(_on_send_pressed)
|
||||
hbox.add_child(send_btn)
|
||||
|
||||
|
||||
func _on_text_submitted(text: String) -> void:
|
||||
text = text.strip_edges()
|
||||
if text.is_empty():
|
||||
return
|
||||
_input.text = ""
|
||||
message_submitted.emit(text)
|
||||
_input.grab_focus()
|
||||
|
||||
|
||||
func _on_send_pressed() -> void:
|
||||
_on_text_submitted(_input.text)
|
||||
extends "res://addons/godot-ui/chat/input_bar.gd"
|
||||
## Chat input bar — delegates to godot-ui InputBar component.
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ func _ready() -> void:
|
|||
min_size = Vector2i(340, 480)
|
||||
size = Vector2i(380, 560)
|
||||
super._ready()
|
||||
setup()
|
||||
|
||||
|
||||
func _build_ui() -> void:
|
||||
|
|
@ -64,7 +65,7 @@ func _build_ui() -> void:
|
|||
root.add_theme_constant_override("separation", 0)
|
||||
bg.add_child(root)
|
||||
|
||||
root.add_child(_build_title_bar())
|
||||
root.add_child(_build_chat_title_bar())
|
||||
root.add_child(_build_divider())
|
||||
|
||||
_conversation_list = ConversationListScript.new()
|
||||
|
|
@ -94,7 +95,7 @@ func _build_ui() -> void:
|
|||
root.add_child(_input_bar)
|
||||
|
||||
|
||||
func _build_title_bar() -> Control:
|
||||
func _build_chat_title_bar() -> Control:
|
||||
_status_label = Label.new()
|
||||
_status_label.text = ""
|
||||
_status_label.add_theme_color_override("font_color", UiTheme.text_muted)
|
||||
|
|
@ -120,7 +121,7 @@ func _build_title_bar() -> Control:
|
|||
new_btn.add_theme_font_size_override("font_size", 18)
|
||||
new_btn.pressed.connect(_on_new_conversation)
|
||||
|
||||
return _build_panel_title_bar("✦ MIKU", [_status_label, list_btn, new_btn])
|
||||
return _build_title_bar("✦ MIKU", [_status_label, list_btn, new_btn])
|
||||
|
||||
|
||||
func replay_messages(messages: Array[Dictionary]) -> void:
|
||||
|
|
|
|||
|
|
@ -2,14 +2,6 @@ extends VBoxContainer
|
|||
## Collapsible conversation history list for the chat window.
|
||||
## Shows recent conversations, highlights the active one, emits switch signals.
|
||||
|
||||
const BG_DARK := Color("#0D1117")
|
||||
const BG_PANEL := Color("#111822")
|
||||
const BG_HOVER := Color("#162230")
|
||||
const MIKU_TEAL := Color("#39C5BB")
|
||||
const TEXT_PRIMARY := Color("#E8F4F3")
|
||||
const TEXT_MUTED := Color("#6B8E8B")
|
||||
const BORDER_COLOR := Color("#1A3330")
|
||||
|
||||
const MAX_VISIBLE: int = 10
|
||||
|
||||
var _item_container: VBoxContainer
|
||||
|
|
@ -22,9 +14,9 @@ func setup() -> void:
|
|||
|
||||
var wrapper := PanelContainer.new()
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = BG_PANEL
|
||||
style.bg_color = UiTheme.bg_panel
|
||||
style.set_border_width_all(1)
|
||||
style.border_color = BORDER_COLOR
|
||||
style.border_color = UiTheme.border
|
||||
style.content_margin_left = 6
|
||||
style.content_margin_right = 6
|
||||
style.content_margin_top = 6
|
||||
|
|
@ -74,7 +66,7 @@ func refresh() -> void:
|
|||
if conversations.is_empty():
|
||||
var empty_label := Label.new()
|
||||
empty_label.text = "No conversations yet"
|
||||
empty_label.add_theme_color_override("font_color", TEXT_MUTED)
|
||||
empty_label.add_theme_color_override("font_color", UiTheme.text_muted)
|
||||
empty_label.add_theme_font_size_override("font_size", 11)
|
||||
_item_container.add_child(empty_label)
|
||||
return
|
||||
|
|
@ -109,16 +101,16 @@ func _build_item(
|
|||
btn.text = display_title + suffix
|
||||
|
||||
if is_active:
|
||||
btn.add_theme_color_override("font_color", MIKU_TEAL)
|
||||
btn.add_theme_color_override("font_hover_color", MIKU_TEAL)
|
||||
btn.add_theme_color_override("font_color", UiTheme.accent)
|
||||
btn.add_theme_color_override("font_hover_color", UiTheme.accent)
|
||||
else:
|
||||
btn.add_theme_color_override("font_color", TEXT_PRIMARY)
|
||||
btn.add_theme_color_override("font_hover_color", MIKU_TEAL)
|
||||
btn.add_theme_color_override("font_color", UiTheme.text_primary)
|
||||
btn.add_theme_color_override("font_hover_color", UiTheme.accent)
|
||||
|
||||
btn.add_theme_font_size_override("font_size", 12)
|
||||
|
||||
var hover_style := StyleBoxFlat.new()
|
||||
hover_style.bg_color = BG_HOVER
|
||||
hover_style.bg_color = UiTheme.item_hover
|
||||
hover_style.set_corner_radius_all(4)
|
||||
btn.add_theme_stylebox_override("hover", hover_style)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue