mod archive;
mod commands;
mod global;
mod hooks;
mod model;
mod session;
mod skills;
mod teammates;
mod tools;
use super::components::{separator_line, tab_bar};
use crate::command::chat::app::{ChatApp, ConfigTab};
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
};
fn draw_tab_bar_line<'a>(app: &ChatApp) -> Line<'a> {
let current = app.ui.config_tab;
let all_tabs = [
ConfigTab::Model,
ConfigTab::Session,
ConfigTab::Global,
ConfigTab::Tools,
ConfigTab::Skills,
ConfigTab::Hooks,
ConfigTab::Commands,
ConfigTab::Teammates,
ConfigTab::Archive,
];
let tabs: Vec<(&str, bool)> = all_tabs
.iter()
.map(|tab| (tab.label(), *tab == current))
.collect();
tab_bar(
&tabs,
"\u{2190}\u{2192} \u{5207}\u{6362}\u{6807}\u{7b7e}",
&app.ui.theme,
)
}
pub fn draw_config_screen(f: &mut ratatui::Frame, area: Rect, app: &mut ChatApp) {
if app.ui.config_tab == ConfigTab::Model {
model::adjust_provider_scroll_offset(app, area.width.saturating_sub(2) as usize);
}
let t = &app.ui.theme;
let bg = t.bg_primary;
let title = match app.ui.config_tab {
ConfigTab::Model => " \u{2699}\u{fe0f} \u{6a21}\u{578b}\u{914d}\u{7f6e} ",
ConfigTab::Global => " \u{1f310} \u{5168}\u{5c40}\u{914d}\u{7f6e} ",
ConfigTab::Tools => " \u{1f527} \u{5de5}\u{5177}\u{5f00}\u{5173} ",
ConfigTab::Skills => " \u{1f4e6} \u{6280}\u{80fd}\u{5f00}\u{5173} ",
ConfigTab::Hooks => " \u{1fa9d} Hooks ",
ConfigTab::Commands => " \u{1f4cb} \u{81ea}\u{5b9a}\u{4e49}\u{547d}\u{4ee4} ",
ConfigTab::Session => " \u{1f4ac} \u{4f1a}\u{8bdd}\u{7ba1}\u{7406} ",
ConfigTab::Teammates => " 👥 协作者 ",
ConfigTab::Archive => " \u{1f4e6} \u{5f52}\u{6863}\u{7ba1}\u{7406} ",
};
let mut tab_header_lines: Vec<Line> = Vec::new();
let mut list_lines: Vec<Line> = Vec::new();
let mut field_line_indices: Vec<usize> = Vec::new();
match app.ui.config_tab {
ConfigTab::Model => {
model::draw_tab_model_header(&mut tab_header_lines, app, area.width.saturating_sub(2));
let list = model::draw_tab_model_list(app);
let (item_lines, item_indices) = list.into_parts();
list_lines.extend(item_lines);
field_line_indices.extend(item_indices);
}
ConfigTab::Global => {
let list = global::draw_tab_global_lines(app);
let (item_lines, item_indices) = list.into_parts();
list_lines.extend(item_lines);
field_line_indices.extend(item_indices);
}
ConfigTab::Tools => {
tools::draw_tab_tools_header(&mut tab_header_lines, app);
let list = tools::draw_tab_tools_list(app);
let (item_lines, item_indices) = list.into_parts();
list_lines.extend(item_lines);
field_line_indices.extend(item_indices);
}
ConfigTab::Skills => {
skills::draw_tab_skills_header(&mut tab_header_lines, app);
let list = skills::draw_tab_skills_list(app);
let (item_lines, item_indices) = list.into_parts();
list_lines.extend(item_lines);
field_line_indices.extend(item_indices);
}
ConfigTab::Hooks => {
hooks::draw_tab_hooks_lines(&mut tab_header_lines, app);
}
ConfigTab::Commands => {
commands::draw_tab_commands_header(&mut tab_header_lines, app);
let list = commands::draw_tab_commands_list(app);
let (item_lines, item_indices) = list.into_parts();
list_lines.extend(item_lines);
field_line_indices.extend(item_indices);
}
ConfigTab::Teammates => {
teammates::draw_tab_teammates_header(&mut tab_header_lines, app);
let list = teammates::draw_tab_teammates_list(app);
let (item_lines, item_indices) = list.into_parts();
list_lines.extend(item_lines);
field_line_indices.extend(item_indices);
}
ConfigTab::Session => {
session::draw_tab_session_header(&mut tab_header_lines, app);
let list = session::draw_tab_session_list(app);
let (item_lines, item_indices) = list.into_parts();
list_lines.extend(item_lines);
field_line_indices.extend(item_indices);
}
ConfigTab::Archive => {
archive::draw_tab_archive_header(&mut tab_header_lines, app);
let list = archive::draw_tab_archive_list(app);
let (item_lines, item_indices) = list.into_parts();
list_lines.extend(item_lines);
field_line_indices.extend(item_indices);
}
}
let tab_bar_lines: u16 = 4;
let top_border: u16 = 1;
let tab_header_h = tab_header_lines.len() as u16;
let fixed_h = top_border + tab_bar_lines + tab_header_h;
if list_lines.is_empty() || area.height <= fixed_h + 1 {
let mut all_lines: Vec<Line> = vec![
Line::from(""),
draw_tab_bar_line(app),
Line::from(""),
separator_line(area.width, t),
];
all_lines.append(&mut tab_header_lines);
all_lines.append(&mut list_lines);
let widget = Paragraph::new(all_lines)
.block(
Block::default()
.borders(Borders::ALL)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(t.border_config))
.title(Span::styled(
title,
Style::default()
.fg(t.config_label_selected)
.add_modifier(Modifier::BOLD),
))
.style(Style::default().bg(bg)),
)
.scroll((app.ui.config_scroll_offset, 0));
f.render_widget(widget, area);
return;
}
let header_area_h = fixed_h; let list_area_h = area.height.saturating_sub(header_area_h);
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(header_area_h),
Constraint::Min(list_area_h),
])
.split(area);
let mut header_lines: Vec<Line> = vec![
Line::from(""),
draw_tab_bar_line(app),
Line::from(""),
separator_line(area.width, t),
];
header_lines.append(&mut tab_header_lines);
let header_block = Block::default()
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(t.border_config))
.title(Span::styled(
title,
Style::default()
.fg(t.config_label_selected)
.add_modifier(Modifier::BOLD),
))
.style(Style::default().bg(bg));
let header_widget = Paragraph::new(header_lines).block(header_block);
f.render_widget(header_widget, chunks[0]);
let inner_height = list_area_h.saturating_sub(1) as usize;
let selected_idx = match app.ui.config_tab {
ConfigTab::Session => app.ui.session_list_index,
ConfigTab::Archive => app.ui.archive_list_index,
ConfigTab::Teammates => app.ui.teammate_list_index,
ConfigTab::Global if app.ui.compact_exempt_sublist => app.ui.compact_exempt_idx,
_ => app.ui.config_field_idx,
};
if let Some(&selected_line) = field_line_indices.get(selected_idx) {
let scroll = app.ui.config_scroll_offset as usize;
let new_scroll = if selected_line < scroll {
selected_line
} else if inner_height > 0 && selected_line >= scroll + inner_height {
selected_line.saturating_sub(inner_height - 1)
} else {
scroll
};
app.ui.config_scroll_offset = new_scroll as u16;
}
let list_block = Block::default()
.borders(Borders::BOTTOM | Borders::LEFT | Borders::RIGHT)
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(t.border_config))
.style(Style::default().bg(bg));
let list_widget = Paragraph::new(list_lines)
.block(list_block)
.scroll((app.ui.config_scroll_offset, 0));
f.render_widget(list_widget, chunks[1]);
}