@@ -13,6 +13,7 @@ use serde::Serialize;
1313use serde_json:: Value ;
1414
1515use crate :: log_debug;
16+ use super :: sanitize:: { sanitize_path_segment, sanitize_slug} ;
1617
1718const MAX_RESULTS : usize = 3 ;
1819const BOX_WIDTH : usize = 90 ;
@@ -534,6 +535,26 @@ pub struct SuggestResult {
534535 pub matched_keywords : Vec < String > ,
535536}
536537
538+ #[ derive( Debug , Clone , Serialize ) ]
539+ pub struct BeautifyResult {
540+ pub style : Vec < HashMap < String , String > > ,
541+ pub color : Vec < HashMap < String , String > > ,
542+ pub typography : Vec < HashMap < String , String > > ,
543+ }
544+
545+ #[ derive( Debug , Clone , Serialize ) ]
546+ pub struct PersistSummary {
547+ pub design_system_dir : String ,
548+ pub created_files : Vec < String > ,
549+ }
550+
551+ #[ derive( Debug , Clone ) ]
552+ pub struct DesignSystemOutput {
553+ pub design_system : DesignSystem ,
554+ pub persisted : Option < PersistSummary > ,
555+ pub formatted : String ,
556+ }
557+
537558struct UiuxStore {
538559 domains : HashMap < & ' static str , DomainIndex > ,
539560 stacks : HashMap < & ' static str , DomainIndex > ,
@@ -647,6 +668,13 @@ fn normalize_format(value: Option<&str>, default: &str) -> String {
647668 . to_lowercase ( )
648669}
649670
671+ /// 结果数量上限控制,避免过大输出
672+ pub fn cap_max_results ( value : Option < u32 > , cap : u32 , default : u32 ) -> usize {
673+ let cap = cap. max ( 1 ) ;
674+ let raw = value. unwrap_or ( default) . max ( 1 ) ;
675+ raw. min ( cap) as usize
676+ }
677+
650678pub fn search_domain ( query : & str , domain : Option < & str > , max_results : Option < usize > ) -> SearchResult {
651679 let store = & * UIUX_STORE ;
652680 let requested_domain = domain. unwrap_or_else ( || detect_domain ( query) ) ;
@@ -769,6 +797,38 @@ pub fn format_search_json(result: &SearchResult) -> Result<String, String> {
769797 serde_json:: to_string_pretty ( result) . map_err ( |e| format ! ( "JSON 序列化失败: {}" , e) )
770798}
771799
800+ /// UI 提示词美化:基于现有数据组合,避免重复
801+ pub fn beautify_prompt ( query : & str , max_results : usize ) -> BeautifyResult {
802+ let limit = max_results. max ( 1 ) ;
803+ let style = search_domain ( query, Some ( "style" ) , Some ( limit) ) ;
804+ let color = search_domain ( query, Some ( "color" ) , Some ( limit) ) ;
805+ let typography = search_domain ( query, Some ( "typography" ) , Some ( limit) ) ;
806+
807+ BeautifyResult {
808+ style : dedupe_results ( style. results ) ,
809+ color : dedupe_results ( color. results ) ,
810+ typography : dedupe_results ( typography. results ) ,
811+ }
812+ }
813+
814+ fn dedupe_results ( rows : Vec < HashMap < String , String > > ) -> Vec < HashMap < String , String > > {
815+ let mut seen = HashSet :: new ( ) ;
816+ let mut output = Vec :: new ( ) ;
817+ for row in rows {
818+ let mut items: Vec < _ > = row. iter ( ) . collect ( ) ;
819+ items. sort_by ( |a, b| a. 0 . cmp ( b. 0 ) ) ;
820+ let signature = items
821+ . iter ( )
822+ . map ( |( k, v) | format ! ( "{}={}" , k, v) )
823+ . collect :: < Vec < _ > > ( )
824+ . join ( "|" ) ;
825+ if seen. insert ( signature) {
826+ output. push ( row) ;
827+ }
828+ }
829+ output
830+ }
831+
772832#[ derive( Debug , Clone , Serialize ) ]
773833pub struct PatternInfo {
774834 pub name : String ,
@@ -1993,25 +2053,18 @@ fn format_page_override_md(design_system: &DesignSystem, page_name: &str, page_q
19932053 lines. join ( "\n " )
19942054}
19952055
1996- struct PersistResult {
1997- design_system_dir : PathBuf ,
1998- created_files : Vec < PathBuf > ,
1999- }
2000-
20012056fn persist_design_system (
20022057 design_system : & DesignSystem ,
20032058 page : Option < & str > ,
20042059 output_dir : Option < & Path > ,
20052060 page_query : Option < & str > ,
2006- ) -> Result < PersistResult , String > {
2061+ ) -> Result < PersistSummary , String > {
20072062 let base_dir = output_dir
20082063 . map ( |p| p. to_path_buf ( ) )
20092064 . unwrap_or_else ( || std:: env:: current_dir ( ) . unwrap_or_else ( |_| PathBuf :: from ( "." ) ) ) ;
20102065
2011- let project_slug = design_system
2012- . project_name
2013- . to_lowercase ( )
2014- . replace ( ' ' , "-" ) ;
2066+ // 关键路径使用净化结果防止路径穿越
2067+ let project_slug = sanitize_slug ( & design_system. project_name ) ;
20152068
20162069 let design_system_dir = base_dir. join ( "design-system" ) . join ( & project_slug) ;
20172070 let pages_dir = design_system_dir. join ( "pages" ) ;
@@ -2026,16 +2079,19 @@ fn persist_design_system(
20262079 let mut created = vec ! [ master_file] ;
20272080
20282081 if let Some ( page_name) = page {
2029- let page_slug = page_name . to_lowercase ( ) . replace ( ' ' , "-" ) ;
2082+ let page_slug = sanitize_path_segment ( page_name ) ;
20302083 let page_file = pages_dir. join ( format ! ( "{}.md" , page_slug) ) ;
20312084 std:: fs:: write ( & page_file, format_page_override_md ( design_system, page_name, page_query) )
20322085 . map_err ( |e| format ! ( "写入页面覆盖文件失败: {}" , e) ) ?;
20332086 created. push ( page_file) ;
20342087 }
20352088
2036- Ok ( PersistResult {
2037- design_system_dir,
2038- created_files : created,
2089+ Ok ( PersistSummary {
2090+ design_system_dir : design_system_dir. to_string_lossy ( ) . to_string ( ) ,
2091+ created_files : created
2092+ . into_iter ( )
2093+ . map ( |path| path. to_string_lossy ( ) . to_string ( ) )
2094+ . collect ( ) ,
20392095 } )
20402096}
20412097
@@ -2046,56 +2102,54 @@ pub fn generate_design_system(
20462102 persist : bool ,
20472103 page : Option < & str > ,
20482104 output_dir : Option < & Path > ,
2049- ) -> Result < String , String > {
2105+ ) -> Result < DesignSystemOutput , String > {
20502106 let generator = DesignSystemGenerator :: new ( ) ;
20512107 let design_system = generator. generate ( query, project_name) ;
20522108
2053- if persist {
2054- let persist_result = persist_design_system ( & design_system, page, output_dir, Some ( query) ) ?;
2055- let mut output = match normalize_format ( format, "ascii" ) . as_str ( ) {
2056- "markdown" => format_markdown ( & design_system) ,
2057- _ => format_ascii_box ( & design_system) ,
2058- } ;
2109+ let persisted = if persist {
2110+ Some ( persist_design_system ( & design_system, page, output_dir, Some ( query) ) ?)
2111+ } else {
2112+ None
2113+ } ;
20592114
2060- let project_slug = design_system
2061- . project_name
2062- . to_lowercase ( )
2063- . replace ( ' ' , "-" ) ;
2064- output. push_str ( "\n " ) ;
2065- output. push_str ( & "=" . repeat ( 60 ) ) ;
2066- output. push_str ( "\n " ) ;
2067- output. push_str ( & format ! (
2115+ let mut formatted = match normalize_format ( format, "ascii" ) . as_str ( ) {
2116+ "markdown" => format_markdown ( & design_system) ,
2117+ _ => format_ascii_box ( & design_system) ,
2118+ } ;
2119+
2120+ if persist {
2121+ let project_slug = sanitize_slug ( & design_system. project_name ) ;
2122+ formatted. push_str ( "\n " ) ;
2123+ formatted. push_str ( & "=" . repeat ( 60 ) ) ;
2124+ formatted. push_str ( "\n " ) ;
2125+ formatted. push_str ( & format ! (
20682126 "✅ Design system persisted to design-system/{}/\n " ,
20692127 project_slug
20702128 ) ) ;
2071- output . push_str ( & format ! (
2129+ formatted . push_str ( & format ! (
20722130 " 📄 design-system/{}/MASTER.md (Global Source of Truth)\n " ,
20732131 project_slug
20742132 ) ) ;
20752133 if let Some ( page_name) = page {
2076- let page_slug = page_name . to_lowercase ( ) . replace ( ' ' , "-" ) ;
2077- output . push_str ( & format ! (
2134+ let page_slug = sanitize_path_segment ( page_name ) ;
2135+ formatted . push_str ( & format ! (
20782136 " 📄 design-system/{}/pages/{}.md (Page Overrides)\n " ,
20792137 project_slug, page_slug
20802138 ) ) ;
20812139 }
2082- output . push_str ( "\n " ) ;
2083- output . push_str ( & format ! (
2140+ formatted . push_str ( "\n " ) ;
2141+ formatted . push_str ( & format ! (
20842142 "📖 Usage: When building a page, check design-system/{}/pages/[page].md first.\n " ,
20852143 project_slug
20862144 ) ) ;
2087- output. push_str ( " If exists, its rules override MASTER.md. Otherwise, use MASTER.md.\n " ) ;
2088- output. push_str ( & "=" . repeat ( 60 ) ) ;
2089-
2090- // 避免未使用告警
2091- let _ = persist_result;
2092-
2093- return Ok ( output) ;
2145+ formatted. push_str ( " If exists, its rules override MASTER.md. Otherwise, use MASTER.md.\n " ) ;
2146+ formatted. push_str ( & "=" . repeat ( 60 ) ) ;
20942147 }
20952148
2096- Ok ( match normalize_format ( format, "ascii" ) . as_str ( ) {
2097- "markdown" => format_markdown ( & design_system) ,
2098- _ => format_ascii_box ( & design_system) ,
2149+ Ok ( DesignSystemOutput {
2150+ design_system,
2151+ persisted,
2152+ formatted,
20992153 } )
21002154}
21012155
0 commit comments