From da8c19c2e8272732eb75981cac97261f8f727184 Mon Sep 17 00:00:00 2001 From: shiyu Date: Mon, 26 May 2025 14:43:56 +0800 Subject: [PATCH] feat(AiService, Config, DatabaseInitializer, SystemConfig): enhance AI prompt configurations and improve UI for prompt management --- Models/DataBase/Config.cs | 2 +- Services/AI/AiService.cs | 29 ++-- Services/Initializer/DatabaseInitializer.cs | 61 +++++--- Web/src/pages/settings/SystemConfig.tsx | 145 +++++++++++++++++--- 4 files changed, 187 insertions(+), 50 deletions(-) diff --git a/Models/DataBase/Config.cs b/Models/DataBase/Config.cs index 60ebf8b..2794a69 100644 --- a/Models/DataBase/Config.cs +++ b/Models/DataBase/Config.cs @@ -9,7 +9,7 @@ public class Config : BaseModel public string Key { get; set; } = string.Empty; [Required] - [StringLength(255)] + [StringLength(1000)] public string Value { get; set; } = string.Empty; [StringLength(255)] diff --git a/Services/AI/AiService.cs b/Services/AI/AiService.cs index 07d8c1a..5f3fe88 100644 --- a/Services/AI/AiService.cs +++ b/Services/AI/AiService.cs @@ -59,7 +59,7 @@ public class AiService : IAiService var textContent = new TextContent { Type = "text", - Text = + Text = _configService["AI:ImageAnalysisPrompt"] ?? "请详细分析这张图片,并提供全面的描述,以便用于向量嵌入和基于文本的图像搜索。描述需要包含:主体对象、场景环境、色彩特点、构图布局、风格特征、情绪氛围、细节特征等关键元素。请提供一个简短有力的标题,然后提供详细描述。\n\n请以JSON格式返回,格式如下:\n{\"title\": \"简短概括图片的核心内容\", \"description\": \"全面详细的描述,包含上述所有元素,使用丰富精确的词汇,避免笼统表达\"}\n\n请确保返回有效的JSON格式。" }; @@ -103,7 +103,6 @@ public class AiService : IAiService { try { - // 获取配置好的 HttpClient var client = ConfigureHttpClient(); if (availableTags.Count == 0) @@ -111,11 +110,19 @@ public class AiService : IAiService string model = _configService["AI:Model"]; var tagsText = string.Join(", ", availableTags); + + string promptTemplate = _configService["AI:TagMatchingPrompt"] ?? + "以下是一组标签:[{tagsText}]。\n\n请从这些标签中严格选择与下面描述内容高度相关的标签(最多选择5个)。只选择确实匹配的标签,如果找不到完全匹配或高度相关的标签,宁可返回空数组也不要选择不太相关的标签。\n\n描述内容:{description}\n\n请以JSON格式返回,格式如下:\n{{\"tags\": [\"标签1\", \"标签2\", \"标签3\"]}}\n\n请确保返回有效的JSON格式前面不要加```,并且只包含确实匹配的标签名称。"; + + // 替换占位符 + string promptText = promptTemplate + .Replace("{tagsText}", tagsText) + .Replace("{description}", description); + var textContent = new TextContent { Type = "text", - Text = - $"以下是一组标签:[{tagsText}]。\n\n请从这些标签中严格选择与下面描述内容高度相关的标签(最多选择5个)。只选择确实匹配的标签,如果找不到完全匹配或高度相关的标签,宁可返回空数组也不要选择不太相关的标签。\n\n描述内容:{description}\n\n请以JSON格式返回,格式如下:\n{{\"tags\": [\"标签1\", \"标签2\", \"标签3\"]}}\n\n请确保返回有效的JSON格式前面不要加```,并且只包含确实匹配的标签名称。" + Text = promptText }; var message = new ChatMessage @@ -232,10 +239,14 @@ public class AiService : IAiService if (allowNewTags) { + // 获取配置的标签生成提示词,如果没有则使用默认提示词 + string defaultPrompt = _configService["AI:TagGenerationPrompt"] ?? + "请为图片生成5个最相关的标签,每个标签应该是简短且描述性的词语或短语。\n\n请以JSON格式返回,格式如下:\n{\"tags\": [\"标签1\", \"标签2\", \"标签3\", \"标签4\", \"标签5\"]}\n\n请确保返回有效的JSON格式。"; + // 如果允许新标签,则提供现有标签作为参考,但允许生成新标签 promptText = availableTags.Count > 0 - ? $"可以参考这些现有标签:[{string.Join(", ", availableTags)}],但也可以生成其他与图片内容相关的新标签。\n\n请为图片生成5个最相关的标签,优先使用已有标签,但如果有更恰当的新标签也可以使用。\n\n请以JSON格式返回,格式如下:\n{{\"tags\": [\"标签1\", \"标签2\", \"标签3\", \"标签4\", \"标签5\"]}}\n\n请确保返回有效的JSON格式。" - : "请为图片生成5个最相关的标签,每个标签应该是简短且描述性的词语或短语。\n\n请以JSON格式返回,格式如下:\n{\"tags\": [\"标签1\", \"标签2\", \"标签3\", \"标签4\", \"标签5\"]}\n\n请确保返回有效的JSON格式。"; + ? $"可以参考这些现有标签:[{string.Join(", ", availableTags)}],但也可以生成其他与图片内容相关的新标签。\n\n{defaultPrompt}" + : defaultPrompt; } else { @@ -244,8 +255,10 @@ public class AiService : IAiService return new List(); var tagsText = string.Join(", ", availableTags); - promptText = - $"以下是一组标签:[{tagsText}]。\n\n请从这些标签中严格选择与图片内容高度相关的标签(最多选择5个)。只选择确实匹配的标签,如果找不到完全匹配或高度相关的标签,宁可返回空数组也不要选择不太相关的标签。\n\n请以JSON格式返回,格式如下:\n{{\"tags\": [\"标签1\", \"标签2\", \"标签3\"]}}\n\n请确保返回有效的JSON格式,并且只包含上述列表中的标签名称。"; + string templatePrompt = _configService["AI:TagMatchingPrompt"] ?? + "以下是一组标签:[{tagsText}]。\n\n请从这些标签中严格选择与图片内容高度相关的标签(最多选择5个)。只选择确实匹配的标签,如果找不到完全匹配或高度相关的标签,宁可返回空数组也不要选择不太相关的标签。\n\n请以JSON格式返回,格式如下:\n{{\"tags\": [\"标签1\", \"标签2\", \"标签3\"]}}\n\n请确保返回有效的JSON格式,并且只包含上述列表中的标签名称。"; + + promptText = templatePrompt.Replace("{tagsText}", tagsText); } var textContent = new TextContent diff --git a/Services/Initializer/DatabaseInitializer.cs b/Services/Initializer/DatabaseInitializer.cs index 03336d5..cfdff2d 100644 --- a/Services/Initializer/DatabaseInitializer.cs +++ b/Services/Initializer/DatabaseInitializer.cs @@ -21,7 +21,7 @@ public class DatabaseInitializer( // 检查是否已经完成初始化 if (await configService.ExistsAsync(InitializationFlag) && - configService[InitializationFlag] == "true") + configService[InitializationFlag] == "true") { logger.LogInformation("数据库已完成初始化,跳过初始化步骤"); return; @@ -34,27 +34,43 @@ public class DatabaseInitializer( // 确保数据库已创建 await context.Database.EnsureCreatedAsync(); - // 初始化JWT配置 - await EnsureConfigExistsAsync("Jwt:SecretKey", "ChAtPiCdEfAuLtSeCrEtKeY2023_Extended_Secure_Key"); - await EnsureConfigExistsAsync("Jwt:Issuer", "Foxel"); - await EnsureConfigExistsAsync("Jwt:Audience", "FoxelUsers"); + // 初始化默认配置 + var defaultConfigs = new Dictionary + { + // JWT配置 + ["Jwt:SecretKey"] = "ChAtPiCdEfAuLtSeCrEtKeY2023_Extended_Secure_Key", + ["Jwt:Issuer"] = "Foxel", + ["Jwt:Audience"] = "FoxelUsers", - // 初始化GitHub认证配置 - await EnsureConfigExistsAsync("Authentication:GitHubClientId", "placeholder_replace_with_actual_github_client_id"); - await EnsureConfigExistsAsync("Authentication:GitHubClientSecret", "placeholder_replace_with_actual_github_client_secret"); - await EnsureConfigExistsAsync("Authentication:GitHubCallbackUrl", ""); - - // 初始化AI相关配置 - await EnsureConfigExistsAsync("AI:ApiEndpoint", ""); - await EnsureConfigExistsAsync("AI:ApiKey", ""); - await EnsureConfigExistsAsync("AI:Model", ""); - await EnsureConfigExistsAsync("AI:EmbeddingModel", ""); - // 初始化存储配置 - await EnsureConfigExistsAsync("Storage:TelegramStorageBotToken", ""); - await EnsureConfigExistsAsync("Storage:TelegramStorageChatId", ""); - await EnsureConfigExistsAsync("Storage:DefaultStorage", "Local"); - // 初始化其他配置 - await EnsureConfigExistsAsync("AppSettings:ServerUrl", ""); + // GitHub认证配置 + ["Authentication:GitHubClientId"] = "placeholder_replace_with_actual_github_client_id", + ["Authentication:GitHubClientSecret"] = "placeholder_replace_with_actual_github_client_secret", + ["Authentication:GitHubCallbackUrl"] = "", + + // AI相关配置 + ["AI:ApiEndpoint"] = "", + ["AI:ApiKey"] = "", + ["AI:Model"] = "", + ["AI:EmbeddingModel"] = "", + ["AI:ImageAnalysisPrompt"] = + "Please analyze the given image in detail and provide a comprehensive description suitable for **vector embedding** and **text-based image retrieval**. Your description must include the following key elements:\n\n- **Main subject**\n- **Scene environment**\n- **Color characteristics**\n- **Composition layout**\n- **Stylistic features**\n- **Emotional atmosphere**\n- **Fine-grained details**\n\nReturn your response in **valid JSON format** as shown below:\n\n```json\n{\n \"title\": \"用中文简要概括图像核心内容的标题\",\n \"description\": \"使用中文全面、详细地描述图像内容,涵盖上述所有要素。使用丰富且精确的词汇,避免模糊或通用的表述。描述不得超过2000个字符。\"\n}\n```\n\n⚠️ Make sure:\n- Both `title` and `description` must be written in **Chinese**.\n- The `description` must be **rich, accurate**, and **strictly under 2000 characters**.\n- The output must be **valid JSON only**, with no code fences or extra formatting.", + ["AI:TagGenerationPrompt"] = + "Please generate **5 most relevant tags** for the given image. Each tag should be a **short and descriptive** word or phrase that accurately reflects key visual or thematic elements of the image.\n\nReturn your response in **valid JSON format** as shown below:\n\n```json\n{\n \"tags\": [\"标签1\", \"标签2\", \"标签3\", \"标签4\", \"标签5\"]\n}\n```\n\nMake sure the output is **strictly valid JSON**.", + ["AI:TagMatchingPrompt"] = + "Given a list of tags: `[{tagsText}]`\n\nPlease strictly select only those tags that are **highly relevant** to the following description (select **up to 5**). Only include tags that **exactly or strongly match** the content. If **none** of the tags are a good match, return an **empty array** instead of including loosely related ones.\n\n**Description:**\n{description}\n\nReturn your response in **valid JSON format** as shown below:\n\n```json\n{\n \"tags\": [\"标签1\", \"标签2\", \"标签3\", \"标签4\", \"标签5\"]\n}\n```\n\n⚠️ Do **not** include code fences (no triple backticks), and ensure the JSON is **valid** and includes only truly matching tag names.", + // 存储配置 + ["Storage:TelegramStorageBotToken"] = "", + ["Storage:TelegramStorageChatId"] = "", + ["Storage:DefaultStorage"] = "Local", + + // 其他配置 + ["AppSettings:ServerUrl"] = "" + }; + + foreach (var (key, value) in defaultConfigs) + { + await EnsureConfigExistsAsync(key, value); + } // 初始化管理员角色和用户 await InitializeAdminRoleAndUserAsync(); @@ -111,6 +127,7 @@ public class DatabaseInitializer( await context.Roles.AddAsync(adminRole); await context.SaveChangesAsync(); } + logger.LogInformation("请注意,第一个注册的用户将自动成为管理员"); } -} +} \ No newline at end of file diff --git a/Web/src/pages/settings/SystemConfig.tsx b/Web/src/pages/settings/SystemConfig.tsx index ec7711a..7b68ea5 100644 --- a/Web/src/pages/settings/SystemConfig.tsx +++ b/Web/src/pages/settings/SystemConfig.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { Tabs, Card, message, Spin, Select, Button, Upload, Modal, Space, Tooltip } from 'antd'; +import { Tabs, Card, message, Spin, Select, Button, Upload, Modal, Space, Tooltip, Input } from 'antd'; import { CloudOutlined, DatabaseOutlined, CloudServerOutlined, GlobalOutlined, DownloadOutlined, UploadOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { getAllConfigs, setConfig, backupConfigs, restoreConfigs } from '../../api'; import ConfigGroup from './ConfigGroup.tsx'; @@ -243,24 +243,131 @@ const SystemConfig: React.FC = () => { }} > - + + + + + + + { + const newConfigs = { ...configs }; + if (!newConfigs.AI) newConfigs.AI = {}; + newConfigs.AI.ImageAnalysisPrompt = e.target.value; + setConfigs(newConfigs); + }} + /> +
+ +
+
+ 用于分析图片内容并提取描述的提示词。请确保提示词包含返回JSON格式的指示,并且要求返回标题(title)和描述(description)字段。 +
+
+ + + { + const newConfigs = { ...configs }; + if (!newConfigs.AI) newConfigs.AI = {}; + newConfigs.AI.TagGenerationPrompt = e.target.value; + setConfigs(newConfigs); + }} + /> +
+ +
+
+ 用于从图片内容生成标签的提示词。请确保提示词包含返回JSON格式的指示,并且要求返回tags数组字段。 +
+
+ + + { + const newConfigs = { ...configs }; + if (!newConfigs.AI) newConfigs.AI = {}; + newConfigs.AI.TagMatchingPrompt = e.target.value; + setConfigs(newConfigs); + }} + /> +
+ +
+
+ 用于将描述内容与已有标签进行匹配的提示词。请确保提示词包含{'{'+'tagsText'+'}'}和{'{'+'description'+'}'}占位符,系统将会用实际的标签列表和描述内容替换这些占位符。 +
+
+
+