diff --git a/.gitignore b/.gitignore index f7275bb..ca5e3f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ venv/ +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index aeeec4c..2b2a346 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ # ai_service + +## LLM 调用部分 + +### 功能组件 +使用 DeepSeek API 提供的 LLM(大型语言模型)功能,用于生成 React 组件代码,主要服务于端午节活动海报的排版设计。当前实现的功能包括: +- **代码生成**:根据提示(prompt)生成 React 组件代码,专注于排版位置(不包含样式描述)。 +- **分层排版**:生成包含背景图层、主体图层、活动亮点和页脚的 React 组件 + +- **文件保存**:将生成的 React 代码保存到指定路径。 + +### 如何调用 +1. **环境准备**: + - 确保已安装 Python 环境和必要依赖(`openai`、`python-dotenv`等)。 + - 在项目根目录的 `.env` 文件中配置 `DEEPSEEK_API_KEY`。 + +2. **运行脚本**: + - 直接运行 `generate_layout.py` 脚本: + ```bash + python generate_layout.py + ``` + - 该脚本会自动调用 DeepSeek API 生成 React 组件代码,并将其保存到指定路径。 + 默认为``output/generated_code.jsx``,可以根据需要修改。 \ No newline at end of file diff --git a/outputs/generated_code.jsx b/outputs/generated_code.jsx new file mode 100644 index 0000000..6f04287 --- /dev/null +++ b/outputs/generated_code.jsx @@ -0,0 +1,47 @@ +```jsx +import React from 'react'; + +const DragonBoatFestivalPoster = () => { + return ( +
+ {/* 背景图层 */} +
+ + {/* 主体图层 */} +
+

+

+
+ + {/* 活动亮点 */} +
+
+
+
+

+

+
+
+
+

+

+
+
+
+

+

+
+
+
+ + {/* 页脚 */} +
+

+
+
+
+ ); +}; + +export default DragonBoatFestivalPoster; +``` \ No newline at end of file diff --git a/scripts/generate_layout.py b/scripts/generate_layout.py new file mode 100644 index 0000000..56906c4 --- /dev/null +++ b/scripts/generate_layout.py @@ -0,0 +1,140 @@ +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