prompt
This commit is contained in:
@@ -80,6 +80,33 @@ CLEANUP_CONFIG = {
|
||||
"interval": int(os.getenv("CLEANUP_INTERVAL_SECONDS", "600"))
|
||||
}
|
||||
|
||||
# 提示词配置 (Prompt Config)
|
||||
PROMPTS = {
|
||||
"translate": "请将以下描述翻译成简洁、精准的英文,用于图像分割模型(SAM)的提示词。直接返回英文,不要包含任何解释或其他文字。\n\n输入: {text}",
|
||||
"tarot_card_dual": """这是一张塔罗牌的两个方向:
|
||||
图1:原始方向
|
||||
图2:旋转180度后的方向
|
||||
|
||||
请仔细对比两张图片的牌面内容(文字方向、人物站立方向、图案逻辑):
|
||||
1. 识别这张牌的名字(中文)。
|
||||
2. 判断哪一张图片展示了正确的“正位”(Upright)状态。
|
||||
- 如果图1是正位,说明原图就是正位。
|
||||
- 如果图2是正位,说明原图是逆位。
|
||||
|
||||
请以JSON格式返回,包含 'name' 和 'position' 两个字段。
|
||||
例如:{'name': '愚者', 'position': '正位'} 或 {'name': '倒吊人', 'position': '逆位'}。
|
||||
不要包含Markdown代码块标记。""",
|
||||
"tarot_card_single": "这是一张塔罗牌。请识别它的名字(中文),并判断它是正位还是逆位。请以JSON格式返回,包含 'name' 和 'position' 两个字段。例如:{'name': '愚者', 'position': '正位'}。不要包含Markdown代码块标记。",
|
||||
"tarot_spread": "这是一张包含多张塔罗牌的图片。请根据牌的排列方式识别这是什么牌阵(例如:圣三角、凯尔特十字、三张牌等)。如果看不出明显的正规牌阵,请返回“不是正规牌阵”。请以JSON格式返回,包含 'spread_name' 和 'description' 两个字段。例如:{'spread_name': '圣三角', 'description': '常见的时间流占卜法'}。不要包含Markdown代码块标记。",
|
||||
"face_analysis": """请仔细观察这张图片中的人物头部/面部特写:
|
||||
1. 识别性别 (Gender):男性/女性
|
||||
2. 预估年龄 (Age):请给出一个合理的年龄范围,例如 "25-30岁"
|
||||
3. 简要描述:发型、发色、是否有眼镜等显著特征。
|
||||
|
||||
请以 JSON 格式返回,包含 'gender', 'age', 'description' 字段。
|
||||
不要包含 Markdown 标记。"""
|
||||
}
|
||||
|
||||
# API Tags (用于文档分类)
|
||||
TAG_GENERAL = "General Segmentation (通用分割)"
|
||||
TAG_TAROT = "Tarot Analysis (塔罗牌分析)"
|
||||
@@ -265,11 +292,12 @@ def translate_to_sam3_prompt(text: str) -> str:
|
||||
"""
|
||||
print(f"正在翻译提示词: {text}")
|
||||
try:
|
||||
prompt_template = PROMPTS["translate"]
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"text": f"请将以下描述翻译成简洁、精准的英文,用于图像分割模型(SAM)的提示词。直接返回英文,不要包含任何解释或其他文字。\n\n输入: {text}"}
|
||||
{"text": prompt_template.format(text=text)}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -503,19 +531,7 @@ def recognize_card_with_qwen(image_path: str) -> dict:
|
||||
"content": [
|
||||
{"image": file_url}, # 图1 (原图)
|
||||
{"image": rotated_file_url}, # 图2 (旋转180度)
|
||||
{"text": """这是一张塔罗牌的两个方向:
|
||||
图1:原始方向
|
||||
图2:旋转180度后的方向
|
||||
|
||||
请仔细对比两张图片的牌面内容(文字方向、人物站立方向、图案逻辑):
|
||||
1. 识别这张牌的名字(中文)。
|
||||
2. 判断哪一张图片展示了正确的“正位”(Upright)状态。
|
||||
- 如果图1是正位,说明原图就是正位。
|
||||
- 如果图2是正位,说明原图是逆位。
|
||||
|
||||
请以JSON格式返回,包含 'name' 和 'position' 两个字段。
|
||||
例如:{'name': '愚者', 'position': '正位'} 或 {'name': '倒吊人', 'position': '逆位'}。
|
||||
不要包含Markdown代码块标记。"""}
|
||||
{"text": PROMPTS["tarot_card_dual"]}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -527,7 +543,7 @@ def recognize_card_with_qwen(image_path: str) -> dict:
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"image": file_url},
|
||||
{"text": "这是一张塔罗牌。请识别它的名字(中文),并判断它是正位还是逆位。请以JSON格式返回,包含 'name' 和 'position' 两个字段。例如:{'name': '愚者', 'position': '正位'}。不要包含Markdown代码块标记。"}
|
||||
{"text": PROMPTS["tarot_card_single"]}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -562,7 +578,7 @@ def recognize_spread_with_qwen(image_path: str) -> dict:
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"image": file_url},
|
||||
{"text": "这是一张包含多张塔罗牌的图片。请根据牌的排列方式识别这是什么牌阵(例如:圣三角、凯尔特十字、三张牌等)。如果看不出明显的正规牌阵,请返回“不是正规牌阵”。请以JSON格式返回,包含 'spread_name' 和 'description' 两个字段。例如:{'spread_name': '圣三角', 'description': '常见的时间流占卜法'}。不要包含Markdown代码块标记。"}
|
||||
{"text": PROMPTS["tarot_spread"]}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1034,7 +1050,8 @@ async def segment_face(
|
||||
image=image,
|
||||
prompt=final_prompt,
|
||||
output_base_dir=RESULT_IMAGE_DIR,
|
||||
qwen_model=QWEN_MODEL
|
||||
qwen_model=QWEN_MODEL,
|
||||
analysis_prompt=PROMPTS["face_analysis"]
|
||||
)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
@@ -1268,6 +1285,27 @@ async def set_model(model: str = Form(...)):
|
||||
QWEN_MODEL = model
|
||||
return {"status": "success", "message": f"Model switched to {model}", "current_model": QWEN_MODEL}
|
||||
|
||||
@app.get("/admin/api/prompts", dependencies=[Depends(verify_admin)])
|
||||
async def get_prompts():
|
||||
"""
|
||||
Get all prompts
|
||||
"""
|
||||
return PROMPTS
|
||||
|
||||
@app.post("/admin/api/prompts", dependencies=[Depends(verify_admin)])
|
||||
async def update_prompts(
|
||||
key: str = Form(...),
|
||||
content: str = Form(...)
|
||||
):
|
||||
"""
|
||||
Update a specific prompt
|
||||
"""
|
||||
if key not in PROMPTS:
|
||||
raise HTTPException(status_code=400, detail="Invalid prompt key")
|
||||
|
||||
PROMPTS[key] = content
|
||||
return {"status": "success", "message": f"Prompt '{key}' updated"}
|
||||
|
||||
# ==========================================
|
||||
# 10. Main Entry Point (启动入口)
|
||||
# ==========================================
|
||||
|
||||
@@ -95,7 +95,7 @@ def create_highlighted_visualization(image: Image.Image, masks, output_path: str
|
||||
# Save
|
||||
Image.fromarray(result_np).save(output_path)
|
||||
|
||||
def analyze_demographics_with_qwen(image_path: str, model_name: str = 'qwen-vl-max') -> dict:
|
||||
def analyze_demographics_with_qwen(image_path: str, model_name: str = 'qwen-vl-max', prompt_template: str = None) -> dict:
|
||||
"""
|
||||
调用 Qwen-VL 模型分析人物的年龄和性别
|
||||
"""
|
||||
@@ -104,19 +104,24 @@ def analyze_demographics_with_qwen(image_path: str, model_name: str = 'qwen-vl-m
|
||||
abs_path = os.path.abspath(image_path)
|
||||
file_url = f"file://{abs_path}"
|
||||
|
||||
# 默认 Prompt
|
||||
default_prompt = """请仔细观察这张图片中的人物头部/面部特写:
|
||||
1. 识别性别 (Gender):男性/女性
|
||||
2. 预估年龄 (Age):请给出一个合理的年龄范围,例如 "25-30岁"
|
||||
3. 简要描述:发型、发色、是否有眼镜等显著特征。
|
||||
|
||||
请以 JSON 格式返回,包含 'gender', 'age', 'description' 字段。
|
||||
不要包含 Markdown 标记。"""
|
||||
|
||||
final_prompt = prompt_template if prompt_template else default_prompt
|
||||
|
||||
# 构造 Prompt
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"image": file_url},
|
||||
{"text": """请仔细观察这张图片中的人物头部/面部特写:
|
||||
1. 识别性别 (Gender):男性/女性
|
||||
2. 预估年龄 (Age):请给出一个合理的年龄范围,例如 "25-30岁"
|
||||
3. 简要描述:发型、发色、是否有眼镜等显著特征。
|
||||
|
||||
请以 JSON 格式返回,包含 'gender', 'age', 'description' 字段。
|
||||
不要包含 Markdown 标记。"""}
|
||||
{"text": final_prompt}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -144,7 +149,8 @@ def process_face_segmentation_and_analysis(
|
||||
image: Image.Image,
|
||||
prompt: str = "head",
|
||||
output_base_dir: str = "static/results",
|
||||
qwen_model: str = "qwen-vl-max"
|
||||
qwen_model: str = "qwen-vl-max",
|
||||
analysis_prompt: str = None
|
||||
) -> dict:
|
||||
"""
|
||||
核心处理逻辑:
|
||||
@@ -209,7 +215,7 @@ def process_face_segmentation_and_analysis(
|
||||
cropped_img.save(save_path)
|
||||
|
||||
# 3. 识别
|
||||
analysis = analyze_demographics_with_qwen(save_path, model_name=qwen_model)
|
||||
analysis = analyze_demographics_with_qwen(save_path, model_name=qwen_model, prompt_template=analysis_prompt)
|
||||
|
||||
# 构造返回结果
|
||||
# 注意:URL 生成需要依赖外部的 request context,这里只返回相对路径或文件名
|
||||
|
||||
@@ -13,7 +13,7 @@ SCRIPT_NAME="fastAPI_tarot.py" # Python 启动脚本
|
||||
LOG_FILE="${PROJECT_DIR}/log/monitor.log" # 监控日志文件
|
||||
APP_LOG_FILE="${PROJECT_DIR}/log/app.log" # 应用输出日志文件
|
||||
PORT=55600 # 服务端口
|
||||
CHECK_INTERVAL=20 # 检查间隔(秒)
|
||||
CHECK_INTERVAL=60 # 检查间隔(秒)
|
||||
MAX_FAILURES=3 # 最大连续失败次数,超过则重启
|
||||
STARTUP_TIMEOUT=300 # 启动超时时间(秒),等待模型加载
|
||||
PYTHON_CMD="python" # Python 命令,根据环境可能是 python3
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
<a href="#" @click.prevent="currentTab = 'files'" :class="{'bg-blue-600': currentTab === 'files', 'hover:bg-slate-700': currentTab !== 'files'}" class="block py-2.5 px-4 rounded transition duration-200">
|
||||
<i class="fas fa-folder-open w-6"></i> 文件管理
|
||||
</a>
|
||||
<a href="#" @click.prevent="currentTab = 'prompts'" :class="{'bg-blue-600': currentTab === 'prompts', 'hover:bg-slate-700': currentTab !== 'prompts'}" class="block py-2.5 px-4 rounded transition duration-200">
|
||||
<i class="fas fa-comment-dots w-6"></i> 提示词管理
|
||||
</a>
|
||||
<a href="#" @click.prevent="currentTab = 'settings'" :class="{'bg-blue-600': currentTab === 'settings', 'hover:bg-slate-700': currentTab !== 'settings'}" class="block py-2.5 px-4 rounded transition duration-200">
|
||||
<i class="fas fa-cogs w-6"></i> 系统设置
|
||||
</a>
|
||||
@@ -164,6 +167,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示词管理 Prompts -->
|
||||
<div v-if="currentTab === 'prompts'">
|
||||
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-2">提示词管理</h2>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div v-for="(content, key) in prompts" :key="key" class="bg-white rounded-lg shadow p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-bold text-gray-700 flex items-center gap-2">
|
||||
<span class="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded border border-blue-400 font-mono">{{ key }}</span>
|
||||
<span class="text-sm font-normal text-gray-500">{{ getPromptDescription(key) }}</span>
|
||||
</h3>
|
||||
<button @click="savePrompt(key)" class="bg-green-500 hover:bg-green-600 text-white font-bold py-1 px-3 rounded text-sm transition">
|
||||
<i class="fas fa-save mr-1"></i> 保存
|
||||
</button>
|
||||
</div>
|
||||
<textarea v-model="prompts[key]" rows="6" class="w-full p-3 border rounded font-mono text-sm bg-gray-50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 transition"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统设置 Settings -->
|
||||
<div v-if="currentTab === 'settings'">
|
||||
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-2">系统设置</h2>
|
||||
@@ -266,6 +289,7 @@
|
||||
lifetime: 3600,
|
||||
interval: 600
|
||||
});
|
||||
const prompts = ref({});
|
||||
|
||||
// 检查登录状态
|
||||
const checkLogin = () => {
|
||||
@@ -288,6 +312,7 @@
|
||||
loginError.value = '';
|
||||
fetchHistory();
|
||||
fetchSystemInfo();
|
||||
fetchPrompts();
|
||||
}
|
||||
} catch (e) {
|
||||
loginError.value = '密码错误';
|
||||
@@ -347,6 +372,38 @@
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPrompts = async () => {
|
||||
try {
|
||||
const res = await axios.get('/admin/api/prompts');
|
||||
prompts.value = res.data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const savePrompt = async (key) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('key', key);
|
||||
formData.append('content', prompts.value[key]);
|
||||
const res = await axios.post('/admin/api/prompts', formData);
|
||||
alert(res.data.message);
|
||||
} catch (e) {
|
||||
alert('保存失败: ' + (e.response?.data?.detail || e.message));
|
||||
}
|
||||
};
|
||||
|
||||
const getPromptDescription = (key) => {
|
||||
const map = {
|
||||
'translate': 'Prompt 翻译 (中文 -> 英文)',
|
||||
'tarot_card_dual': '塔罗牌识别 (正/逆位对比模式)',
|
||||
'tarot_card_single': '塔罗牌识别 (单图模式)',
|
||||
'tarot_spread': '塔罗牌阵识别',
|
||||
'face_analysis': '人脸/属性分析 (Qwen-VL)'
|
||||
};
|
||||
return map[key] || '';
|
||||
};
|
||||
|
||||
const updateModel = async () => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -439,6 +496,7 @@
|
||||
Vue.watch(currentTab, (newTab) => {
|
||||
if (newTab === 'files') fetchFiles();
|
||||
if (newTab === 'dashboard') fetchHistory();
|
||||
if (newTab === 'prompts') fetchPrompts();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
@@ -452,7 +510,8 @@
|
||||
viewResult, previewImage, isImage, previewUrl,
|
||||
formatDate, getTypeBadgeClass, cleaning, deviceInfo,
|
||||
currentModel, availableModels, updateModel,
|
||||
cleanupConfig, saveCleanupConfig
|
||||
cleanupConfig, saveCleanupConfig,
|
||||
prompts, fetchPrompts, savePrompt, getPromptDescription
|
||||
};
|
||||
}
|
||||
}).mount('#app');
|
||||
|
||||
Reference in New Issue
Block a user