diff --git a/assets/icons/radix/maximize.svg b/assets/icons/radix/maximize.svg new file mode 100644 index 0000000000..f37f6a2087 --- /dev/null +++ b/assets/icons/radix/maximize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/radix/minimize.svg b/assets/icons/radix/minimize.svg new file mode 100644 index 0000000000..ec78f152e1 --- /dev/null +++ b/assets/icons/radix/minimize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs index 4d300230e1..35c88486f7 100644 --- a/crates/ai/src/assistant.rs +++ b/crates/ai/src/assistant.rs @@ -2061,6 +2061,8 @@ impl ConversationEditor { let remaining_tokens = self.conversation.read(cx).remaining_tokens()?; let remaining_tokens_style = if remaining_tokens <= 0 { &style.no_remaining_tokens + } else if remaining_tokens <= 500 { + &style.low_remaining_tokens } else { &style.remaining_tokens }; diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 1949a5d9bb..fae7c470e3 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1030,6 +1030,7 @@ pub struct AssistantStyle { pub system_sender: Interactive, pub model: Interactive, pub remaining_tokens: ContainedText, + pub low_remaining_tokens: ContainedText, pub no_remaining_tokens: ContainedText, pub error_icon: Icon, pub api_key_editor: FieldEditor, diff --git a/styles/src/component/tab_bar_button.ts b/styles/src/component/tab_bar_button.ts new file mode 100644 index 0000000000..0c43e7010e --- /dev/null +++ b/styles/src/component/tab_bar_button.ts @@ -0,0 +1,55 @@ +import { Theme, StyleSets } from "../common" +import { interactive } from "../element" +import { InteractiveState } from "../element/interactive" +import { background, foreground } from "../style_tree/components" + +interface TabBarButtonOptions { + icon: string + color?: StyleSets +} + +type TabBarButtonProps = TabBarButtonOptions & { + state?: Partial>> +} + +export function tab_bar_button(theme: Theme, { icon, color = "base" }: TabBarButtonProps) { + const button_spacing = 8 + + return ( + interactive({ + base: { + icon: { + color: foreground(theme.middle, color), + asset: icon, + dimensions: { + width: 15, + height: 15, + }, + }, + container: { + corner_radius: 4, + padding: { + top: 4, bottom: 4, left: 4, right: 4 + }, + margin: { + left: button_spacing / 2, + right: button_spacing / 2, + }, + }, + }, + state: { + hovered: { + container: { + background: background(theme.middle, color, "hovered"), + + } + }, + clicked: { + container: { + background: background(theme.middle, color, "pressed"), + } + }, + }, + }) + ) +} diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 6df02a0e33..cfc1f8d813 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -1,233 +1,133 @@ -import { text, border, background, foreground } from "./components" -import { interactive } from "../element" -import { useTheme } from "../theme" +import { text, border, background, foreground, TextStyle } from "./components" +import { Interactive, interactive } from "../element" +import { tab_bar_button } from "../component/tab_bar_button" +import { StyleSets, useTheme } from "../theme" + +type RoleCycleButton = TextStyle & { + background?: string +} +// TODO: Replace these with zed types +type RemainingTokens = TextStyle & { + background: string, + margin: { top: number, right: number }, + padding: { + right: number, + left: number, + top: number, + bottom: number, + }, + corner_radius: number, +} export default function assistant(): any { const theme = useTheme() + const interactive_role = (color: StyleSets): Interactive => { + return ( + interactive({ + base: { + ...text(theme.highest, "sans", color, { size: "sm" }), + }, + state: { + hovered: { + ...text(theme.highest, "sans", color, { size: "sm" }), + background: background(theme.highest, color, "hovered"), + }, + clicked: { + ...text(theme.highest, "sans", color, { size: "sm" }), + background: background(theme.highest, color, "pressed"), + } + }, + }) + ) + } + + const tokens_remaining = (color: StyleSets): RemainingTokens => { + return ( + { + ...text(theme.highest, "mono", color, { size: "xs" }), + background: background(theme.highest, "on", "default"), + margin: { top: 12, right: 20 }, + padding: { right: 4, left: 4, top: 1, bottom: 1 }, + corner_radius: 6, + } + ) + } + return { container: { background: background(theme.highest), padding: { left: 12 }, }, message_header: { - margin: { bottom: 6, top: 6 }, + margin: { bottom: 4, top: 4 }, background: background(theme.highest), }, - hamburger_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/hamburger_15.svg", - dimensions: { - width: 15, - height: 15, - }, - }, - container: { - padding: { left: 12, right: 8.5 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + hamburger_button: tab_bar_button(theme, { + icon: "icons/hamburger_15.svg", }), - split_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/split_message_15.svg", - dimensions: { - width: 15, - height: 15, - }, - }, - container: { - padding: { left: 8.5, right: 8.5 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + + split_button: tab_bar_button(theme, { + icon: "icons/split_message_15.svg", }), - quote_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/quote_15.svg", - dimensions: { - width: 15, - height: 15, - }, - }, - container: { - padding: { left: 8.5, right: 8.5 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + quote_button: tab_bar_button(theme, { + icon: "icons/radix/quote.svg", }), - assist_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/assist_15.svg", - dimensions: { - width: 15, - height: 15, - }, - }, - container: { - padding: { left: 8.5, right: 8.5 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + assist_button: tab_bar_button(theme, { + icon: "icons/radix/magic-wand.svg", }), - zoom_in_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/maximize_8.svg", - dimensions: { - width: 12, - height: 12, - }, - }, - container: { - padding: { left: 10, right: 10 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + zoom_in_button: tab_bar_button(theme, { + icon: "icons/radix/maximize.svg", }), - zoom_out_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/minimize_8.svg", - dimensions: { - width: 12, - height: 12, - }, - }, - container: { - padding: { left: 10, right: 10 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + zoom_out_button: tab_bar_button(theme, { + icon: "icons/radix/minimize.svg", }), - plus_button: interactive({ - base: { - icon: { - color: foreground(theme.highest, "variant"), - asset: "icons/plus_12.svg", - dimensions: { - width: 12, - height: 12, - }, - }, - container: { - padding: { left: 10, right: 10 }, - }, - }, - state: { - hovered: { - icon: { - color: foreground(theme.highest, "hovered"), - }, - }, - }, + plus_button: tab_bar_button(theme, { + icon: "icons/radix/plus.svg", }), title: { - ...text(theme.highest, "sans", "default", { size: "sm" }), + ...text(theme.highest, "sans", "default", { size: "xs" }), }, saved_conversation: { container: interactive({ base: { - background: background(theme.highest, "on"), + background: background(theme.middle), padding: { top: 4, bottom: 4 }, + border: border(theme.middle, "default", { top: true, overlay: true }), }, state: { hovered: { - background: background(theme.highest, "on", "hovered"), + background: background(theme.middle, "hovered"), }, + clicked: { + background: background(theme.middle, "pressed"), + } }, }), saved_at: { margin: { left: 8 }, - ...text(theme.highest, "sans", "default", { size: "xs" }), + ...text(theme.highest, "sans", "variant", { size: "xs" }), }, title: { - margin: { left: 16 }, + margin: { left: 12 }, ...text(theme.highest, "sans", "default", { size: "sm", weight: "bold", }), }, }, - user_sender: { - default: { - ...text(theme.highest, "sans", "default", { - size: "sm", - weight: "bold", - }), - }, - }, - assistant_sender: { - default: { - ...text(theme.highest, "sans", "accent", { - size: "sm", - weight: "bold", - }), - }, - }, - system_sender: { - default: { - ...text(theme.highest, "sans", "variant", { - size: "sm", - weight: "bold", - }), - }, - }, + user_sender: interactive_role("base"), + assistant_sender: interactive_role("accent"), + system_sender: interactive_role("warning"), sent_at: { margin: { top: 2, left: 8 }, - ...text(theme.highest, "sans", "default", { size: "2xs" }), + ...text(theme.highest, "sans", "variant", { size: "2xs" }), }, model: interactive({ base: { - background: background(theme.highest, "on"), - margin: { left: 12, right: 12, top: 12 }, - padding: 4, + background: background(theme.highest), + margin: { left: 12, right: 4, top: 12 }, + padding: { right: 4, left: 4, top: 1, bottom: 1 }, corner_radius: 4, ...text(theme.highest, "sans", "default", { size: "xs" }), }, @@ -238,20 +138,9 @@ export default function assistant(): any { }, }, }), - remaining_tokens: { - background: background(theme.highest, "on"), - margin: { top: 12, right: 24 }, - padding: 4, - corner_radius: 4, - ...text(theme.highest, "sans", "positive", { size: "xs" }), - }, - no_remaining_tokens: { - background: background(theme.highest, "on"), - margin: { top: 12, right: 24 }, - padding: 4, - corner_radius: 4, - ...text(theme.highest, "sans", "negative", { size: "xs" }), - }, + remaining_tokens: tokens_remaining("positive"), + low_remaining_tokens: tokens_remaining("warning"), + no_remaining_tokens: tokens_remaining("negative"), error_icon: { margin: { left: 8 }, color: foreground(theme.highest, "negative"), @@ -259,7 +148,7 @@ export default function assistant(): any { }, api_key_editor: { background: background(theme.highest, "on"), - corner_radius: 6, + corner_radius: 4, text: text(theme.highest, "mono", "on"), placeholder_text: text(theme.highest, "mono", "on", "disabled", { size: "xs", diff --git a/styles/src/theme/create_theme.ts b/styles/src/theme/create_theme.ts index dff4c3dbc4..d2701f8341 100644 --- a/styles/src/theme/create_theme.ts +++ b/styles/src/theme/create_theme.ts @@ -12,8 +12,17 @@ export interface Theme { name: string is_light: boolean + /** + * App background, other elements that should sit directly on top of the background. + */ lowest: Layer + /** + * Panels, tabs, other UI surfaces that sit on top of the background. + */ middle: Layer + /** + * Editors like code buffers, conversation editors, etc. + */ highest: Layer ramps: RampSet