@@ -106,6 +106,48 @@ public async Task<string> ChatAsync(string systemPrompt, string userPrompt, long
106106 return await ChatMessagesAsync ( messages , providerId , config , modelOverride ) ;
107107 }
108108
109+ /// <summary>视觉对话:文本 + 图片(OpenAI 兼容多模态 content 数组)。</summary>
110+ public async Task < string > ChatWithImagesAsync ( string systemPrompt , string userText , List < string > imageUrls ,
111+ long providerId = 0 , string ? modelOverride = null )
112+ {
113+ var cfg = await ResolveConfigAsync ( providerId > 0 ? providerId : null ) ;
114+ if ( ! string . IsNullOrEmpty ( modelOverride ) ) cfg . Model = modelOverride ;
115+ if ( string . IsNullOrEmpty ( cfg . BaseUrl ) || string . IsNullOrEmpty ( cfg . ApiKey ) )
116+ throw new Exception ( "未配置 AI 大模型:请在系统设置 → 大模型设置中添加并启用供应商" ) ;
117+
118+ var url = cfg . BaseUrl . TrimEnd ( '/' ) ;
119+ if ( ! url . EndsWith ( "/chat/completions" , StringComparison . OrdinalIgnoreCase ) )
120+ url += "/chat/completions" ;
121+
122+ // 多模态 content:[{type:text,text},{type:image_url,image_url:{url}}]
123+ var content = new List < object > ( ) ;
124+ if ( ! string . IsNullOrWhiteSpace ( userText ) )
125+ content . Add ( new { type = "text" , text = userText } ) ;
126+ foreach ( var img in imageUrls )
127+ content . Add ( new { type = "image_url" , image_url = new { url = img } } ) ;
128+
129+ var client = _httpClientFactory . CreateClient ( ) ;
130+ client . Timeout = TimeSpan . FromSeconds ( 180 ) ;
131+ using var reqMsg = new HttpRequestMessage ( HttpMethod . Post , url ) ;
132+ reqMsg . Headers . TryAddWithoutValidation ( "Authorization" , $ "Bearer { cfg . ApiKey } ") ;
133+ reqMsg . Content = new StringContent ( JsonSerializer . Serialize ( new
134+ {
135+ model = cfg . Model ,
136+ messages = new object [ ]
137+ {
138+ new { role = "system" , content = systemPrompt } ,
139+ new { role = "user" , content = content . ToArray ( ) }
140+ } ,
141+ temperature = 0.2
142+ } ) , Encoding . UTF8 , "application/json" ) ;
143+ using var resp = await client . SendAsync ( reqMsg ) ;
144+ var body = await resp . Content . ReadAsStringAsync ( ) ;
145+ if ( ! resp . IsSuccessStatusCode )
146+ throw new Exception ( $ "AI 调用失败({ ( int ) resp . StatusCode } ): { Truncate ( body , 300 ) } ") ;
147+ using var doc = JsonDocument . Parse ( body ) ;
148+ return doc . RootElement . GetProperty ( "choices" ) [ 0 ] . GetProperty ( "message" ) . GetProperty ( "content" ) . GetString ( ) ? . Trim ( ) ?? "" ;
149+ }
150+
109151 /// <summary>多轮对话:完整消息列表(system + 历史消息)一次性发送。</summary>
110152 public async Task < string > ChatMessagesAsync ( List < ( string Role , string Content ) > messages , long providerId = 0 , AiConfig ? config = null , string ? modelOverride = null )
111153 {
0 commit comments