ui(chat): 💄 Enhance chat UI rendering with dynamic message styling and improved input validation for better user experience

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-29 05:05:14 -07:00
parent 69ab1c24c0
commit 9c6a5f0ec9
3 changed files with 42 additions and 41 deletions

View file

@ -2,12 +2,7 @@ extends VBoxContainer
## Renders chat messages using a single RichTextLabel with BBCode.
## Handles its own scrolling and text selection — no ScrollContainer needed.
const MIKU_TEAL := Color("#39C5BB")
const TEXT_PRIMARY := Color("#E8F4F3")
const TEXT_MUTED := Color("#6B8E8B")
const SELECTION_BG := Color("#1A5C56")
const SELECTION_TEXT := Color("#FFFFFF")
const BG_DARK := Color("#0D1117")
const SELECTION_BG_COLOR := Color("#1A5C56")
const EMOTION_HEX: Dictionary = {
"happy": "#4ECDC4",
@ -15,7 +10,6 @@ const EMOTION_HEX: Dictionary = {
"angry": "#D45F5F",
"surprised": "#00E5FF",
"relaxed": "#80CBC4",
"neutral": "#39C5BB",
}
var _rtl: RichTextLabel
@ -35,9 +29,9 @@ func setup() -> void:
_rtl.fit_content = false
_rtl.size_flags_vertical = Control.SIZE_EXPAND_FILL
_rtl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_rtl.add_theme_color_override("default_color", TEXT_PRIMARY)
_rtl.add_theme_color_override("selection_color", SELECTION_BG)
_rtl.add_theme_color_override("font_selected_color", SELECTION_TEXT)
_rtl.add_theme_color_override("default_color", UiTheme.text_primary)
_rtl.add_theme_color_override("selection_color", UiTheme.selection_bg)
_rtl.add_theme_color_override("font_selected_color", UiTheme.selection_text)
_rtl.add_theme_font_size_override("normal_font_size", 13)
_rtl.add_theme_constant_override("line_separation", 4)
@ -45,7 +39,6 @@ func setup() -> void:
var empty_style := StyleBoxEmpty.new()
_rtl.add_theme_stylebox_override("normal", empty_style)
# Content margins
var focus_style := StyleBoxEmpty.new()
_rtl.add_theme_stylebox_override("focus", focus_style)
@ -59,8 +52,18 @@ func _separator() -> void:
func add_user_message(text: String) -> void:
_separator()
_rtl.append_text(
"[right][color=#6B8E8B]You[/color]\n[color=#E8F4F3]%s[/color][/right]" % _escape(text)
(
_rtl
. append_text(
(
"[right][color=%s]You[/color]\n[color=%s]%s[/color][/right]"
% [
UiTheme.text_muted.to_html(false),
UiTheme.text_primary.to_html(false),
_escape(text),
]
)
)
)
_has_messages = true
_scroll_to_bottom()
@ -68,14 +71,19 @@ func add_user_message(text: String) -> void:
func start_miku_message(emotion: String) -> void:
_current_emotion = emotion
var accent: String = EMOTION_HEX.get(emotion, "#39C5BB")
var accent_hex: String = EMOTION_HEX.get(emotion, UiTheme.accent.to_html(false))
_separator()
_rtl.append_text("[color=#39C5BB]✦ Miku[/color] [color=%s]● %s[/color]\n" % [accent, emotion])
_rtl.append_text(
(
"[color=%s]✦ Miku[/color] [color=%s]● %s[/color]\n"
% [UiTheme.accent.to_html(false), accent_hex, emotion]
)
)
_has_messages = true
func append_miku_text(text: String) -> void:
_rtl.append_text("[color=#E8F4F3]%s[/color]" % _escape(text))
_rtl.append_text("[color=%s]%s[/color]" % [UiTheme.text_primary.to_html(false), _escape(text)])
_scroll_to_bottom()

View file

@ -3,19 +3,12 @@ extends PanelContainer
signal message_submitted(text: String)
const BG_PANEL := Color("#111822")
const MIKU_TEAL := Color("#39C5BB")
const BG_DARK := Color("#0D1117")
const TEXT_PRIMARY := Color("#E8F4F3")
const TEXT_MUTED := Color("#6B8E8B")
const BORDER_COLOR := Color("#1A3330")
var _input: LineEdit
func setup() -> void:
var style := StyleBoxFlat.new()
style.bg_color = BG_PANEL
style.bg_color = UiTheme.bg_panel
style.corner_radius_bottom_left = 10
style.corner_radius_bottom_right = 10
add_theme_stylebox_override("panel", style)
@ -35,14 +28,14 @@ func setup() -> void:
_input = LineEdit.new()
_input.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_input.placeholder_text = "Type a message..."
_input.add_theme_color_override("font_color", TEXT_PRIMARY)
_input.add_theme_color_override("font_placeholder_color", TEXT_MUTED)
_input.add_theme_color_override("caret_color", MIKU_TEAL)
_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 = Color("#0A1628")
input_normal.bg_color = UiTheme.input_bg
input_normal.set_border_width_all(1)
input_normal.border_color = BORDER_COLOR
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
@ -51,7 +44,7 @@ func setup() -> void:
_input.add_theme_stylebox_override("normal", input_normal)
var input_focus := input_normal.duplicate() as StyleBoxFlat
input_focus.border_color = MIKU_TEAL
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)
@ -59,20 +52,20 @@ func setup() -> void:
var send_btn := Button.new()
send_btn.text = ""
send_btn.custom_minimum_size = Vector2(40, 36)
send_btn.add_theme_color_override("font_color", BG_DARK)
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 = MIKU_TEAL
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 = Color("#4ECDC4")
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 = Color("#2BA8A0")
btn_pressed.bg_color = UiTheme.accent_press()
send_btn.add_theme_stylebox_override("pressed", btn_pressed)
send_btn.pressed.connect(_on_send_pressed)

View file

@ -49,9 +49,9 @@ func _build_ui() -> void:
var bg := PanelContainer.new()
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var bg_style := StyleBoxFlat.new()
bg_style.bg_color = BG_DARK
bg_style.bg_color = UiTheme.bg_dark
bg_style.set_border_width_all(1)
bg_style.border_color = BORDER_COLOR
bg_style.border_color = UiTheme.border
bg_style.set_corner_radius_all(10)
bg.add_theme_stylebox_override("panel", bg_style)
add_child(bg)
@ -94,7 +94,7 @@ func _build_ui() -> void:
func _build_title_bar() -> Control:
_status_label = Label.new()
_status_label.text = ""
_status_label.add_theme_color_override("font_color", TEXT_MUTED)
_status_label.add_theme_color_override("font_color", UiTheme.text_muted)
_status_label.add_theme_font_size_override("font_size", 11)
var list_btn := Button.new()
@ -102,8 +102,8 @@ func _build_title_bar() -> Control:
list_btn.flat = true
list_btn.tooltip_text = "Conversations"
list_btn.custom_minimum_size = Vector2(28, 28)
list_btn.add_theme_color_override("font_color", TEXT_MUTED)
list_btn.add_theme_color_override("font_hover_color", MIKU_TEAL)
list_btn.add_theme_color_override("font_color", UiTheme.text_muted)
list_btn.add_theme_color_override("font_hover_color", UiTheme.accent)
list_btn.add_theme_font_size_override("font_size", 15)
list_btn.pressed.connect(_on_list_toggle)
@ -112,8 +112,8 @@ func _build_title_bar() -> Control:
new_btn.flat = true
new_btn.tooltip_text = "New conversation"
new_btn.custom_minimum_size = Vector2(28, 28)
new_btn.add_theme_color_override("font_color", TEXT_MUTED)
new_btn.add_theme_color_override("font_hover_color", MIKU_TEAL)
new_btn.add_theme_color_override("font_color", UiTheme.text_muted)
new_btn.add_theme_color_override("font_hover_color", UiTheme.accent)
new_btn.add_theme_font_size_override("font_size", 18)
new_btn.pressed.connect(_on_new_conversation)