inksiyu commited on
Commit
0756e48
1 Parent(s): 6445a4d

Upload 5 files

Browse files
Files changed (5) hide show
  1. app.py +162 -0
  2. requirements.txt +0 -0
  3. static/css/style.css +167 -0
  4. static/js/script.js +135 -0
  5. templates/index.html +69 -0
app.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, Response
2
+ import requests
3
+ import json
4
+ import logging
5
+ import re
6
+
7
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', encoding='utf-8')
8
+ logger = logging.getLogger(__name__)
9
+ app = Flask(__name__)
10
+
11
+ def generate_content(messages, model, api_key, url):
12
+ payload = json.dumps({
13
+ "model": model,
14
+ "messages": messages,
15
+ "stream": True
16
+ })
17
+ headers = {
18
+ 'Accept': 'application/json',
19
+ 'Authorization': api_key,
20
+ 'Content-Type': 'application/json'
21
+ }
22
+
23
+ decoded_payload = json.loads(payload)
24
+ logger.info(f"Request payload: {json.dumps(decoded_payload, ensure_ascii=False)}")
25
+
26
+ response = requests.post(url, headers=headers, data=payload, stream=True)
27
+
28
+ content = ""
29
+ for line in response.iter_lines():
30
+ if line:
31
+ decoded_line = line.decode('utf-8')
32
+ if "data: " in decoded_line:
33
+ data_str = decoded_line[6:]
34
+ if data_str.strip() == "[DONE]":
35
+ break
36
+ try:
37
+ data = json.loads(data_str)
38
+ if "choices" in data:
39
+ delta = data["choices"][0]["delta"]
40
+ if "content" in delta:
41
+ content_str = delta["content"]
42
+ content += content_str
43
+ yield content_str
44
+ except json.JSONDecodeError as e:
45
+ logger.error(f"JSON decode error: {e}")
46
+ elif "error" in decoded_line:
47
+ error_data = json.loads(decoded_line)
48
+ logger.error(f"Error: {error_data}")
49
+ break
50
+
51
+ def write_to_file(file_path, outline, generated_content):
52
+ with open(file_path, 'w', encoding='utf-8') as file:
53
+ for part, content in zip(outline, generated_content):
54
+ file.write(part + "\n")
55
+ if content:
56
+ file.write(content.strip() + "\n\n")
57
+
58
+ @app.route('/')
59
+ def index():
60
+ return render_template('index.html')
61
+
62
+ @app.route('/generate_outline', methods=['POST'])
63
+ def generate_outline():
64
+ data = request.get_json()
65
+ outline_model = data['outline_model']
66
+ url = data['proxy_url'] or "https://api.openai.com/v1/chat/completions"
67
+ api_key = data['api_key']
68
+ title = data['title']
69
+ doc_type = data['doc_type']
70
+ notice = data['notice']
71
+
72
+ system_prompt = ""
73
+ doc_type_name = ""
74
+ if doc_type == "1":
75
+ system_prompt = "You are ChatGPT, a large language model by OpenAI, and you are good at writing academic papers."
76
+ doc_type_name = "论文"
77
+ elif doc_type == "2":
78
+ system_prompt = "You are ChatGPT, a large language model by OpenAI, and you are good at writing reports."
79
+ doc_type_name = "报告"
80
+ elif doc_type == "3":
81
+ system_prompt = "You are ChatGPT, a large language model by OpenAI. From now on, you need to play the role of a writer. You are good at writing articles."
82
+ doc_type_name = "文章"
83
+
84
+ outline_prompt = f'我想写一个题目是"{title}"的{doc_type_name},下面是具体要求:{notice}\n请你帮我列出一个详细具体的大纲,大纲需要列出各个标题,只能用Markdown中的#和##来区分标题的层级(例如#大标题1\n##小标题1\n##小标题2\n#大标题2\n##小标题3\n##小标题4\n##小标题5\n以此类推,标题数量不固定\n注意:有且仅有大纲中绝对禁止出现"###"的组合,大纲中只能通过#和##进行标题层次的划分,之后的输出待定)。你的输出仅可包含示例中的内容,无需任何其他的解释说明'
85
+ retry_prompt = '前文已经要求过你不能输出###了,你误解了我的意思,请你直接重新生成新的outline,直接输出outline即可,无需道歉和其他任何解释'
86
+
87
+ messages = [
88
+ {"role": "system", "content": system_prompt},
89
+ {"role": "user", "content": outline_prompt}
90
+ ]
91
+ outline = "".join(list(generate_content(messages, outline_model, api_key, url)))
92
+
93
+ if re.search(r'###', outline):
94
+ temp_outline = outline
95
+ messages = [
96
+ {"role": "system", "content": system_prompt},
97
+ {"role": "user", "content": outline_prompt},
98
+ {"role": "assistant", "content": temp_outline},
99
+ {"role": "user", "content": retry_prompt}
100
+ ]
101
+ outline = "".join(list(generate_content(messages, outline_model, api_key, url)))
102
+
103
+ return jsonify({"outline": outline})
104
+
105
+ @app.route('/generate_content', methods=['POST'])
106
+ def generate_article_content():
107
+ data = request.get_json()
108
+ expand_outline = data['expand_outline']
109
+ outline = data['outline']
110
+ title = data['title']
111
+ content_model = data['content_model']
112
+ api_key = data['api_key']
113
+ url = data['proxy_url'] or "https://api.openai.com/v1/chat/completions"
114
+ doc_type = data['doc_type']
115
+
116
+ system_prompt = ""
117
+ if doc_type == "1":
118
+ system_prompt = "You are ChatGPT, a large language model by OpenAI, and you are good at writing academic papers."
119
+ elif doc_type == "2":
120
+ system_prompt = "You are ChatGPT, a large language model by OpenAI, and you are good at writing reports."
121
+ elif doc_type == "3":
122
+ system_prompt = "You are ChatGPT, a large language model by OpenAI. From now on, you need to play the role of a writer. You are good at writing articles."
123
+
124
+ outline_parts = [part for part in outline.split("\n") if part.strip()]
125
+ messages = [
126
+ {"role": "system", "content": system_prompt},
127
+ {"role": "assistant", "content": outline}
128
+ ]
129
+
130
+ def generate():
131
+ for part in outline_parts:
132
+ if part.startswith("##"):
133
+ if expand_outline.lower() == 'n':
134
+ article_prompt = f'参考之前的整体大纲,以及已经生成的内容,为"{part}"部分扩写内容。在扩写中,严禁出现下一级小标题,要求语言通俗易懂,不要过于死板,善于使用各种修辞手法。在每次输出的开头注明小标题是哪个(要用##放到小标题前面),结尾必须换2行'
135
+ else:
136
+ article_prompt = f'参考之前的整体大纲,以及已经生成的内容,为"{part}"部分扩写内容。你需要在##小标题的基础上再扩充###小小标题,要求语言通俗易懂,不要过于死板,善于使用各种修辞手法。在每次输出的开头注明小标题是哪个(要用##放到小标题前面),结尾必须换2行'
137
+
138
+ messages.append({"role": "user", "content": article_prompt})
139
+ article_content = ""
140
+ for chunk in generate_content(messages, content_model, api_key, url):
141
+ article_content += chunk
142
+ yield chunk
143
+ messages.append({"role": "assistant", "content": article_content})
144
+ else:
145
+ yield "\n\n"
146
+
147
+ return Response(generate(), mimetype='text/plain')
148
+
149
+ @app.route('/write_to_file', methods=['POST'])
150
+ def write_to_file():
151
+ data = request.get_json()
152
+ title = data['title']
153
+ content = data['content']
154
+
155
+ file_path = f"{title}.txt"
156
+ with open(file_path, 'w', encoding='utf-8') as file:
157
+ file.write(content)
158
+
159
+ return jsonify({"message": "Content written to file successfully."})
160
+
161
+ if __name__ == "__main__":
162
+ app.run(debug=True)
requirements.txt ADDED
File without changes
static/css/style.css ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
3
+ background-color: #f6f6f6;
4
+ background-image: linear-gradient(135deg, #f6f6f6 0%, #e9e9e9 100%);
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ .container {
10
+ max-width: 960px;
11
+ margin: 40px auto;
12
+ padding: 40px;
13
+ background-color: #fff;
14
+ border-radius: 20px;
15
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08);
16
+ }
17
+
18
+ h1 {
19
+ text-align: center;
20
+ color: #333;
21
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
22
+ font-size: 36px;
23
+ margin-bottom: 30px;
24
+ }
25
+
26
+ form {
27
+ margin-bottom: 30px;
28
+ }
29
+
30
+ .form-group {
31
+ margin-bottom: 25px;
32
+ }
33
+
34
+ label {
35
+ display: block;
36
+ font-weight: 600;
37
+ margin-bottom: 10px;
38
+ color: #555;
39
+ font-size: 18px;
40
+ }
41
+
42
+ input[type="text"],
43
+ select,
44
+ textarea {
45
+ width: 100%;
46
+ padding: 12px;
47
+ border: 1px solid #ddd;
48
+ border-radius: 8px;
49
+ font-size: 16px;
50
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
51
+ }
52
+
53
+ input[type="text"]:focus,
54
+ select:focus,
55
+ textarea:focus {
56
+ outline: none;
57
+ border-color: #7f7cff;
58
+ box-shadow: 0 0 5px rgba(127, 124, 255, 0.5);
59
+ }
60
+
61
+ button {
62
+ padding: 12px 24px;
63
+ background-color: #7f7cff;
64
+ color: #fff;
65
+ border: none;
66
+ border-radius: 8px;
67
+ font-size: 18px;
68
+ cursor: pointer;
69
+ transition: background-color 0.3s ease, transform 0.3s ease;
70
+ }
71
+
72
+ button:hover {
73
+ background-color: #6c63ff;
74
+ transform: translateY(-2px);
75
+ }
76
+
77
+ button:active {
78
+ transform: translateY(0);
79
+ }
80
+
81
+ #outline-container,
82
+ #content-container {
83
+ margin-top: 30px;
84
+ padding: 30px;
85
+ background-color: #f9f9f9;
86
+ border-radius: 10px;
87
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
88
+ }
89
+
90
+ h2 {
91
+ color: #333;
92
+ margin-bottom: 15px;
93
+ font-size: 24px;
94
+ }
95
+
96
+ #outline,
97
+ #content {
98
+ width: 100%;
99
+ padding: 15px;
100
+ border: 1px solid #ddd;
101
+ border-radius: 8px;
102
+ font-size: 16px;
103
+ min-height: 250px;
104
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
105
+ }
106
+
107
+ #outline:focus,
108
+ #content:focus {
109
+ outline: none;
110
+ border-color: #7f7cff;
111
+ box-shadow: 0 0 5px rgba(127, 124, 255, 0.5);
112
+ }
113
+
114
+ /* 添加更多样式 */
115
+
116
+ .container::before {
117
+ content: '';
118
+ position: absolute;
119
+ top: -20px;
120
+ left: -20px;
121
+ right: -20px;
122
+ bottom: -20px;
123
+ background: linear-gradient(135deg, #7f7cff, #6c63ff);
124
+ filter: blur(40px);
125
+ z-index: -1;
126
+ opacity: 0.3;
127
+ }
128
+
129
+ button {
130
+ position: relative;
131
+ overflow: hidden;
132
+ }
133
+
134
+ button::after {
135
+ content: '';
136
+ position: absolute;
137
+ width: 100%;
138
+ height: 100%;
139
+ top: 0;
140
+ left: 0;
141
+ background-color: rgba(255, 255, 255, 0.2);
142
+ transform: translateX(-100%);
143
+ transition: transform 0.3s ease;
144
+ }
145
+
146
+ button:hover::after {
147
+ transform: translateX(0);
148
+ }
149
+
150
+ #outline-container,
151
+ #content-container {
152
+ position: relative;
153
+ }
154
+
155
+ #outline-container::before,
156
+ #content-container::before {
157
+ content: '';
158
+ position: absolute;
159
+ top: -10px;
160
+ left: -10px;
161
+ right: -10px;
162
+ bottom: -10px;
163
+ background: linear-gradient(135deg, #7f7cff, #6c63ff);
164
+ filter: blur(20px);
165
+ z-index: -1;
166
+ opacity: 0.2;
167
+ }
static/js/script.js ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ const form = document.getElementById('article-form');
3
+ const outlineContainer = document.getElementById('outline-container');
4
+ const outlineElement = document.getElementById('outline');
5
+ const expandOutlineElement = document.getElementById('expand-outline');
6
+ const generateContentButton = document.getElementById('generate-content');
7
+ const contentContainer = document.getElementById('content-container');
8
+ const contentElement = document.getElementById('content');
9
+ const docType = document.getElementById('doc-type').value; // 获取文档类型
10
+
11
+ form.addEventListener('submit', function(event) {
12
+ event.preventDefault();
13
+
14
+ const formData = new FormData(form);
15
+ const requestData = {
16
+ outline_model: formData.get('outline_model'),
17
+ content_model: formData.get('content_model'),
18
+ proxy_url: formData.get('proxy_url'),
19
+ api_key: formData.get('api_key'),
20
+ title: formData.get('title'),
21
+ doc_type: formData.get('doc_type'),
22
+ notice: formData.get('notice'),
23
+ //doc_type: formData.get('doc_type') || docType
24
+ };
25
+
26
+ outlineElement.value = 'Generating outline...';
27
+ outlineContainer.style.display = 'block';
28
+
29
+ fetch('/generate_outline', {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json'
33
+ },
34
+ body: JSON.stringify(requestData)
35
+ })
36
+ .then(response => response.json())
37
+ .then(data => {
38
+ outlineElement.value = data.outline;
39
+ })
40
+ .catch(error => {
41
+ console.error('Error:', error);
42
+ outlineElement.value = 'Failed to generate outline. Please check the console for more details.';
43
+ });
44
+ });
45
+
46
+ generateContentButton.addEventListener('click', function() {
47
+ const expandOutline = expandOutlineElement.value;
48
+ const outline = outlineElement.value;
49
+ const title = document.getElementById('title').value;
50
+ const contentModel = document.getElementById('content-model').value;
51
+ const apiKey = document.getElementById('api-key').value;
52
+ const proxyUrl = document.getElementById('proxy-url').value;
53
+
54
+ const requestData = {
55
+ expand_outline: expandOutline,
56
+ outline: outline,
57
+ title: title,
58
+ content_model: contentModel,
59
+ api_key: apiKey,
60
+ proxy_url: proxyUrl,
61
+ doc_type: docType
62
+ };
63
+
64
+ contentElement.innerText = 'Generating content...';
65
+ contentContainer.style.display = 'block';
66
+
67
+ fetch('/generate_content', {
68
+ method: 'POST',
69
+ headers: {
70
+ 'Content-Type': 'application/json'
71
+ },
72
+ body: JSON.stringify(requestData)
73
+ })
74
+ .then(response => {
75
+ if (!response.ok) {
76
+ throw new Error('Network response was not ok');
77
+ }
78
+ return response.body;
79
+ })
80
+ .then(body => {
81
+ const reader = body.getReader();
82
+ const decoder = new TextDecoder('utf-8');
83
+ return new ReadableStream({
84
+ start(controller) {
85
+ function push() {
86
+ reader.read().then(({ done, value }) => {
87
+ if (done) {
88
+ controller.close();
89
+ return;
90
+ }
91
+ controller.enqueue(decoder.decode(value));
92
+ push();
93
+ });
94
+ }
95
+ push();
96
+ }
97
+ });
98
+ })
99
+ .then(stream => {
100
+ const reader = stream.getReader();
101
+ let fullContent = '';
102
+ function read() {
103
+ reader.read().then(({ done, value }) => {
104
+ if (done) {
105
+ // 内容生成完成后,将其发送到后端进行文件写入
106
+ const title = document.getElementById('title').value;
107
+ fetch('/write_to_file', {
108
+ method: 'POST',
109
+ headers: {
110
+ 'Content-Type': 'application/json'
111
+ },
112
+ body: JSON.stringify({ title: title, content: fullContent })
113
+ })
114
+ .then(response => response.json())
115
+ .then(data => {
116
+ console.log(data.message);
117
+ })
118
+ .catch(error => {
119
+ console.error('Error:', error);
120
+ });
121
+ return;
122
+ }
123
+ fullContent += value;
124
+ contentElement.innerText += value;
125
+ read();
126
+ });
127
+ }
128
+ read();
129
+ })
130
+ .catch(error => {
131
+ console.error('Error:', error);
132
+ contentElement.innerText = 'Failed to generate content. Please check the console for more details.';
133
+ });
134
+ });
135
+ });
templates/index.html ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Article Generator</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <h1>Article Generator</h1>
12
+ <form id="article-form">
13
+ <div class="form-group">
14
+ <label for="outline-model">Outline Model:</label>
15
+ <input type="text" id="outline-model" name="outline_model" required>
16
+ </div>
17
+ <div class="form-group">
18
+ <label for="content-model">Content Generation Model:</label>
19
+ <input type="text" id="content-model" name="content_model" required>
20
+ </div>
21
+ <div class="form-group">
22
+ <label for="proxy-url">Proxy URL:</label>
23
+ <input type="text" id="proxy-url" name="proxy_url" required>
24
+ </div>
25
+ <div class="form-group">
26
+ <label for="api-key">API Key:</label>
27
+ <input type="text" id="api-key" name="api_key" required>
28
+ </div>
29
+ <div class="form-group">
30
+ <label for="title">Title:</label>
31
+ <input type="text" id="title" name="title" required>
32
+ </div>
33
+ <div class="form-group">
34
+ <label for="doc-type">Document Type:</label>
35
+ <select id="doc-type" name="doc_type" required>
36
+ <option value="1">论文</option>
37
+ <option value="2">报告</option>
38
+ <option value="3">文章</option>
39
+ </select>
40
+ </div>
41
+ <div class="form-group">
42
+ <label for="notice">Notice for AI:</label>
43
+ <textarea id="notice" name="notice" rows="4" required></textarea>
44
+ </div>
45
+ <button type="submit">Generate Outline</button>
46
+ </form>
47
+
48
+ <div id="outline-container" style="display: none;">
49
+ <h2>Generated Outline:</h2>
50
+ <textarea id="outline" rows="10"></textarea>
51
+ <div class="form-group">
52
+ <label for="expand-outline">Do you want to expand the outline further?</label>
53
+ <select id="expand-outline" name="expand_outline" required>
54
+ <option value="y">Yes</option>
55
+ <option value="n">No</option>
56
+ </select>
57
+ </div>
58
+ <button id="generate-content">Generate Content</button>
59
+ </div>
60
+
61
+ <div id="content-container" style="display: none;">
62
+ <h2>Generated Content:</h2>
63
+ <div id="content"></div>
64
+ </div>
65
+ </div>
66
+
67
+ <script src="{{ url_for('static', filename='js/script.js') }}"></script>
68
+ </body>
69
+ </html>