| author | |
| committer | |
| log | 348f217a853dd4a23d7fa4834dedb05f545c2075 |
| tree | f5b725829b4c289fc96d9b4c613c66df3fca1d4b |
| parent | e47bfe4617f6fdca2c6513bd0afe0f6b8d24688a |
| signature | Signed by PGP key B5690EEEBB952194 |
5 files changed, 102 insertions(+), 13 deletions(-)
apps/mobile/src/components/ProviderIcon.tsx+28-4| ... | @@ -1,8 +1,8 @@ | ... | @@ -1,8 +1,8 @@ |
| 1 | import { Image } from "expo-image"; | 1 | import { Image } from "expo-image"; |
| 2 | import { Path, Svg } from "react-native-svg"; | 2 | import { Defs, LinearGradient, Path, Stop, Svg } from "react-native-svg"; |
| 3 | import { View } from "react-native"; | 3 | import { View } from "react-native"; |
| 4 | import { providerInstanceInitials } from "@t3tools/client-runtime/state/provider-instance-display"; | 4 | import { providerInstanceInitials } from "@t3tools/client-runtime/state/provider-instance-display"; |
| 5 | import { useState } from "react"; | 5 | import { useId, useState } from "react"; |
| 6 | import { resolveOfficialAcpRegistryIconUrl } from "@t3tools/contracts"; | 6 | import { resolveOfficialAcpRegistryIconUrl } from "@t3tools/contracts"; |
| 7 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; | 7 | import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; |
| 8 | import { AppText as Text } from "./AppText"; | 8 | import { AppText as Text } from "./AppText"; |
| ... | @@ -66,6 +66,7 @@ function AcpRegistryProviderIcon(props: { | ... | @@ -66,6 +66,7 @@ function AcpRegistryProviderIcon(props: { |
| 66 | export function ProviderIcon(props: ProviderIconProps) { | 66 | export function ProviderIcon(props: ProviderIconProps) { |
| 67 | const { themeAppearance } = useAppearancePreferences(); | 67 | const { themeAppearance } = useAppearancePreferences(); |
| 68 | const isDarkMode = themeAppearance === "dark"; | 68 | const isDarkMode = themeAppearance === "dark"; |
| 69 | const gradientId = useId(); | ||
| 69 | const size = props.size ?? 16; | 70 | const size = props.size ?? 16; |
| 70 | const mono = isDarkMode ? "#e5e5e5" : "#171717"; | 71 | const mono = isDarkMode ? "#e5e5e5" : "#171717"; |
| 71 | 72 | ||
| ... | @@ -99,12 +100,35 @@ export function ProviderIcon(props: ProviderIconProps) { | ... | @@ -99,12 +100,35 @@ export function ProviderIcon(props: ProviderIconProps) { |
| 99 | isDarkMode ? fill.dark : fill.light; | 100 | isDarkMode ? fill.dark : fill.light; |
| 100 | return ( | 101 | return ( |
| 101 | <Svg width={size} height={size} viewBox={packageIcon.viewBox} fill="none"> | 102 | <Svg width={size} height={size} viewBox={packageIcon.viewBox} fill="none"> |
| 102 | {packageIcon.paths.map((path) => ( | 103 | <Defs> |
| 104 | {packageIcon.paths.map((path, index) => | ||
| 105 | path.gradient ? ( | ||
| 106 | <LinearGradient | ||
| 107 | key={path.d} | ||
| 108 | id={`${gradientId}-${index}`} | ||
| 109 | x1={path.gradient.x1} | ||
| 110 | y1={path.gradient.y1} | ||
| 111 | x2={path.gradient.x2} | ||
| 112 | y2={path.gradient.y2} | ||
| 113 | gradientUnits="userSpaceOnUse" | ||
| 114 | > | ||
| 115 | {path.gradient.stops.map((stop) => ( | ||
| 116 | <Stop key={stop.offset} offset={stop.offset} stopColor={stop.color} /> | ||
| 117 | ))} | ||
| 118 | </LinearGradient> | ||
| 119 | ) : null, | ||
| 120 | )} | ||
| 121 | </Defs> | ||
| 122 | {packageIcon.paths.map((path, index) => ( | ||
| 103 | <Path | 123 | <Path |
| 104 | key={path.d} | 124 | key={path.d} |
| 105 | d={path.d} | 125 | d={path.d} |
| 106 | fillRule={path.fillRule} | 126 | fillRule={path.fillRule} |
| 107 | fill={themeFill(path.fill ?? packageIcon.fill)} | 127 | fill={ |
| 128 | path.gradient | ||
| 129 | ? `url(#${gradientId}-${index})` | ||
| 130 | : themeFill(path.fill ?? packageIcon.fill) | ||
| 131 | } | ||
| 108 | /> | 132 | /> |
| 109 | ))} | 133 | ))} |
| 110 | </Svg> | 134 | </Svg> |
apps/web/src/components/chat/ChatComposer.tsx+2-1| ... | @@ -5649,7 +5649,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -5649,7 +5649,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5649 | activeProviderIconClassName: cn( | 5649 | activeProviderIconClassName: cn( |
| 5650 | composerProviderState.modelPickerIconClassName, | 5650 | composerProviderState.modelPickerIconClassName, |
| 5651 | composerControlsCollapsed && | 5651 | composerControlsCollapsed && |
| 5652 | "fill-muted-foreground/70! text-muted-foreground/70! [&_path]:fill-muted-foreground/70! [&_rect]:fill-muted-foreground/70! [&_[data-icon-detail]]:fill-transparent!", | 5652 | // Package glyphs get opaque fills and one opacity, so overlapping paths don't band. |
| 5653 | "fill-muted-foreground/70! text-muted-foreground/70! [&_path]:fill-muted-foreground/70! [&_rect]:fill-muted-foreground/70! [&_[data-icon-detail]]:fill-transparent! [&[data-provider-package-icon]]:opacity-70 [&[data-provider-package-icon]_path:not([data-icon-detail])]:fill-muted-foreground!", | ||
| 5653 | ), | 5654 | ), |
| 5654 | } | 5655 | } |
| 5655 | : {})} | 5656 | : {})} |
apps/web/src/components/chat/ProviderPackageIcon.tsx+28-2| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | import type { CSSProperties, SVGProps } from "react"; | 1 | import { useId, type CSSProperties, type SVGProps } from "react"; |
| 2 | 2 | ||
| 3 | import type { ProviderIcon } from "@t3tools/provider-core/client"; | 3 | import type { ProviderIcon } from "@t3tools/provider-core/client"; |
| 4 | 4 | ||
| ... | @@ -13,11 +13,14 @@ export function ProviderPackageIcon({ | ... | @@ -13,11 +13,14 @@ export function ProviderPackageIcon({ |
| 13 | className, | 13 | className, |
| 14 | ...props | 14 | ...props |
| 15 | }: { readonly icon: ProviderIcon } & SVGProps<SVGSVGElement>) { | 15 | }: { readonly icon: ProviderIcon } & SVGProps<SVGSVGElement>) { |
| 16 | const gradientId = useId(); | ||
| 16 | return ( | 17 | return ( |
| 17 | <svg | 18 | <svg |
| 18 | {...props} | 19 | {...props} |
| 19 | viewBox={icon.viewBox} | 20 | viewBox={icon.viewBox} |
| 20 | fill="none" | 21 | fill="none" |
| 22 | // Lets monochrome renderings dim the whole glyph once instead of each path. | ||
| 23 | data-provider-package-icon | ||
| 21 | className={cn("fill-(--icon-light) dark:fill-(--icon-dark)", className)} | 24 | className={cn("fill-(--icon-light) dark:fill-(--icon-dark)", className)} |
| 22 | style={ | 25 | style={ |
| 23 | { | 26 | { |
| ... | @@ -26,11 +29,34 @@ export function ProviderPackageIcon({ | ... | @@ -26,11 +29,34 @@ export function ProviderPackageIcon({ |
| 26 | } as CSSProperties | 29 | } as CSSProperties |
| 27 | } | 30 | } |
| 28 | > | 31 | > |
| 29 | {icon.paths.map((path) => ( | 32 | {icon.paths.some((path) => path.gradient) ? ( |
| 33 | <defs> | ||
| 34 | {icon.paths.map((path, index) => | ||
| 35 | path.gradient ? ( | ||
| 36 | <linearGradient | ||
| 37 | key={path.d} | ||
| 38 | id={`${gradientId}-${index}`} | ||
| 39 | x1={path.gradient.x1} | ||
| 40 | y1={path.gradient.y1} | ||
| 41 | x2={path.gradient.x2} | ||
| 42 | y2={path.gradient.y2} | ||
| 43 | gradientUnits="userSpaceOnUse" | ||
| 44 | > | ||
| 45 | {path.gradient.stops.map((stop) => ( | ||
| 46 | <stop key={stop.offset} offset={stop.offset} stopColor={stop.color} /> | ||
| 47 | ))} | ||
| 48 | </linearGradient> | ||
| 49 | ) : null, | ||
| 50 | )} | ||
| 51 | </defs> | ||
| 52 | ) : null} | ||
| 53 | {icon.paths.map((path, index) => ( | ||
| 30 | <path | 54 | <path |
| 31 | key={path.d} | 55 | key={path.d} |
| 32 | d={path.d} | 56 | d={path.d} |
| 33 | fillRule={path.fillRule} | 57 | fillRule={path.fillRule} |
| 58 | // An attribute, so monochrome renderings can still override it from CSS. | ||
| 59 | fill={path.gradient ? `url(#${gradientId}-${index})` : undefined} | ||
| 34 | // Monochrome renderings (the collapsed composer) clear these detail paths. | 60 | // Monochrome renderings (the collapsed composer) clear these detail paths. |
| 35 | data-icon-detail={path.fill ? true : undefined} | 61 | data-icon-detail={path.fill ? true : undefined} |
| 36 | className={path.fill ? "fill-(--path-light) dark:fill-(--path-dark)" : undefined} | 62 | className={path.fill ? "fill-(--path-light) dark:fill-(--path-dark)" : undefined} |
packages/provider-core/src/client.ts+12-2| ... | @@ -33,8 +33,8 @@ export interface ProviderEnvironmentField { | ... | @@ -33,8 +33,8 @@ export interface ProviderEnvironmentField { |
| 33 | 33 | ||
| 34 | /** | 34 | /** |
| 35 | * A provider glyph as plain data, so web and mobile can each draw it with | 35 | * A provider glyph as plain data, so web and mobile can each draw it with |
| 36 | * their own SVG renderer. Colors are per theme; a path without a fill uses | 36 | * their own SVG renderer. Colors are per theme; a path without a fill or |
| 37 | * the icon's `fill`. | 37 | * gradient uses the icon's `fill`. |
| 38 | */ | 38 | */ |
| 39 | export interface ProviderIcon { | 39 | export interface ProviderIcon { |
| 40 | readonly viewBox: string; | 40 | readonly viewBox: string; |
| ... | @@ -43,9 +43,19 @@ export interface ProviderIcon { | ... | @@ -43,9 +43,19 @@ export interface ProviderIcon { |
| 43 | readonly d: string; | 43 | readonly d: string; |
| 44 | readonly fillRule?: "evenodd" | "nonzero"; | 44 | readonly fillRule?: "evenodd" | "nonzero"; |
| 45 | readonly fill?: { readonly light: string; readonly dark: string }; | 45 | readonly fill?: { readonly light: string; readonly dark: string }; |
| 46 | /** A brand gradient, the same in both themes. Coordinates are in viewBox units. */ | ||
| 47 | readonly gradient?: ProviderIconGradient; | ||
| 46 | }>; | 48 | }>; |
| 47 | } | 49 | } |
| 48 | 50 | ||
| 51 | export interface ProviderIconGradient { | ||
| 52 | readonly x1: number; | ||
| 53 | readonly y1: number; | ||
| 54 | readonly x2: number; | ||
| 55 | readonly y2: number; | ||
| 56 | readonly stops: ReadonlyArray<{ readonly offset: number; readonly color: string }>; | ||
| 57 | } | ||
| 58 | |||
| 49 | export interface ProviderClientDefinition { | 59 | export interface ProviderClientDefinition { |
| 50 | readonly driverKind: ProviderDriverKind; | 60 | readonly driverKind: ProviderDriverKind; |
| 51 | readonly label: string; | 61 | readonly label: string; |
packages/provider-muse/src/client.ts+32-4| ... | @@ -14,13 +14,41 @@ export const museClient = defineProviderClient({ | ... | @@ -14,13 +14,41 @@ export const museClient = defineProviderClient({ |
| 14 | label: "Muse Code", | 14 | label: "Muse Code", |
| 15 | settingsSchema: MuseSettings, | 15 | settingsSchema: MuseSettings, |
| 16 | badgeLabel: "Beta", | 16 | badgeLabel: "Beta", |
| 17 | // Meta's symbol (https://commons.wikimedia.org/wiki/File:Meta_Platforms_Inc._logo_(cropped).svg). | ||
| 17 | icon: { | 18 | icon: { |
| 18 | viewBox: "0 0 150.4828 100", | 19 | viewBox: "0 0 287.56 191", |
| 19 | fill: { light: "#171717", dark: "#e5e5e5" }, | 20 | fill: { light: "#0081fb", dark: "#0081fb" }, |
| 20 | paths: [ | 21 | paths: [ |
| 21 | { | 22 | { |
| 22 | d: "M107.9999847,0.0000013c-12.3391113,0-21.984787,9.2935991-30.7164001,21.0993118 C65.2847366,5.821527,55.2499847,0,43.2413788,0C18.7586193,0,0,31.8620682,0,65.5862122 C0,86.6896515,10.2095623,100,27.3103466,100c12.3080502,0,21.1600361-5.8025894,36.8965454-33.3103409 c0,0,6.5597382-11.5841255,11.0725021-19.563858c1.5813522,2.5531845,3.2431107,5.3001404,4.9964676,8.2535095 l7.379303,12.4137955C102.0298843,91.8478851,110.039032,100,124.5517273,100 c16.6595764,0,25.9310303-13.4923325,25.9310303-35.0344849 C150.4827576,29.6551723,131.301178,0.0000013,107.9999847,0.0000013z M52.2068977,59.2413788 c-12.7586212,20-17.1724167,24.4827576-24.2758656,24.4827576c-7.3103428,0-11.6551704-6.4178314-11.6551704-17.8620682 c0-24.4827576,12.2068977-49.5172424,26.7586231-49.5172424c7.880127,0,14.4653816,4.5510426,24.5522308,18.9913158 C58.0087967,50.0272293,52.2068977,59.2413788,52.2068977,59.2413788z M100.3597336,56.7235756L91.5367355,42.00877 c-2.3876724-3.8832092-4.6757278-7.451458-6.8838348-10.7230263 c7.951973-12.273531,14.5113449-18.3891907,22.3126144-18.3891907c16.2069016,0,29.1724243,23.8620682,29.1724243,53.1724091 c0,11.1724167-3.6593475,17.6551743-11.2413864,17.6551743 C117.6294937,83.7241364,114.1578674,78.924736,100.3597336,56.7235756z", | 23 | d: "m31.06,125.96c0,10.98 2.41,19.41 5.56,24.51 4.13,6.68 10.29,9.51 16.57,9.51 8.1,0 15.51-2.01 29.79-21.76 11.44-15.83 24.92-38.05 33.99-51.98l15.36-23.6c10.67-16.39 23.02-34.61 37.18-46.96 11.56-10.08 24.03-15.68 36.58-15.68 21.07,0 41.14,12.21 56.5,35.11 16.81,25.08 24.97,56.67 24.97,89.27 0,19.38-3.82,33.62-10.32,44.87-6.28,10.88-18.52,21.75-39.11,21.75l0-31.02c17.63,0 22.03-16.2 22.03-34.74 0-26.42-6.16-55.74-19.73-76.69-9.63-14.86-22.11-23.94-35.84-23.94-14.85,0-26.8,11.2-40.23,31.17-7.14,10.61-14.47,23.54-22.7,38.13l-9.06,16.05c-18.2,32.27-22.81,39.62-31.91,51.75-15.95,21.24-29.57,29.29-47.5,29.29-21.27,0-34.72-9.21-43.05-23.09-6.8-11.31-10.14-26.15-10.14-43.06z", |
| 23 | fillRule: "evenodd", | 24 | }, |
| 25 | { | ||
| 26 | d: "m24.49,37.3c14.24-21.95 34.79-37.3 58.36-37.3 13.65,0 27.22,4.04 41.39,15.61 15.5,12.65 32.02,33.48 52.63,67.81l7.39,12.32c17.84,29.72 27.99,45.01 33.93,52.22 7.64,9.26 12.99,12.02 19.94,12.02 17.63,0 22.03-16.2 22.03-34.74l27.4-.86c0,19.38-3.82,33.62-10.32,44.87-6.28,10.88-18.52,21.75-39.11,21.75-12.8,0-24.14-2.78-36.68-14.61-9.64-9.08-20.91-25.21-29.58-39.71l-25.79-43.08c-12.94-21.62-24.81-37.74-31.68-45.04-7.39-7.85-16.89-17.33-32.05-17.33-12.27,0-22.69,8.61-31.41,21.78z", | ||
| 27 | gradient: { | ||
| 28 | x1: 61, | ||
| 29 | y1: 117, | ||
| 30 | x2: 259, | ||
| 31 | y2: 127, | ||
| 32 | stops: [ | ||
| 33 | { offset: 0, color: "#0064e1" }, | ||
| 34 | { offset: 0.4, color: "#0064e1" }, | ||
| 35 | { offset: 0.83, color: "#0073ee" }, | ||
| 36 | { offset: 1, color: "#0082fb" }, | ||
| 37 | ], | ||
| 38 | }, | ||
| 39 | }, | ||
| 40 | { | ||
| 41 | d: "m82.35,31.23c-12.27,0-22.69,8.61-31.41,21.78-12.33,18.61-19.88,46.33-19.88,72.95 0,10.98 2.41,19.41 5.56,24.51l-26.48,17.44c-6.8-11.31-10.14-26.15-10.14-43.06 0-30.75 8.44-62.8 24.49-87.55 14.24-21.95 34.79-37.3 58.36-37.3z", | ||
| 42 | gradient: { | ||
| 43 | x1: 45, | ||
| 44 | y1: 139, | ||
| 45 | x2: 45, | ||
| 46 | y2: 66, | ||
| 47 | stops: [ | ||
| 48 | { offset: 0, color: "#0082fb" }, | ||
| 49 | { offset: 1, color: "#0064e0" }, | ||
| 50 | ], | ||
| 51 | }, | ||
| 24 | }, | 52 | }, |
| 25 | ], | 53 | ], |
| 26 | }, | 54 | }, |