This commit is contained in:
HuangHai
2026-01-29 09:00:56 +08:00
parent 8101a3eafc
commit 1c7da206ce
3 changed files with 170 additions and 142 deletions

View File

@@ -189,6 +189,7 @@ namespace WordAddIn
if (login.ShowDialog() == DialogResult.OK) if (login.ShowDialog() == DialogResult.OK)
{ {
_isLoggedIn = true; _isLoggedIn = true;
AccessToken = login.Token; // 获取登录成功后的 Token
SaveLoginState(); SaveLoginState();
// 刷新 Ribbon 状态,使按钮变亮 // 刷新 Ribbon 状态,使按钮变亮

View File

@@ -339,6 +339,15 @@ namespace WordAddIn
optimizedPrompt = await OptimizePromptForImage(prompt, style); optimizedPrompt = await OptimizePromptForImage(prompt, style);
} }
// 检查登录状态
if (string.IsNullOrEmpty(AiRibbon.AccessToken))
{
throw new Exception("未登录,请先点击功能区的【登录】按钮获取权限。");
}
// 指向本地 FastAPI 后端代理
string requestUrl = $"{Config.BackendBaseUrl}/api/image";
// 使用匿名对象构造请求体,以获得最大灵活性 // 使用匿名对象构造请求体,以获得最大灵活性
var requestBody = new var requestBody = new
{ {
@@ -351,12 +360,23 @@ namespace WordAddIn
var json = JsonConvert.SerializeObject(requestBody); var json = JsonConvert.SerializeObject(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json"); var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync($"{Config.TuZiBaseUrl}/images/generations", content); // 创建请求并添加 Token
using (var request = new HttpRequestMessage(HttpMethod.Post, requestUrl))
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", AiRibbon.AccessToken);
request.Content = content;
using (var response = await _httpClient.SendAsync(request))
{
var responseString = await response.Content.ReadAsStringAsync(); var responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
throw new Exception($"API Error ({response.StatusCode}): {responseString}"); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
throw new Exception("认证失效,请重新登录。");
}
throw new Exception($"后端服务错误 ({response.StatusCode}): {responseString}");
} }
// 使用 JObject 动态解析响应,以兼容不同厂商的 API 格式差异 // 使用 JObject 动态解析响应,以兼容不同厂商的 API 格式差异
@@ -396,6 +416,8 @@ namespace WordAddIn
throw new Exception($"无法从响应中解析图片 URL。原始响应: {responseString}"); throw new Exception($"无法从响应中解析图片 URL。原始响应: {responseString}");
} }
}
}
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception($"生图请求失败: {ex.Message}", ex); throw new Exception($"生图请求失败: {ex.Message}", ex);
@@ -424,50 +446,31 @@ namespace WordAddIn
/// <summary> /// <summary>
/// 通用聊天补全请求方法。 /// 通用聊天补全请求方法。
/// 改为通过本地 Python FastAPI 后端代理请求。
/// </summary> /// </summary>
private async Task<string> GetChatCompletion(List<Message> messages, Action<string> onProgress = null) private async Task<string> GetChatCompletion(List<Message> messages, Action<string> onProgress = null)
{ {
try try
{ {
// 检查登录状态
if (string.IsNullOrEmpty(AiRibbon.AccessToken))
{
throw new Exception("未登录,请先点击功能区的【登录】按钮获取权限。");
}
// 获取当前选择的模型 // 获取当前选择的模型
string selectedModelId = Properties.Settings.Default.SelectedModel; string selectedModelId = Properties.Settings.Default.SelectedModel;
// 如果未设置,默认使用第一个 (qwen-plus)
if (string.IsNullOrEmpty(selectedModelId)) selectedModelId = "qwen-plus"; if (string.IsNullOrEmpty(selectedModelId)) selectedModelId = "qwen-plus";
var modelInfo = ModelConfig.GetModel(selectedModelId); var modelInfo = ModelConfig.GetModel(selectedModelId);
string apiKey = ""; // 指向本地 FastAPI 后端代理
string requestUrl = ""; string requestUrl = "http://127.0.0.1:8000/api/chat";
string accessToken = AiRibbon.AccessToken;
string jsonContent = ""; string jsonContent = "";
// 根据模型类型配置请求 // 统一构造 OpenAI 兼容格式请求体发送给后端
if (modelInfo.ApiType == "Native") // DashScope Native (e.g., qwen-plus) // 后端负责根据模型类型转发给 DashScope 或其他服务
{
apiKey = Config.DashScopeApiKey;
requestUrl = Config.DashScopeNativeUrl;
// 构造 DashScope Native 格式请求体
var requestBody = new
{
model = modelInfo.Id,
input = new
{
messages = messages
},
parameters = new
{
result_format = "message",
incremental_output = (onProgress != null) // 如果有进度回调,则开启增量输出
}
};
jsonContent = JsonConvert.SerializeObject(requestBody);
}
else // Compatible (TuZi, DeepSeek, etc.)
{
apiKey = Config.TuZiApiKey;
requestUrl = $"{Config.TuZiBaseUrl}/chat/completions";
// 构造 OpenAI 兼容格式请求体
var requestDict = new Dictionary<string, object> var requestDict = new Dictionary<string, object>
{ {
{ "model", modelInfo.Id }, { "model", modelInfo.Id },
@@ -475,38 +478,45 @@ namespace WordAddIn
{ "temperature", 0.7 } { "temperature", 0.7 }
}; };
// 注意:目前后端 Mock 暂时只支持非流式,但协议上保留 stream 字段
if (onProgress != null) if (onProgress != null)
{ {
requestDict["stream"] = true; requestDict["stream"] = true;
} }
// 针对 glm-4.7 开启思考模式 // 针对 glm-4.7 开启思考模式 (透传给后端)
if (modelInfo.Id == "glm-4.7") if (modelInfo.Id == "glm-4.7")
{ {
requestDict["enable_thinking"] = true; requestDict["enable_thinking"] = true;
} }
jsonContent = JsonConvert.SerializeObject(requestDict); jsonContent = JsonConvert.SerializeObject(requestDict);
}
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
// 创建新的 HttpRequestMessage 以设置特定的 Authorization Header // 创建新的 HttpRequestMessage
using (var request = new HttpRequestMessage(HttpMethod.Post, requestUrl)) using (var request = new HttpRequestMessage(HttpMethod.Post, requestUrl))
{ {
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiKey); // 使用登录获取的 Access Token
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
request.Content = content; request.Content = content;
// 如果是流式请求,使用 ResponseHeadersRead
var completionOption = onProgress != null ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead; var completionOption = onProgress != null ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead;
using (var response = await _httpClient.SendAsync(request, completionOption)) using (var response = await _httpClient.SendAsync(request, completionOption))
{ {
if (!response.IsSuccessStatusCode) if (!response.IsSuccessStatusCode)
{ {
var errorString = await response.Content.ReadAsStringAsync(); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
throw new Exception($"API Error ({response.StatusCode}): {errorString}"); {
throw new Exception("认证失效,请重新登录。");
} }
var errorString = await response.Content.ReadAsStringAsync();
throw new Exception($"后端服务错误 ({response.StatusCode}): {errorString}");
}
// ... 后续处理保持不变,后端返回格式应兼容 OpenAI ...
if (onProgress != null) if (onProgress != null)
{ {
@@ -549,38 +559,14 @@ namespace WordAddIn
var json = JObject.Parse(dataStr); var json = JObject.Parse(dataStr);
string contentDelta = ""; string contentDelta = "";
if (modelInfo.ApiType == "Native") // 统一处理:假设后端代理返回 OpenAI 兼容格式
{ // 优先尝试获取 delta (流式)
// Check for DashScope API Error in stream
if (json["code"] != null)
{
string errorCode = json["code"].ToString();
string errorMsg = json["message"]?.ToString() ?? "Unknown error";
try { System.IO.File.AppendAllText(@"C:\Users\Public\WordAddInLog.txt", $"{DateTime.Now} DashScope Stream Error: {errorCode} - {errorMsg}\n"); } catch { }
throw new Exception($"DashScope Error: {errorCode} - {errorMsg}");
}
// DashScope Native incremental output
// Priority 1: output.choices[0].message.content (Standard for result_format='message')
if (json["output"]?["choices"]?[0]?["message"]?["content"] != null)
{
contentDelta = json["output"]["choices"][0]["message"]["content"].ToString();
}
// Priority 2: output.text (Standard for result_format='text' or older models)
else if (json["output"]?["text"] != null)
{
contentDelta = json["output"]["text"].ToString();
}
// Priority 3: output.choices[0].delta.content (OpenAI compatible style)
else if (json["output"]?["choices"]?[0]?["delta"]?["content"] != null)
{
contentDelta = json["output"]["choices"][0]["delta"]["content"].ToString();
}
}
else
{
// OpenAI Compatible
contentDelta = json["choices"]?[0]?["delta"]?["content"]?.ToString(); contentDelta = json["choices"]?[0]?["delta"]?["content"]?.ToString();
// 如果没有 delta,尝试获取 message (非流式/完整包)
if (string.IsNullOrEmpty(contentDelta))
{
contentDelta = json["choices"]?[0]?["message"]?["content"]?.ToString();
} }
if (!string.IsNullOrEmpty(contentDelta)) if (!string.IsNullOrEmpty(contentDelta))
@@ -603,22 +589,15 @@ namespace WordAddIn
// 非流式处理 // 非流式处理
var responseString = await response.Content.ReadAsStringAsync(); var responseString = await response.Content.ReadAsStringAsync();
if (modelInfo.ApiType == "Native") // 统一按照 OpenAI 兼容格式解析
try
{ {
// 解析 DashScope Native 响应
var json = JObject.Parse(responseString);
var contentText = json["output"]?["text"]?.ToString();
if (contentText == null)
{
contentText = json["output"]?["choices"]?[0]?["message"]?["content"]?.ToString();
}
return contentText ?? responseString;
}
else
{
// 解析 OpenAI 兼容响应
var result = JsonConvert.DeserializeObject<ChatCompletionResponse>(responseString); var result = JsonConvert.DeserializeObject<ChatCompletionResponse>(responseString);
return result?.choices?.FirstOrDefault()?.message?.content; return result?.choices?.FirstOrDefault()?.message?.content ?? responseString;
}
catch
{
return responseString;
} }
} }
} }

View File

@@ -150,22 +150,70 @@ namespace WordAddIn
/// 登录按钮点击事件。 /// 登录按钮点击事件。
/// 验证用户名和密码。 /// 验证用户名和密码。
/// </summary> /// </summary>
private void BtnLogin_Click(object sender, EventArgs e) private async void BtnLogin_Click(object sender, EventArgs e)
{ {
string user = txtUsername.Text.Trim(); string user = txtUsername.Text.Trim();
string pass = txtPassword.Text.Trim(); string pass = txtPassword.Text.Trim();
// 模拟验证逻辑 if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(pass))
if (user == "huanghai" && pass == "12345678") {
MessageBox.Show("请输入用户名和密码!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
btnLogin.Enabled = false;
btnLogin.Text = "登录中...";
try
{
using (var client = new HttpClient())
{
// 指向本地 FastAPI 后端
string url = $"{Config.BackendBaseUrl}/token";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("username", user),
new KeyValuePair<string, string>("password", pass),
new KeyValuePair<string, string>("grant_type", "password")
});
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
string jsonResponse = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(jsonResponse);
this.Token = json["access_token"]?.ToString();
if (!string.IsNullOrEmpty(this.Token))
{ {
this.DialogResult = DialogResult.OK; this.DialogResult = DialogResult.OK;
this.Close(); this.Close();
} }
else else
{ {
MessageBox.Show("用户名或密码错误!\n(默认账号: huanghai, 密码: 12345678)", "登录失败", MessageBoxButtons.OK, MessageBoxIcon.Error); MessageBox.Show("登录失败:未能获取有效的 Token。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
txtPassword.Focus(); }
txtPassword.SelectAll(); }
else
{
string errorDetail = await response.Content.ReadAsStringAsync();
MessageBox.Show($"登录失败:{response.ReasonPhrase}\n{errorDetail}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
catch (Exception ex)
{
MessageBox.Show($"连接服务器失败:{ex.Message}\n请确保后端服务已启动 (http://127.0.0.1:8000)", "网络错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
if (!this.IsDisposed)
{
btnLogin.Enabled = true;
btnLogin.Text = "立即登录";
}
} }
} }
} }