DYE+_m!Ks@j_QrSxdylokVTLOJF}8
zotbvEv2FdCu+KeFGUtxn_2En}iTp|9s~RQh+~aY^U+#E<^lW%xUQBtWiRLOO_hm;)5{wq|wPC%M1U+34Jl*njHNbARD6qi}UM9C*R8lYnFg
z0(jScKc9a8qTL3dIc@f?sdX;yB})7PT_LCuTn=OtfsGY<7IZn7RAC)gwRIwFHZdF*
zqhr6GGcQ}Hd
zoelJ(Me*kLjF5Q`Da1yV&7)gbGKz{iBt{0x7vZhWDzRGt2O(WXR;_okigre_fVdDe
z-vY1A9bLA!u48ssp~!B{#tw%SVaRvC*sw^-9&21@5g%?{K^~)$N5Y4>iAlp&Nz@n9
z2K_$!S+CFZyJNaxkZ27aJENh+&SaCSR0u?Pm3GKdq0&gF2a`i|`!L%ZtXk1%UxTe6
z7O)q4FVy-sUAmzvz6%YS4*um;n$dW6d(%bXu*0HdaH&uuq{ajE0(q&TZx2M9ZlvIR
zl!jyY`#7_Wn&B{`=a)t*QPD~}Uc-Sd4K2M0h$GVE_{~UbVl_Yy3Rw)(Xdz*yX)klo
zXOBbY+f8^kC;0MD
zaZCS_{=9DCMNO(NUt=vUWrmp5ffahOJ-=w!dIsVN3a9(mKlL=Qwuw>13aS;@F5I&Q
zl!Gm7erMjH)t<;&StA*PL0S}Cw>EA;0{_~@Dl)?1WM$C=!p9G*vUnPC@!2ils*YK+
zEofR7`W{&j1uNi65^-k-feLL(f<5l4cE
zzTB$`GKW|AjxI=GND=@*NEPVXz#J96!%c^%G8X-3bV%7wAo@{9cZHs}3eiu`cIu%V
zg5M6Q`c|rcoAxL2xX3+C(JER_vB6ny-0cqlGB=yGVAx)|2z7xx)Inzo+*dKwj?-{3
z_;a1TK?J#Qv*9{Oe}AUTx;~gEnFvzE(cAEM$#FR#Qz-zNl4Tf~Pqiy%X@9DS7fhrI~e1ENjVX@A#pwOLZ
zA(b+}Abg@kttAO3DiGE?50dYy0*uguGg{yb{M!(uq`a}Br8VMQBjXb2;E)SC87ku#
zXF~BRVnr3S#VL8&5cQf}da}RxayS^;*V(Vp@7Qew1^J)}uJJ!lTd?}_K)8XEf8ngN4IKbY9BWo~BnSw61XmZZ1WOese%PxCr!
zxCYM2*%?MX&~^!u;gt}wV}Mtrf~%?FJQSY&gb&QC;oOARWJtq^2}AQ*bVv})#|){f
z`)q1vHa(ofiTyLTyu3>=Z2g)J_20jR=)(MXRj)bFSUwzZ-yq
zJucq=kX2-B;!VyC%&~bNN9qqlQCb0y#F2OEpEC%(24HIbFOJ{p_I;fBGX3~}e1*#U
zzsZ**8@^9Y(Bn55vp1Xcf5^A*b5s15hTws74&n1vOtkJ^v,
+ /// 首页「项目与支持」→ 赞助商:独立页面,不是外链行。切换分区时清掉。
+ about_sponsor_open: bool,
settings_search_input: Entity,
search_origin_section: Option,
/// 每项还带着自己的 `values` 表:`SelectState` 只认索引,而从代码侧
@@ -1295,6 +1298,7 @@ impl SettingsPane {
.into_any_element();
}
match self.active_section {
+ 0 if self.about_sponsor_open => self.section_sponsor(cx),
0 => self.section_home(window, cx),
1 => self.section_appearance(window, cx),
2 => self.section_profiles(window, cx),
@@ -1379,6 +1383,7 @@ impl SettingsPane {
.when(!active, |item| item.text_color(muted).hover(|s| s.bg(hover_bg)))
.on_click(cx.listener(move |this, _, _, cx| {
this.active_section = ix;
+ this.about_sponsor_open = false;
cx.notify();
}))
.child(
diff --git a/nebula_app/src/gpui_shell/settings_pane/about.rs b/nebula_app/src/gpui_shell/settings_pane/about.rs
index 5d2b681..1826d76 100644
--- a/nebula_app/src/gpui_shell/settings_pane/about.rs
+++ b/nebula_app/src/gpui_shell/settings_pane/about.rs
@@ -8,6 +8,31 @@ impl SettingsPane {
url: String,
cx: &Context,
) -> gpui::AnyElement {
+ Self::about_row(id, icon, title, IconName::ExternalLink, cx)
+ .on_click(move |_, _, cx| cx.open_url(&url))
+ .into_any_element()
+ }
+
+ /// 与外链行同一条骨架,尾标是「进入页面」而不是「离开应用」。
+ pub(super) fn about_page_row(
+ id: &'static str,
+ icon: IconName,
+ title: &'static str,
+ on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
+ cx: &Context,
+ ) -> gpui::AnyElement {
+ Self::about_row(id, icon, title, IconName::ChevronRight, cx)
+ .on_click(on_click)
+ .into_any_element()
+ }
+
+ fn about_row(
+ id: &'static str,
+ icon: IconName,
+ title: &'static str,
+ trailing: IconName,
+ cx: &Context,
+ ) -> gpui::Stateful {
let muted = cx.theme().muted_foreground;
let hover = cx.theme().list_hover;
h_flex()
@@ -21,11 +46,9 @@ impl SettingsPane {
.rounded_md()
.cursor_pointer()
.hover(move |row| row.bg(hover))
- .on_click(move |_, _, cx| cx.open_url(&url))
.child(Icon::new(icon).small().text_color(muted))
.child(div().flex_1().min_w_0().child(title))
- .child(Icon::new(IconName::ExternalLink).xsmall().text_color(muted))
- .into_any_element()
+ .child(Icon::new(trailing).xsmall().text_color(muted))
}
pub(super) fn about_value_row(
@@ -252,6 +275,13 @@ impl SettingsPane {
language.pick("更新内容", "Release notes"),
crate::update_check::RELEASES_PAGE.to_owned(),
cx,
+ ))
+ .child(Self::about_page_row(
+ "about-sponsors",
+ IconName::Heart,
+ language.pick("赞助商", "Sponsors"),
+ cx.listener(|this, _, _, cx| this.open_sponsor_page(cx)),
+ cx,
));
v_flex().w_full().child(identity).child(
diff --git a/nebula_app/src/gpui_shell/settings_pane/initialization.rs b/nebula_app/src/gpui_shell/settings_pane/initialization.rs
index d972d16..1ff1abe 100644
--- a/nebula_app/src/gpui_shell/settings_pane/initialization.rs
+++ b/nebula_app/src/gpui_shell/settings_pane/initialization.rs
@@ -584,6 +584,7 @@ impl SettingsPane {
about_update: AboutUpdateState::Idle,
about_update_seq: 0,
about_last_checked: None,
+ about_sponsor_open: false,
settings_search_input,
search_origin_section: None,
selects,
diff --git a/nebula_app/src/gpui_shell/settings_pane/sponsor.rs b/nebula_app/src/gpui_shell/settings_pane/sponsor.rs
new file mode 100644
index 0000000..b603b7c
--- /dev/null
+++ b/nebula_app/src/gpui_shell/settings_pane/sponsor.rs
@@ -0,0 +1,132 @@
+//! 设置首页「项目与支持」下的赞助商页:左侧品牌贴图、右侧介绍与注册链接。
+//!
+//! 不做成外链行:外链行一点就跳浏览器,赞助商介绍在应用里就该读得到;
+//! 页面只提供一条明确的注册链接,点它才离开应用。
+
+use std::sync::{Arc, OnceLock};
+
+use super::*;
+
+/// 注册链接。`source` / `campaign` / `promo` 三个参数是赞助商给本项目的
+/// 专属归因,README 与应用内必须用同一条,不能各写各的。
+pub(super) const SPONSOR_URL: &str =
+ "https://fluxionai.space/register?source=github&campaign=pebrel&promo=pebrel";
+const SPONSOR_NAME: &str = "Fluxion AI";
+const LOGO_PNG: &[u8] = include_bytes!("../../../../extra/logo/sponsor_fluxionai.png");
+/// 贴图自带白底圆角,深浅主题都读得清品牌字。
+const LOGO_WIDTH: f32 = 188.0;
+
+/// 贴图只解码一次:RGBA8 → BGRA(gpui 帧通道序),与壁纸 / 文档图同款。
+fn sponsor_logo() -> Option> {
+ static LOGO: OnceLock>> = OnceLock::new();
+ LOGO.get_or_init(|| {
+ let mut rgba = image::load_from_memory(LOGO_PNG).ok()?.into_rgba8();
+ for pixel in rgba.chunks_exact_mut(4) {
+ pixel.swap(0, 2);
+ }
+ Some(Arc::new(RenderImage::new([image::Frame::new(rgba)])))
+ })
+ .clone()
+}
+
+impl SettingsPane {
+ pub(super) fn open_sponsor_page(&mut self, cx: &mut Context) {
+ self.about_sponsor_open = true;
+ cx.notify();
+ }
+
+ pub(super) fn close_sponsor_page(&mut self, cx: &mut Context) {
+ self.about_sponsor_open = false;
+ cx.notify();
+ }
+
+ pub(super) fn section_sponsor(&mut self, cx: &mut Context) -> gpui::Div {
+ let language = crate::gpui_shell::config::ui_language(cx);
+ let theme = cx.theme();
+ let muted = theme.muted_foreground;
+ let ink = theme.foreground;
+ let link = theme.primary;
+ let hover = theme.list_hover;
+ let base_px = self.font_size_px(cx);
+
+ let back = h_flex()
+ .id("sponsor-back")
+ .w_auto()
+ .h(px(32.0))
+ .px_2()
+ .gap_2()
+ .items_center()
+ .rounded_md()
+ .cursor_pointer()
+ .text_color(muted)
+ .hover(move |row| row.bg(hover).text_color(ink))
+ .on_click(cx.listener(|this, _, _, cx| this.close_sponsor_page(cx)))
+ .child(Icon::new(IconName::ArrowLeft).small())
+ .child(language.pick("返回", "Back"));
+
+ let logo =
+ div().w(px(LOGO_WIDTH)).flex_shrink_0().when_some(sponsor_logo(), |slot, image| {
+ slot.child(gpui::StyledImage::object_fit(
+ img(image).w(px(LOGO_WIDTH)).h(px(LOGO_WIDTH * 276.0 / 376.0)),
+ gpui::ObjectFit::Contain,
+ ))
+ });
+
+ let register = h_flex()
+ .id("sponsor-register")
+ .w_auto()
+ .mt(px(4.0))
+ .gap_1()
+ .items_center()
+ .cursor_pointer()
+ .text_color(link)
+ .hover(|row| row.underline())
+ .on_click(|_, _, cx| cx.open_url(SPONSOR_URL))
+ .child(language.pick(
+ "立即访问并注册,即可获得 $3 API 额度",
+ "Sign up now to receive $3 in API credit",
+ ))
+ .child(Icon::new(IconName::ExternalLink).xsmall());
+
+ let copy = v_flex()
+ .flex_1()
+ .min_w_0()
+ .gap(px(10.0))
+ .child(
+ div()
+ .text_size(px(base_px * 1.3))
+ .font_weight(gpui::FontWeight::SEMIBOLD)
+ .text_color(ink)
+ .child(SPONSOR_NAME),
+ )
+ .child(div().text_color(ink).child(language.pick(
+ "一个入口,接入并管理全球主流 AI 模型",
+ "One entry point to access and manage the world's leading AI models",
+ )))
+ .child(div().text_color(muted).line_height(px(base_px * 1.6)).child(language.pick(
+ "Fluxion AI 面向个人开发者、技术团队与企业,通过统一 API 接入并管理全球主流 AI 模型;\
+ 通过多线路动态调度提升可用性,模型表现、响应时间与费用透明可查。\
+ 根据不同模型与线路,API 调用成本较官方或基准价格可降低 40%—98%。",
+ "Fluxion AI serves individual developers, technical teams and enterprises with a \
+ unified API for accessing and managing mainstream AI models worldwide. Dynamic \
+ multi-route scheduling improves availability, and model performance, response \
+ time and cost stay transparent. Depending on the model and route, API calls can \
+ cost 40%–98% less than official or benchmark prices.",
+ )))
+ .child(register);
+
+ v_flex()
+ .w_full()
+ .gap(px(20.0))
+ .child(back)
+ .child(
+ div()
+ .h(px(30.0))
+ .text_size(px(base_px * 0.85))
+ .font_weight(gpui::FontWeight::SEMIBOLD)
+ .text_color(muted)
+ .child(language.pick("赞助商", "Sponsors")),
+ )
+ .child(h_flex().w_full().items_start().gap(px(28.0)).child(logo).child(copy))
+ }
+}