From f38e88d04b0c41ea08458ef966300452be5d271c Mon Sep 17 00:00:00 2001 From: Yiwenjia <3025767941@qq.com> Date: Wed, 28 May 2025 21:42:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0=20LLM?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E7=94=9F=E6=88=90=E6=B5=B7=E6=8A=A5=E6=96=87?= =?UTF-8?q?=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 1 + README.md | 42 +++++- configs/font.yaml | 83 +++++++++++ nohup.out | 27 ---- outputs/generated_code.jsx | 51 ------- outputs/poster_content.json | 16 +++ scripts/generate_layout.py | 140 ------------------- scripts/generate_text.py | 272 ++++++++++++++++++++++++++++++++++++ serve.py | 73 ---------- 9 files changed, 413 insertions(+), 292 deletions(-) create mode 100644 .env create mode 100644 configs/font.yaml delete mode 100644 nohup.out delete mode 100644 outputs/generated_code.jsx create mode 100644 outputs/poster_content.json delete mode 100644 scripts/generate_layout.py create mode 100644 scripts/generate_text.py delete mode 100644 serve.py diff --git a/.env b/.env new file mode 100644 index 0000000..04968ad --- /dev/null +++ b/.env @@ -0,0 +1 @@ +MOONSHOT_API_KEY=sk-Xzv3OqwLoM1RRsXIQuEhLMNATrMv2JsLwf7LbM97Ha6sFDun \ No newline at end of file diff --git a/README.md b/README.md index 2b2a346..c931b5f 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,44 @@ python generate_layout.py ``` - 该脚本会自动调用 DeepSeek API 生成 React 组件代码,并将其保存到指定路径。 - 默认为``output/generated_code.jsx``,可以根据需要修改。 \ No newline at end of file + 默认为``output/generated_code.jsx``,可以根据需要修改。 + +## 图层叠加和PSD导出部分 +### 功能组件 +- **图层合成**:根据生成的布局配置,将各个图层叠加在一起,生成完整的海报图像。 +- **PSD导出**:将合成的海报图像保存为标准的PSD格式,同时保留各个图层的独立性,方便后续编辑。 +### 如何调用 +1. **环境准备**: +```bash +conda create -n ai_service python=3.8 +conda activate ai_service +pip install -r requirements.txt +``` +2. **运行脚本**: +```bash +python export_psd.py #简单生成 +python export_psd_from_json.py #从json文件加载布局并生成 +python PSD_test.py #在run_pipline.py中的调用方法 +``` +该脚本会自动调用图层合成模块生成完整海报图像,并将其保存为标准PSD格式。 + +## 文案生成 +### 功能组件 +使用 kimi API 提供的 LLM(大型语言模型)功能,用于生成海报文案设计。当前实现的功能包括: + +- **标题生成**:根据海报主题生成适合的标题。 +- **副标题生成**:提供与主题相关的副标题或说明文案。 +- **字体与颜色建议**:为生成的文案提供适合的字体和颜色建议。 + +### 如何调用 +1. **环境准备**: + - 确保已安装 Python 环境和必要依赖(`openai`、`python-dotenv`等)。 + - 在项目根目录的 `.env` 文件中配置 `MOONSHOT_API_KEY`。 + +2. **运行脚本**: + - 直接运行 `generate_text.py` 脚本: + ```bash + python generate_text.py + ``` + - 该脚本会自动调用 kimi API 生成适合海报主题的标题、副标题和 Logo 文案,并提供字体和颜色建议,并将其保存到指定路径。 + 默认为``output/poster_content.json``,可以根据需要修改。 \ No newline at end of file diff --git a/configs/font.yaml b/configs/font.yaml new file mode 100644 index 0000000..5facdf8 --- /dev/null +++ b/configs/font.yaml @@ -0,0 +1,83 @@ +# 颜色常量 +NAMING_COLORS: + NANKAI_PURPLE: "#7E0C6E" + LOGO_WHITE: "#FFFFFF" + DARK_TEXT_ON_LIGHT_BG: "#000000" # 示例深色文字 + LIGHT_TEXT_ON_DARK_BG: "#f4efd9" # 示例浅色文字 + +# 可用字体列表 +# name: 字体文件在系统中的名称或前端能识别的名称 +# tags: 用于规则匹配的标签 + +available_fonts: + - name: "FZLanTingHei-ExtraBold-GB" # 方正兰亭特黑简体 (假设这是实际的font-family名) + displayName: "方正兰亭特黑简体" + tags: ["现代", "力量感", "标题", "正式", "无衬线", "醒目"] + roles: ["title"] + + - name: "Adobe Song Std L" # adobe宋体 (L 通常表示 Light weight) + displayName: "Adobe 宋体 Std L" + tags: ["传统", "正文", "经典", "宋体"] + roles: ["subtitle", "content"] + + - name: "SimHei" # 黑体 简 + displayName: "黑体 (简体)" + tags: ["通用", "简约", "正文", "无衬线", "清晰"] + roles: ["title", "subtitle", "content"] # 黑体用途广泛 + + - name: "Hiragino Sans GB W3" # 冬青黑体 (W3 通常表示字重) + displayName: "冬青黑体 W3" + tags: ["现代", "清晰", "正文", "优雅", "无衬线"] + roles: ["subtitle", "content"] + +# 核心样式规则 +# 主题 -> 图层ID -> 样式定义 +STYLE_RULES: + "世界读书日": # 对应海报主题 + "layer6_title": # 对应文字图层6:大标题 + font_prefs: # 字体选择偏好 + - name: "ImpactfulModernSans" # 首选这个名字的字体 + - tags: ["title", "bold", "modern_sans"] # 如果首选找不到,则找带这些标签的 + font_size_category: "largest" + color_on_light_bg: DARK_TEXT_ON_LIGHT_BG # 使用上面定义的颜色名 + color_on_dark_bg: LIGHT_TEXT_ON_DARK_BG + position_hint: "图片上方、居中、不遮挡图像" + "layer7_subtitle": # 对应文字图层7:小字/文案 + font_prefs: + - name: "ReadableBodySans" + - tags: ["body", "readable_sans"] + font_size_category: "small_below_title" + color_on_light_bg: DARK_TEXT_ON_LIGHT_BG + color_on_dark_bg: LIGHT_TEXT_ON_DARK_BG + position_hint: "标题下方" + + "南开校庆": + "layer6_title": + font_prefs: + - name: "南开官方标准黑体" + - tags: ["formal", "title"] + font_size_category: "largest" + color_on_light_bg: NANKAI_PURPLE + color_on_dark_bg: LOGO_WHITE + position_hint: "图片上方、居中、庄重" + + "通用默认": # 如果主题未匹配,则使用这里的规则 + "default_title": # 给一个默认的标题图层ID + font_prefs: + - tags: ["title", "bold"] + font_size_category: "largest" + color_on_light_bg: "#1A1A1A" + color_on_dark_bg: "#E5E5E5" + "default_body": + font_prefs: + - tags: ["body", "readable_sans"] + font_size_category: "normal" + color_on_light_bg: "#000000" + color_on_dark_bg: "#ffffff" + +# Logo的颜色规则 (独立于普通文字图层,像是对图像素材的选择) +LOGO_RULES: + "layer5_nankai_logo": + description: "南开大学官方logo,位于四角,预留空隙" + color_on_light_bg: NANKAI_PURPLE + color_on_dark_bg: LOGO_WHITE \ No newline at end of file diff --git a/nohup.out b/nohup.out deleted file mode 100644 index ca741b7..0000000 --- a/nohup.out +++ /dev/null @@ -1,27 +0,0 @@ -python3: can't open file '/var/www/ai_service/your_server.py': [Errno 2] No such file or directory -python3: can't open file '/var/www/ai_service/aihelp.py': [Errno 2] No such file or directory -2025-04-10 10:12:52,702 - INFO - Starting server on port 9000 -2025-04-10 10:13:17,949 - ERROR - Port 9000 is already in use -Traceback (most recent call last): - File "/var/www/ai_service/apihelp.py", line 4, in -Traceback (most recent call last): - File "/var/www/ai_service/apihelp.py", line 4, in - from c import HTTPServer, BaseHTTPRequestHandler - from c import HTTPServer, BaseHTTPRequestHandler -ModuleNotFoundError: No module named 'c' -ModuleNotFoundError: No module named 'c' -Error in sys.excepthook: -Traceback (most recent call last): - File "/usr/lib/python3/dist-packages/apport_python_hook.py", line 228, in partial_apport_excepthook - return apport_excepthook(binary, exc_type, exc_obj, exc_tb) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "/usr/lib/python3/dist-packages/apport_python_hook.py", line 144, in apport_excepthook - os.open(pr_filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o640), "wb" - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -FileExistsError: [Errno 17] File exists: '/var/crash/_var_www_ai_service_apihelp.py.0.crash' - -Original exception was: -Traceback (most recent call last): - File "/var/www/ai_service/apihelp.py", line 4, in - from c import HTTPServer, BaseHTTPRequestHandler -ModuleNotFoundError: No module named 'c' diff --git a/outputs/generated_code.jsx b/outputs/generated_code.jsx deleted file mode 100644 index 5970be7..0000000 --- a/outputs/generated_code.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; - -const DragonBoatFestivalPoster = () => { - return ( -
- {/* 背景图层 */} -
- - {/* 主体图层 */} -
-
-
-
- - {/* 活动亮点 */} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - {/* 页脚 */} -
-
-
-
-
- ); -}; - -export default DragonBoatFestivalPoster; \ No newline at end of file diff --git a/outputs/poster_content.json b/outputs/poster_content.json new file mode 100644 index 0000000..505d224 --- /dev/null +++ b/outputs/poster_content.json @@ -0,0 +1,16 @@ +{ + "layer5_logo_content": { + "text": "", + "color": "#000000" + }, + "layer6_title_content": { + "content": "世界读书日", + "font_name": "FZLanTingHei-ExtraBold-GB", + "color": "#000000" + }, + "layer7_subtitle_content": { + "content": "阅古今中外,启智慧之门", + "font_name": "Adobe Song Std L", + "color": "#4F4F4F" + } +} \ No newline at end of file diff --git a/scripts/generate_layout.py b/scripts/generate_layout.py deleted file mode 100644 index 56906c4..0000000 --- a/scripts/generate_layout.py +++ /dev/null @@ -1,140 +0,0 @@ -import os -from openai import OpenAI -from dotenv import load_dotenv -import time -import logging - - -# === Config LLM call === -load_dotenv() -deepseek_url = 'https://api.deepseek.com/v1' # set to be compatible with the OpenAI API -deepseek_api = os.getenv("DEEPSEEK_API_KEY") -if not deepseek_api: - raise ValueError("DEEPSEEK_API_KEY not set!") - -# 配置日志 -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - - -# === prompts and parameters === -def call_deepseek( - messages=None, - system_prompt="你是一个擅长前端开发的AI,专注于生成Vue.js代码。", - prompt=None, - model='deepseek-chat', - temperature=0.6, - max_tokens=1000, - stream=False, - max_retries=3, -): - """ - 调用 DeepSeek API,支持多轮对话和流式/非流式响应 - - Args: - messages (list): 对话消息列表,格式为[{"role": "user", "content": "..."},...]。 - 如果提供,则优先使用,否则根据 system_prompt和 prompt 构造. - system_prompt (str): 系统提示词,仅在 message 未提供时使用. - prompt (str): 用户提示词,仅在 message 未提供时使用. - model (str): 模型名称,默认为 'deepseek-chat'。 - temperature (float): 温度参数,控制生成文本的随机性,默认为 0.6。 - max_tokens (int): 最大生成 token 数量,默认为 1000。 - stream (bool): 是否使用流式响应,默认为 False。 - max_retries (int): 最大重试次数,默认为 3。 - - Returns: - tuple: (result, usage) - - result (str): 非流式返回字符串(回复内容),流式返回生成器(逐块内容). - - usage (dict): token使用统计(非流式返回 Usage 对象,流式返回 None). - - Raises: - ValueError: 如果 message 和 prompt 都为空或参数无效 - Exception: 如果 API 调用失败且达到最大重试次数 - """ - # 初始化 OpenAI 客户端 - client = OpenAI( - api_key=deepseek_api, - base_url=deepseek_url, - ) - - # 参数验证 - if messages is None: - if not prompt: - raise ValueError("必须提供 message 或 prompt 参数") - messages = [ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": prompt} - ] - elif not isinstance(messages, list) or not messages: - raise ValueError("message 参数必须是非空列表") - - # 模型验证 - models = ["deepseek-chat", "deepseek-reasoner"] - if model not in models: - raise ValueError(f"无效的模型名称: {model},可用模型: {models}") - - # 调用 API - for attempt in range(max_retries): - try: - response = client.chat.completions.create( - model=model, - messages=messages, - temperature=temperature, - max_tokens=max_tokens, - stream=stream - ) - - # 流式响应 - if stream: - def stream_generator(): - usage = None - for chunk in response: - if chunk.choices[0].delta.content is not None: - yield chunk.choices[0].delta.content - return usage - return stream_generator(), None - else: - # 非流式响应 - content = response.choices[0].message.content - return content, response.usage - - except Exception as e: - if hasattr(e, 'status_code') and e.status_code == 429: # 限流 - logger.warning(f"请求过于频繁,正在重试... (尝试 {attempt + 1}/{max_retries})") - time.sleep(2 ** attempt) # 指数退避 - else: - logger.error(f"API 调用失败:{str(e)}") - raise - raise Exception("达到最大重试次数,API 调用失败") - -def generate_vue_code(prompt=None): - prompt = ( - "生成一个React组件代码,用于端午节活动海报,包含以下部分并指定排版位置:" - "1. 背景图层:div,占据整个组件区域。" - "2. 主体图层:div,位于顶部1/4处,居中,包含标题和副标题。" - "3. 活动亮点:div,位于底部1/4处,居中,使用网格布局展示三项活动(每项包含图标、标题和描述)。" - "4. 页脚:div,位于底部,居中,包含主办单位信息和logo图片。" - "组件尺寸为1080x1920px,布局使用absolute定位,仅关注排版位置,不包含任何样式描述(如颜色、字体、阴影、动画等)。" - "仅生成React代码本身,不包含说明性文字、注释或Markdown格式。" - ) - system_prompt = ( - "你是一个擅长前端开发的AI,专注于生成React.js代码。" - "生成的代码仅关注排版位置,使用absolute定位,不包含任何样式描述(如颜色、字体、阴影、动画等)。" - "确保代码符合React最佳实践,仅生成代码本身。" - ) - - - result,usage = call_deepseek(prompt=prompt, system_prompt=system_prompt,temperature=0.4) - # print(result) - # print(usage) - return result - -def save_code(code,file_path="../outputs/generated_code.jsx"): - os.makedirs(os.path.dirname(file_path), exist_ok=True) - with open(file_path, "w", encoding="utf-8") as f: - f.write(code) - -if __name__ == "__main__": - vue_code = generate_vue_code() - save_code(vue_code) - print("React组件代码已生成并保存到outputs/generated_code.jsx") \ No newline at end of file diff --git a/scripts/generate_text.py b/scripts/generate_text.py new file mode 100644 index 0000000..2f6be0b --- /dev/null +++ b/scripts/generate_text.py @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- +import os +from openai import OpenAI +from dotenv import load_dotenv +import json +import yaml + +# 加载 .env 文件 +load_dotenv() + +# 从环境变量中获取 API Key +MOONSHOT_API_KEY = os.getenv("MOONSHOT_API_KEY") + +# --- 可配置的常量 --- +DEFAULT_LOGO_TEXT = "" +AVAILABLE_FONTS = [] +NAMING_COLORS = {} +STYLE_RULES = {} +LOGO_RULES = {} + +def load_config_from_file(file_path): + """从 YAML 文件加载配置""" + global DEFAULT_LOGO_TEXT, AVAILABLE_FONTS, NAMING_COLORS, STYLE_RULES, LOGO_RULES + try: + with open(file_path, 'r', encoding='utf-8') as file: + config_data = yaml.safe_load(file) + + DEFAULT_LOGO_TEXT = config_data.get("default_logo_text", DEFAULT_LOGO_TEXT) + AVAILABLE_FONTS = config_data.get("available_fonts", []) + NAMING_COLORS = config_data.get("NAMING_COLORS", {}) + STYLE_RULES = config_data.get("STYLE_RULES", {}) + LOGO_RULES = config_data.get("LOGO_RULES", {}) + + if not AVAILABLE_FONTS: + print("警告:未能从 YAML 配置中加载可用字体列表,或列表为空。") + AVAILABLE_FONTS = [{"name": "SimHei", "displayName": "黑体 (简体)", "tags": ["通用"], "roles": ["title", "subtitle", "content"]}] + + print("YAML 配置文件加载成功。") + print(f"默认 Logo 文字: {DEFAULT_LOGO_TEXT if DEFAULT_LOGO_TEXT else '未设置'}") + except yaml.YAMLError as e: + print(f"解析 YAML 文件时发生错误: {e}") + AVAILABLE_FONTS = [{"name": "SimHei", "displayName": "黑体 (简体)", "tags": ["通用"], "roles": ["title", "subtitle", "content"]}] + print("已使用内部备用字体列表。") + except FileNotFoundError: + print(f"错误:未找到文件 {file_path}") + AVAILABLE_FONTS = [{"name": "SimHei", "displayName": "黑体 (简体)", "tags": ["通用"], "roles": ["title", "subtitle", "content"]}] + print("已使用内部备用字体列表。") + +if not MOONSHOT_API_KEY: + print("错误:未能从环境变量中获取 MOONSHOT_API_KEY,请检查 .env 文件。") + exit() + +# 初始化 OpenAI 客户端 +try: + client = OpenAI(api_key=MOONSHOT_API_KEY, base_url="https://api.moonshot.cn/v1") + print("Kimi客户端初始化成功。") +except Exception as e: + print(f"初始化OpenAI客户端失败: {e}") + exit() + +def extract_parameters_from_input(user_input): + """ + 使用模型从用户输入中提取主题、风格、元素、背景颜色和自定义Logo文字等信息。 + """ + extraction_prompt = f""" + 你是一个专业的自然语言理解助手,请从以下用户输入中提取海报设计的关键信息,并严格按照JSON格式返回: + 用户输入:{user_input} + 输出格式: + {{ + "poster_theme": "这里是海报的主题,例如:世界读书日、南开大学校庆", + "style_desc": "这里是海报的风格描述,例如:现代、简约、有文化气息", + "elements_to_include": ["这里是需要包含的元素,例如:南开大学教学楼", "书本", "阅读的人"], + "background_is_light": true, + "custom_logo_text": "这里是用户在输入中明确指定的、用作Logo的文字内容。例如,如果用户说“Logo文字用‘技术创新研讨会’”,则提取“技术创新研讨会”。如果用户没有明确指定Logo文字,则返回null或空字符串。" + }} + 注意:对于 "background_is_light",如果用户输入明确提及背景色深浅,则据此判断;若未提及或无法判断,默认为true (浅色)。 + 对于 "custom_logo_text",仅当用户明确指定logo文字时才提取,否则应为null或空字符串。 + """ + + try: + completion = client.chat.completions.create( + model="moonshot-v1-8k", + messages=[ + {"role": "system", "content": "你是一个专业的自然语言理解助手,专门负责从用户输入中提取海报设计的关键参数,并严格以JSON格式输出。"}, + {"role": "user", "content": extraction_prompt} + ], + temperature=0.2 + ) + response_content = completion.choices[0].message.content + print(f"--- 模型返回的参数提取结果 ---\n{response_content}\n--------------------------\n") + + json_str = response_content.strip() + if "```json" in json_str: + json_str = json_str.split("```json")[1].split("```")[0].strip() + elif json_str.startswith("```") and json_str.endswith("```"): + json_str = json_str[3:-3].strip() + + extracted_params = json.loads(json_str) + + # 提供默认值 + if "background_is_light" not in extracted_params: + extracted_params["background_is_light"] = True + if "custom_logo_text" not in extracted_params: + extracted_params["custom_logo_text"] = None + + return extracted_params + except Exception as e: + print(f"调用模型提取参数或解析JSON时发生错误: {e}") + return { + "poster_theme": "默认主题", + "style_desc": "通用风格", + "elements_to_include": [], + "background_is_light": True, + "custom_logo_text": None, + "error": f"参数提取失败: {e}" + } + +def generate_text_content_for_layers(poster_theme, style_desc, background_is_light, logo_text_to_display, elements_to_include=None): + """ + 调用 Kimi API 生成文案内容和设计建议(具体字体名称、颜色),包括指定的Logo文字。 + AI 将从 AVAILABLE_FONTS (来自YAML) 中选择字体。 + """ + global AVAILABLE_FONTS # 确保能访问到从YAML加载的字体列表 + + if elements_to_include is None: elements_to_include = [] + if not client: return {"error": "Kimi客户端未初始化."} + + background_color_desc = '浅色' if background_is_light else '深色' + + # 构建字体列表供AI参考 + font_list_for_prompt = "当前可用的字体列表如下 (请从中选择 'name' 作为 font_name 的值):\n" + if AVAILABLE_FONTS: + for font in AVAILABLE_FONTS: + font_list_for_prompt += ( + f"- 名称(name): '{font['name']}' (显示名: {font.get('displayName', font['name'])}) - " + f"风格标签: [{', '.join(font.get('tags', []))}] - " + f"建议角色: [{', '.join(font.get('roles', []))}]\n" + ) + else: + font_list_for_prompt += "- 名称(name): 'SimHei' (显示名: 黑体) - 风格标签: [通用] - 建议角色: [标题, 内容]\n" + font_list_for_prompt += "注意:可用字体列表似乎未正确加载,请基于通用字体(如SimHei)给出建议。\n" + + + # 提取背景颜色描述到变量中 + background_color_desc = '浅色' if background_is_light else '深色' + + prompt_parts = [ + f"你是一个专业的海报文案及设计元素建议助手,请为关于“{poster_theme}”的主题海报创作宣传文案和设计建议。", + f"海报整体风格要求:{style_desc}。", + f"设计背景是{background_color_desc}。", + f"Logo文字已确定为:“{logo_text_to_display}”。", + "--------------------------------------------------", + "可用字体参考(请为下面的 'font_name' 字段从以下列表的 '名称(name)' 中选择一个最合适的):", + font_list_for_prompt, # 注入格式化后的字体列表 + "--------------------------------------------------", + "请严格按照以下JSON格式返回结果,仅包含针对指定图层的文本内容、从上述列表中选择的具体字体名称和颜色。所有建议需与海报主题、风格及背景色协调:", + "{", + f' "layer5_logo_content": {{ "text": "{logo_text_to_display}", "color": "这里是为Logo文字“{logo_text_to_display}”在背景色 ({background_color_desc}) 上推荐的颜色。例如:浅色背景可使用主题色如南开紫(#7E0C6E)或黑色(#000000),深色背景可使用白色(#FFFFFF)。请提供符合要求的具体十六进制颜色值。" }},', + f' "layer6_title_content": {{ "content": "这里是为“{poster_theme}”生成的主标题。如果用户输入中明确指定了主题(例如:世界读书日、端午、秋分),则标题必须直接使用该主题,且长度严格控制在2到8个汉字之间。", "font_name": "从上面提供的可用字体列表中为标题选择的具体字体\'名称(name)\' (例如: SimHei, FZLanTingHei-ExtraBold-GB)。", "color": "这里是建议的标题文字颜色,需确保在背景上高对比度且与整体风格和谐,可以根据背景选择白色(#FFFFFF)、米色(#f4efd9)、黑色(#000000)等。请提供具体十六进制颜色值。" }},', + f''' "layer7_subtitle_content": {{ "content": "这里是为“{poster_theme}”生成的副标题或说明文案。请根据主题选择以下一种形式并创作,确保内容富有文化内涵或情感共鸣,避免空洞口号:\n 1. 点题短语或对偶句(总计10-25字)。\n 2. 精美描述性或介绍性文字(一段话,20-35字)。\n 3. 若主题适合诗歌,可为原创短诗(例如绝句的前两句或类似形式,总计10-20字)。", "font_name": "从上面提供的可用字体列表中为副标题选择的具体字体\'名称(name)\',应与主标题协调。", "color": "这里是建议的副标题文字颜色,确保清晰可读,并与整体色调和谐,避免过于鲜艳的颜色。请提供具体十六进制颜色值。" }}''', + "}", + "重要提示:", + "1. 为 'content' 字段生成的文本必须是直接的文字内容,不应包含诸如“这里是...”之类的描述性前缀。", + "2. 'font_name' 字段的值必须是从上面提供的可用字体列表中的一个确切的 '名称(name)'。", + "3. 'color' 字段应提供明确的十六进制颜色代码(例如“#FFFFFF”)。", + "4. 确保最终输出是完整且严格符合上述格式的JSON对象,不要在JSON内容之外添加任何解释、注释、Markdown标记(如 ```json ```)或其他任何非JSON字符。" + ] + user_prompt = "\n".join(prompt_parts) + system_prompt_content = "你是一个专业的海报文案及设计元素建议助手。请严格按照用户要求的JSON格式输出,确保内容符合设计原则、主题文化内涵,并直接输出JSON内容,不要包含任何修饰性或解释性的文字,例如 '好的,这是您要求的JSON:' 或 '```json' 等标记。" + + try: + completion = client.chat.completions.create( + model="moonshot-v1-8k", + messages=[ + {"role": "system", "content": system_prompt_content}, + {"role": "user", "content": user_prompt} + ], + temperature=0.4 + ) + response_content = completion.choices[0].message.content + + json_str = response_content.strip() + if "```json" in json_str: + json_str = json_str.split("```json")[1].split("```")[0].strip() + elif json_str.startswith("```") and json_str.endswith("```"): + json_str = json_str[3:-3].strip() + + parsed_json = json.loads(json_str) + return parsed_json + except Exception as e: + print(f"调用Kimi或解析JSON时发生错误: {e}\n原始回复内容: {response_content if 'response_content' in locals() else 'N/A'}") + return {"error": f"LLM调用或解析失败: {e}"} + +def get_poster_content_suggestions(user_input_string: str): + """ + 主接口函数:接收用户输入,提取参数,确定Logo文字,并生成海报图层内容建议。 + 返回包含建议内容的字典,或包含错误信息的字典。 + """ + print(f"--- 开始处理用户输入: {user_input_string} ---") + # 1. 提取参数 + extracted_params = extract_parameters_from_input(user_input_string) + + if "error" in extracted_params: + print(f"参数提取过程中发生错误: {extracted_params['error']}") + return extracted_params # 返回包含错误信息的字典 + + poster_theme = extracted_params.get("poster_theme", "未知主题") + style_desc = extracted_params.get("style_desc", "默认风格") + elements_to_include = extracted_params.get("elements_to_include", []) + background_is_light = extracted_params.get("background_is_light", True) + custom_logo_text = extracted_params.get("custom_logo_text") + + print(f"\n--- 提取到的参数用于生成文案 ---") + print(f"主题: {poster_theme}") + print(f"风格: {style_desc}") + print(f"背景是否浅色: {background_is_light}") + print(f"用户指定Logo文字: {custom_logo_text if custom_logo_text else '未指定'}") + + # 2. Logo文字选择逻辑 + final_logo_text = DEFAULT_LOGO_TEXT # 默认为 "" + if custom_logo_text and custom_logo_text.strip(): # 如果用户指定了且不为空 + final_logo_text = custom_logo_text.strip() + print(f"--- 使用用户自定义Logo文字: “{final_logo_text}” ---") + else: + print(f"--- 使用默认Logo文字: “{final_logo_text}” ---") + print("--------------------------------\n") + + # 3. 调用生成函数 + generated_content = generate_text_content_for_layers( + poster_theme, + style_desc, + background_is_light, + final_logo_text, # 传递最终确定的Logo文字 + elements_to_include + ) + + if "error" in generated_content: + print(f"内容生成过程中发生错误: {generated_content['error']}") + else: + print("\n--- 成功生成的文案及设计建议 ---") + # print(json.dumps(generated_content, indent=2, ensure_ascii=False)) + + return generated_content + + +if __name__ == "__main__": + # 用户自由输入示例 + # user_input_example1 = "帮我设计一个关于'南开百年校庆'的海报,风格要喜庆、现代,Logo文字用'南开大学百年华诞',背景用浅红色。" + # user_input_example2 = "我要一个端午节的海报,中国风,深色背景。" + # user_input_example3 = "世界读书日海报,简约风格,元素包含打开的书和思考的人。" + + load_config_from_file("./configs/font.yaml") + + user_input = input("请输入您的海报需求:") + + # 调用主接口函数 + suggestions = get_poster_content_suggestions(user_input) + + # 打印结果 + if suggestions: + # print("\n--- 最终输出给调用者的内容 ---") + # print(json.dumps(suggestions, indent=2, ensure_ascii=False)) + output_folder = "outputs" + output_file = os.path.join(output_folder, "poster_content.json") + try: + with open(output_file, "w", encoding="utf-8") as file: + json.dump(suggestions, file, indent=2, ensure_ascii=False) + print(f"\n--- 内容已成功保存到文件: {output_file} ---") + except Exception as e: + print(f"\n--- 保存到文件时发生错误: {e} ---") + else: + print("未生成任何内容,无法保存到文件。") \ No newline at end of file diff --git a/serve.py b/serve.py deleted file mode 100644 index 97e56cf..0000000 --- a/serve.py +++ /dev/null @@ -1,73 +0,0 @@ -# server.py - -from flask import Flask, request, jsonify -from flask_cors import CORS -import requests # 用于发起HTTP请求(比如调用外部接口) -import json - -app = Flask(__name__) -CORS(app) # 如果前后端分离需要跨域,启用CORS - -@app.route('/generate', methods=['POST']) -def generate(): - try: - # 1. 从请求体中获取参数 - data = request.json - prompt = data.get('prompt', '') - width = data.get('width', 512) - height = data.get('height', 512) - - print(f'收到参数: prompt={prompt}, width={width}, height={height}') - - # 2. 调用大模型API或其他逻辑,生成图片 - - images = generate_images_coze(prompt, width, height) - - # 返回 JSON 格式数据给前端 - return jsonify({'images': images}) - - except Exception as e: - print('图片生成出错:', e) - return jsonify({'error': '图片生成出错'}), 500 - - -def generate_images_coze(prompt, width, height): - print(f'正在根据 "{prompt}" 生成内容 (宽{width},高{height})...') - - url = 'https://api.coze.cn/v3/chat' - headers = { - 'Authorization': 'Bearer pat_7G46ritHNFFvDnbeIIzXWaoPabf8ocbHsGfnAl20te1dsdHX2aTiRKR0XTLbauHW', - 'Content-Type': 'application/json' - } - - data = { - "bot_id": "7486021323127980071", - "user_id": "123", - "stream": True, - "auto_save_history": True, - "additional_messages": [ - { - "role": "user", - "content": prompt, - "content_type": "text" - } - ] - } - - try: - resp = requests.post(url, headers=headers, data=json.dumps(data)) - resp.raise_for_status() # 如果响应非200,会抛异常 - - result_text = resp.text - print('Coze API 返回结果:', result_text) - - # 假设并没有真正返回图片链接,就先返回一个测试 - return [f"大模型返回的内容:{result_text}"] - except Exception as e: - print('调用 Coze API 失败:', e) - # 出错的话就返回空列表 - return [] - - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=9000, debug=False)