Spaces:
Sleeping
Sleeping
lele
commited on
Commit
·
8445a21
1
Parent(s):
c7653ab
modified: .gitignore
Browse filesmodified: README.md
modified: app.py
modified: prompt.txt
renamed: ai_transform.py -> src/ai_transform.py
renamed: utils.py -> src/utils.py
new file: templates/index.html
- .gitignore +1 -0
- README.md +10 -0
- app.py +31 -31
- prompt.txt +1 -0
- ai_transform.py → src/ai_transform.py +33 -12
- utils.py → src/utils.py +12 -0
- templates/index.html +220 -0
.gitignore
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
secret.env
|
| 2 |
test*.py
|
|
|
|
| 3 |
notion_data.json
|
| 4 |
prompt_1.txt
|
| 5 |
prompt_2.txt
|
|
|
|
| 1 |
secret.env
|
| 2 |
test*.py
|
| 3 |
+
test/
|
| 4 |
notion_data.json
|
| 5 |
prompt_1.txt
|
| 6 |
prompt_2.txt
|
README.md
CHANGED
|
@@ -7,10 +7,16 @@ sdk: docker
|
|
| 7 |
pinned: false
|
| 8 |
license: apache-2.0
|
| 9 |
short_description: Push my data to web and transfer to Notion
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
# update
|
| 15 |
- gpt 3.5 to classify the project.
|
| 16 |
- generalize the model usage.
|
|
@@ -23,6 +29,10 @@ curl -X POST https://beanundlaugh-notion-pusher.hf.space/ \
|
|
| 23 |
|
| 24 |
content should be a string with form "content"
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
# test locally
|
| 27 |
## get_all function
|
| 28 |
|
|
|
|
| 7 |
pinned: false
|
| 8 |
license: apache-2.0
|
| 9 |
short_description: Push my data to web and transfer to Notion
|
| 10 |
+
version: 2.0.0
|
| 11 |
---
|
| 12 |
|
| 13 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 14 |
|
| 15 |
+
# update2
|
| 16 |
+
build a async frontend for visualization
|
| 17 |
+
set a debugging mode, which skips the entire Notion API interaction loop.(accept debug as a query)
|
| 18 |
+
reconstruct the modules.
|
| 19 |
+
|
| 20 |
# update
|
| 21 |
- gpt 3.5 to classify the project.
|
| 22 |
- generalize the model usage.
|
|
|
|
| 29 |
|
| 30 |
content should be a string with form "content"
|
| 31 |
|
| 32 |
+
or
|
| 33 |
+
|
| 34 |
+
visit the root and directly enter the content.
|
| 35 |
+
|
| 36 |
# test locally
|
| 37 |
## get_all function
|
| 38 |
|
app.py
CHANGED
|
@@ -1,34 +1,18 @@
|
|
| 1 |
-
from flask import Flask, request, jsonify
|
| 2 |
-
from ai_transform import gemini_get_answer, classify_task_with_ai
|
| 3 |
-
from utils import *
|
| 4 |
import requests, os, datetime
|
| 5 |
-
import time
|
| 6 |
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
app = Flask(__name__)
|
| 8 |
|
| 9 |
NOTION_TOKEN = os.environ.get("NOTION_TOKEN")
|
| 10 |
DB_ID = os.environ.get("DB_ID")
|
| 11 |
-
with open("prompt.txt", encoding="utf-8") as f:
|
| 12 |
-
system_content = f.read()
|
| 13 |
|
| 14 |
-
def time_cali():
|
| 15 |
-
# 拿到 UTC 时间戳
|
| 16 |
-
ts = time.time()
|
| 17 |
-
# 手动加 8 小时
|
| 18 |
-
beijing_ts = ts + 8*3600
|
| 19 |
-
# 再转回 struct_time
|
| 20 |
-
beijing_t = time.gmtime(beijing_ts)
|
| 21 |
-
# print(time.strftime('%Y-%m-%d', beijing_t))
|
| 22 |
-
return time.strftime('%Y-%m-%d', beijing_t)
|
| 23 |
-
def modified_with_ai(items):
|
| 24 |
-
# 在这里调用AI模型对item进行修改
|
| 25 |
-
# today_date = time.strftime("%Y-%m-%d", time.localtime())
|
| 26 |
-
today_date = time_cali()
|
| 27 |
-
system_instu = system_content.replace("today", today_date)
|
| 28 |
-
items = gemini_get_answer(items, system_instu)
|
| 29 |
-
print(f"--- AI modification complete ---")
|
| 30 |
-
# print(items)
|
| 31 |
-
return items
|
| 32 |
def get_property(it, task_name, url, api_key, model):
|
| 33 |
properties = {
|
| 34 |
"Task name": {
|
|
@@ -81,23 +65,38 @@ def init_items(items):
|
|
| 81 |
# items = ",".join(line.strip() for line in items.splitlines() if line.strip())
|
| 82 |
return items
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
@app.route("/", methods=["POST"])
|
| 85 |
def push():
|
| 86 |
# items = request.get_json(force=True)
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
items = init_items(items)
|
| 89 |
-
items= modified_with_ai(items)
|
| 90 |
# change to json format
|
| 91 |
items = json.loads(items)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
url = "https://api.notion.com/v1/pages"
|
| 93 |
headers = {
|
| 94 |
"Authorization": f"Bearer {NOTION_TOKEN}",
|
| 95 |
"Content-Type": "application/json",
|
| 96 |
"Notion-Version": "2022-06-28"
|
| 97 |
}
|
| 98 |
-
ai_implemented = "gpt"
|
| 99 |
-
if ai_implemented is not None:
|
| 100 |
-
ai = load_ai_config(ai_implemented)
|
| 101 |
# print("ai_config:", ai)
|
| 102 |
for it in items:
|
| 103 |
task_name = it.get("Task name", "Untitled Task")
|
|
@@ -108,7 +107,8 @@ def push():
|
|
| 108 |
else:
|
| 109 |
print(f"Successfully pushed item: {it.get('Task name', 'Untitled Task')}")
|
| 110 |
# print(r.status_code, r.text)
|
| 111 |
-
return jsonify({"ok": True, "inserted": len(items)})
|
|
|
|
| 112 |
|
| 113 |
@app.route("/get", methods=["GET"])
|
| 114 |
def get_all_data():
|
|
@@ -143,4 +143,4 @@ def get_all_data():
|
|
| 143 |
|
| 144 |
return jsonify({"ok": True, "data": all_pages, "count": len(all_pages)})
|
| 145 |
if __name__ == "__main__":
|
| 146 |
-
app.run(host="0.0.0.0", port=7860)
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify, render_template
|
| 2 |
+
from src.ai_transform import gemini_get_answer, classify_task_with_ai, modified_with_ai
|
| 3 |
+
from src.utils import *
|
| 4 |
import requests, os, datetime
|
|
|
|
| 5 |
import json
|
| 6 |
+
|
| 7 |
+
# for offline debugging
|
| 8 |
+
if os.path.exists("secret.env"):
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
+
load_dotenv("secret.env")
|
| 11 |
app = Flask(__name__)
|
| 12 |
|
| 13 |
NOTION_TOKEN = os.environ.get("NOTION_TOKEN")
|
| 14 |
DB_ID = os.environ.get("DB_ID")
|
|
|
|
|
|
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
def get_property(it, task_name, url, api_key, model):
|
| 17 |
properties = {
|
| 18 |
"Task name": {
|
|
|
|
| 65 |
# items = ",".join(line.strip() for line in items.splitlines() if line.strip())
|
| 66 |
return items
|
| 67 |
|
| 68 |
+
@app.route("/", methods=["GET"])
|
| 69 |
+
def index():
|
| 70 |
+
return render_template("index.html")
|
| 71 |
+
|
| 72 |
@app.route("/", methods=["POST"])
|
| 73 |
def push():
|
| 74 |
# items = request.get_json(force=True)
|
| 75 |
+
if request.content_type == 'text/plain':
|
| 76 |
+
items = request.get_data(as_text=True)
|
| 77 |
+
else: # application/json
|
| 78 |
+
items = request.get_json()['text']
|
| 79 |
+
# implement AI
|
| 80 |
+
ai_implemented = "gpt"
|
| 81 |
+
if ai_implemented is not None:
|
| 82 |
+
ai = load_ai_config(ai_implemented)
|
| 83 |
items = init_items(items)
|
| 84 |
+
items = modified_with_ai(items, url=ai["url"], api_key=ai["api_key"], model=ai["model"])
|
| 85 |
# change to json format
|
| 86 |
items = json.loads(items)
|
| 87 |
+
debug_mode = request.args.get("debug") == "true"
|
| 88 |
+
print("Debug mode:", debug_mode)
|
| 89 |
+
if debug_mode:
|
| 90 |
+
return jsonify({"items": items, "debug": True, "ok": True})
|
| 91 |
+
|
| 92 |
+
# push to notion
|
| 93 |
+
|
| 94 |
url = "https://api.notion.com/v1/pages"
|
| 95 |
headers = {
|
| 96 |
"Authorization": f"Bearer {NOTION_TOKEN}",
|
| 97 |
"Content-Type": "application/json",
|
| 98 |
"Notion-Version": "2022-06-28"
|
| 99 |
}
|
|
|
|
|
|
|
|
|
|
| 100 |
# print("ai_config:", ai)
|
| 101 |
for it in items:
|
| 102 |
task_name = it.get("Task name", "Untitled Task")
|
|
|
|
| 107 |
else:
|
| 108 |
print(f"Successfully pushed item: {it.get('Task name', 'Untitled Task')}")
|
| 109 |
# print(r.status_code, r.text)
|
| 110 |
+
return jsonify({"ok": True, "inserted": len(items), "debug": False, "items": items})
|
| 111 |
+
|
| 112 |
|
| 113 |
@app.route("/get", methods=["GET"])
|
| 114 |
def get_all_data():
|
|
|
|
| 143 |
|
| 144 |
return jsonify({"ok": True, "data": all_pages, "count": len(all_pages)})
|
| 145 |
if __name__ == "__main__":
|
| 146 |
+
app.run(host="0.0.0.0", port=7860)# , debug=True
|
prompt.txt
CHANGED
|
@@ -29,6 +29,7 @@
|
|
| 29 |
* **傍晚/夜晚关键词**: "dinner", "supper", "evening", "night", "lecturing", "relaxing", "streaming" 等词语旁的数字,应优先推断为 **PM (12:00 - 23:00)**。
|
| 30 |
* **早晨/白天关键词**: "breakfast", "morning", "晨会", "standup", "meeting", "work" 等词语旁的数字,应优先推断为 **AM 或工作时间 (07:00 - 18:00)**。
|
| 31 |
* **无明确线索**: 如果没有任何上下文线索,根据你的世界知识做出最合理的判断(例如,一个在“2点”的“商务会议”极有可能是下午14:00,而不是凌晨02:00)。
|
|
|
|
| 32 |
* 可以推理device,如streaming为Phone,project working一般是电脑(涉及编程)
|
| 33 |
|
| 34 |
### 5. 示例 (必须严格模仿)
|
|
|
|
| 29 |
* **傍晚/夜晚关键词**: "dinner", "supper", "evening", "night", "lecturing", "relaxing", "streaming" 等词语旁的数字,应优先推断为 **PM (12:00 - 23:00)**。
|
| 30 |
* **早晨/白天关键词**: "breakfast", "morning", "晨会", "standup", "meeting", "work" 等词语旁的数字,应优先推断为 **AM 或工作时间 (07:00 - 18:00)**。
|
| 31 |
* **无明确线索**: 如果没有任何上下文线索,根据你的世界知识做出最合理的判断(例如,一个在“2点”的“商务会议”极有可能是下午14:00,而不是凌晨02:00)。
|
| 32 |
+
* 注意:不要返回{current_date}字符,要根据给定的日期替换。
|
| 33 |
* 可以推理device,如streaming为Phone,project working一般是电脑(涉及编程)
|
| 34 |
|
| 35 |
### 5. 示例 (必须严格模仿)
|
ai_transform.py → src/ai_transform.py
RENAMED
|
@@ -3,13 +3,28 @@ import json
|
|
| 3 |
import requests
|
| 4 |
import os
|
| 5 |
import google.generativeai as genai
|
| 6 |
-
from utils import *
|
| 7 |
-
# from dotenv import load_dotenv
|
| 8 |
-
# load_dotenv("secret.env")
|
| 9 |
ST_API_PASS = os.environ.get("ST_API_PASSWORD")
|
| 10 |
ST_api_key = f"Bearer {ST_API_PASS}"
|
| 11 |
ST_url = "https://spark-api-open.xf-yun.com/v1/chat/completions"
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
def classify_task_with_ai(task_info: str, url=ST_url, api_key=ST_api_key, model=None, categories_filepath: str = "related_tasks.txt", config = None) -> str:
|
| 14 |
"""
|
| 15 |
使用 ai 模型和 RAG 方式对任务名称进行分类。
|
|
@@ -25,7 +40,7 @@ def classify_task_with_ai(task_info: str, url=ST_url, api_key=ST_api_key, model=
|
|
| 25 |
api_key : str, optional
|
| 26 |
API 请求的密钥,默认为 ST_api_key。
|
| 27 |
model : str, optional
|
| 28 |
-
|
| 29 |
返回
|
| 30 |
----
|
| 31 |
str
|
|
@@ -79,6 +94,7 @@ def gemini_get_answer(user_input: str, system_prompt: str = "") -> str:
|
|
| 79 |
# 2. 创建模型实例
|
| 80 |
model = genai.GenerativeModel(
|
| 81 |
model_name="gemini-2.5-flash",
|
|
|
|
| 82 |
system_instruction=system_prompt or None # 空字符串时传 None,避免多余字段
|
| 83 |
)
|
| 84 |
|
|
@@ -94,10 +110,10 @@ def _init_model(url):
|
|
| 94 |
model = "lite"
|
| 95 |
# could be extended
|
| 96 |
# elif "gemini" in url:
|
| 97 |
-
# model = "gemini-2.5-
|
| 98 |
return model
|
| 99 |
|
| 100 |
-
def _get_answer(message, url=ST_url, api_key=ST_api_key, model=None):
|
| 101 |
"""
|
| 102 |
Accepts a single string `message` as the prompt and returns the model-generated response.
|
| 103 |
"""
|
|
@@ -115,14 +131,19 @@ def _get_answer(message, url=ST_url, api_key=ST_api_key, model=None):
|
|
| 115 |
if model is None:
|
| 116 |
model = _init_model(url)
|
| 117 |
# 这里的 message 应该是我们之前设计好的、包含示例的完整提示词
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
body = {
|
| 119 |
"model": model,
|
| 120 |
-
"messages":
|
| 121 |
-
{
|
| 122 |
-
"role": "user",
|
| 123 |
-
"content": message
|
| 124 |
-
},
|
| 125 |
-
],
|
| 126 |
"stream": True
|
| 127 |
}
|
| 128 |
|
|
|
|
| 3 |
import requests
|
| 4 |
import os
|
| 5 |
import google.generativeai as genai
|
| 6 |
+
from src.utils import *
|
|
|
|
|
|
|
| 7 |
ST_API_PASS = os.environ.get("ST_API_PASSWORD")
|
| 8 |
ST_api_key = f"Bearer {ST_API_PASS}"
|
| 9 |
ST_url = "https://spark-api-open.xf-yun.com/v1/chat/completions"
|
| 10 |
|
| 11 |
+
with open("prompt.txt", encoding="utf-8") as f:
|
| 12 |
+
system_content = f.read()
|
| 13 |
+
|
| 14 |
+
def modified_with_ai(items, url=ST_url, api_key=ST_api_key, model=None):
|
| 15 |
+
# 在这里调用AI模型对item进行修改
|
| 16 |
+
# today_date = time.strftime("%Y-%m-%d", time.localtime())
|
| 17 |
+
today_date = time_cali()
|
| 18 |
+
system_instu = system_content.replace("today", today_date)
|
| 19 |
+
if "gemini" in url:
|
| 20 |
+
items = gemini_get_answer(items, system_instu)
|
| 21 |
+
else:
|
| 22 |
+
items = _get_answer(items, url, api_key, model, system_instu)
|
| 23 |
+
items = items.replace("{current_date}", today_date)
|
| 24 |
+
print(f"--- AI modification complete ---")
|
| 25 |
+
# print(items)
|
| 26 |
+
return items
|
| 27 |
+
|
| 28 |
def classify_task_with_ai(task_info: str, url=ST_url, api_key=ST_api_key, model=None, categories_filepath: str = "related_tasks.txt", config = None) -> str:
|
| 29 |
"""
|
| 30 |
使用 ai 模型和 RAG 方式对任务名称进行分类。
|
|
|
|
| 40 |
api_key : str, optional
|
| 41 |
API 请求的密钥,默认为 ST_api_key。
|
| 42 |
model : str, optional
|
| 43 |
+
使用的模型-> 使用init_model根据url自动选择, 如:gpt-3.5-turbo, 默认为 None。
|
| 44 |
返回
|
| 45 |
----
|
| 46 |
str
|
|
|
|
| 94 |
# 2. 创建模型实例
|
| 95 |
model = genai.GenerativeModel(
|
| 96 |
model_name="gemini-2.5-flash",
|
| 97 |
+
# model_name = "gemini-2.0-flash-lite",
|
| 98 |
system_instruction=system_prompt or None # 空字符串时传 None,避免多余字段
|
| 99 |
)
|
| 100 |
|
|
|
|
| 110 |
model = "lite"
|
| 111 |
# could be extended
|
| 112 |
# elif "gemini" in url:
|
| 113 |
+
# model = "gemini-2.5-lite"
|
| 114 |
return model
|
| 115 |
|
| 116 |
+
def _get_answer(message, url=ST_url, api_key=ST_api_key, model=None, system_instr=None):
|
| 117 |
"""
|
| 118 |
Accepts a single string `message` as the prompt and returns the model-generated response.
|
| 119 |
"""
|
|
|
|
| 131 |
if model is None:
|
| 132 |
model = _init_model(url)
|
| 133 |
# 这里的 message 应该是我们之前设计好的、包含示例的完整提示词
|
| 134 |
+
message_list =[]
|
| 135 |
+
if system_instr is not None:
|
| 136 |
+
message_list.append({
|
| 137 |
+
"role": "system",
|
| 138 |
+
"content": system_instr
|
| 139 |
+
})
|
| 140 |
+
message_list.append({
|
| 141 |
+
"role": "user",
|
| 142 |
+
"content": message
|
| 143 |
+
})
|
| 144 |
body = {
|
| 145 |
"model": model,
|
| 146 |
+
"messages": message_list,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
"stream": True
|
| 148 |
}
|
| 149 |
|
utils.py → src/utils.py
RENAMED
|
@@ -1,4 +1,6 @@
|
|
| 1 |
import os
|
|
|
|
|
|
|
| 2 |
def load_task_mapping_from_txt(filepath: str = "task_mapping.txt") -> dict:
|
| 3 |
"""
|
| 4 |
从 txt 文件中读取 "类别名称: ID" 格式的数据,并返回一个字典。
|
|
@@ -53,6 +55,16 @@ def load_ai_config(name = "default"):
|
|
| 53 |
config["api_key"] = "Bearer " + os.environ.get(f"{API_MODEL}_KEY")
|
| 54 |
return config
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
if __name__ == "__main__":
|
| 57 |
mapping = load_task_mapping_from_txt("related_tasks.txt")
|
| 58 |
# print(mapping.keys())
|
|
|
|
| 1 |
import os
|
| 2 |
+
import time
|
| 3 |
+
|
| 4 |
def load_task_mapping_from_txt(filepath: str = "task_mapping.txt") -> dict:
|
| 5 |
"""
|
| 6 |
从 txt 文件中读取 "类别名称: ID" 格式的数据,并返回一个字典。
|
|
|
|
| 55 |
config["api_key"] = "Bearer " + os.environ.get(f"{API_MODEL}_KEY")
|
| 56 |
return config
|
| 57 |
|
| 58 |
+
def time_cali():
|
| 59 |
+
# 拿到 UTC 时间戳
|
| 60 |
+
ts = time.time()
|
| 61 |
+
# 手动加 8 小时
|
| 62 |
+
beijing_ts = ts + 8*3600
|
| 63 |
+
# 再转回 struct_time
|
| 64 |
+
beijing_t = time.gmtime(beijing_ts)
|
| 65 |
+
# print(time.strftime('%Y-%m-%d', beijing_t))
|
| 66 |
+
return time.strftime('%Y-%m-%d', beijing_t)
|
| 67 |
+
|
| 68 |
if __name__ == "__main__":
|
| 69 |
mapping = load_task_mapping_from_txt("related_tasks.txt")
|
| 70 |
# print(mapping.keys())
|
templates/index.html
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-CN">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title>Notion Task Pusher</title>
|
| 6 |
+
<style>
|
| 7 |
+
/* 黑色主题的样式 */
|
| 8 |
+
body {
|
| 9 |
+
font-family: sans-serif;
|
| 10 |
+
max-width: 80%;
|
| 11 |
+
margin: 40px auto;
|
| 12 |
+
padding: 20px;
|
| 13 |
+
border: 1px solid #444;
|
| 14 |
+
border-radius: 8px;
|
| 15 |
+
background-color: #1a1a1a; /* 深色背景 */
|
| 16 |
+
color: #f0f0f0; /* 浅色文本 */
|
| 17 |
+
}
|
| 18 |
+
h1 {
|
| 19 |
+
color: #00bcd4; /* 强调色,例如青色 */
|
| 20 |
+
}
|
| 21 |
+
p {
|
| 22 |
+
color: #ccc;
|
| 23 |
+
}
|
| 24 |
+
textarea {
|
| 25 |
+
width: 100%;
|
| 26 |
+
height: 150px;
|
| 27 |
+
padding: 10px;
|
| 28 |
+
box-sizing: border-box;
|
| 29 |
+
margin-bottom: 10px;
|
| 30 |
+
border: 1px solid #333;
|
| 31 |
+
background-color: #2c2c2c; /* 输入框背景 */
|
| 32 |
+
color: #f0f0f0; /* 输入框文本颜色 */
|
| 33 |
+
}
|
| 34 |
+
button {
|
| 35 |
+
padding: 10px 15px;
|
| 36 |
+
background-color: #00bcd4; /* 按钮背景使用强调色 */
|
| 37 |
+
color: #1a1a1a; /* 按钮文本为深色 */
|
| 38 |
+
border: none;
|
| 39 |
+
border-radius: 5px;
|
| 40 |
+
cursor: pointer;
|
| 41 |
+
transition: background-color 0.3s;
|
| 42 |
+
}
|
| 43 |
+
button:hover {
|
| 44 |
+
background-color: #008ba3; /* 鼠标悬停时的深一点的强调色 */
|
| 45 |
+
}
|
| 46 |
+
/* #jsonOutput {
|
| 47 |
+
height: 300px;
|
| 48 |
+
margin-top: 20px;
|
| 49 |
+
color: #00bcd4;
|
| 50 |
+
background-color: #111;
|
| 51 |
+
} */
|
| 52 |
+
.container {
|
| 53 |
+
display: flex;
|
| 54 |
+
gap: 20px; /* 左右间距 */
|
| 55 |
+
/* max-width: 1200px; 视情况可删 */
|
| 56 |
+
width: 100%;
|
| 57 |
+
margin: 0 auto; /* 居中 */
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
/* 左侧主体:自适应剩余宽度 */
|
| 61 |
+
.left-main {
|
| 62 |
+
flex: 1;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
/* 右侧边栏:固定 340px 宽 */
|
| 66 |
+
.right-aside {
|
| 67 |
+
width: 40%;
|
| 68 |
+
}
|
| 69 |
+
.Output {
|
| 70 |
+
width: 100%;
|
| 71 |
+
/* height: 400px; */
|
| 72 |
+
height: 30vh;
|
| 73 |
+
resize: none;
|
| 74 |
+
color: #00bcd4;
|
| 75 |
+
background-color: #111;
|
| 76 |
+
border: 1px solid #00bcd4;
|
| 77 |
+
border-radius: 4px;
|
| 78 |
+
padding: 8px;
|
| 79 |
+
font-family: monospace;
|
| 80 |
+
}
|
| 81 |
+
.Input {
|
| 82 |
+
width: 100%;
|
| 83 |
+
height: 80%; /* 想撑满父级可用 calc(100vh - 40px) */
|
| 84 |
+
resize: none;
|
| 85 |
+
color: #f0f0f0;
|
| 86 |
+
background-color: #2c2c2c;
|
| 87 |
+
border: 1px solid #444;
|
| 88 |
+
border-radius: 4px;
|
| 89 |
+
padding: 8px;
|
| 90 |
+
font-family: monospace;
|
| 91 |
+
}
|
| 92 |
+
#response {
|
| 93 |
+
margin-top: 20px;
|
| 94 |
+
padding: 10px;
|
| 95 |
+
border-left: 5px solid #00bcd4; /* 响应区使用强调色 */
|
| 96 |
+
background-color: #2c2c2c;
|
| 97 |
+
white-space: pre-wrap;
|
| 98 |
+
color: #f0f0f0;
|
| 99 |
+
}
|
| 100 |
+
</style>
|
| 101 |
+
</head>
|
| 102 |
+
<body>
|
| 103 |
+
<h1>Notion 任务快速录入</h1>
|
| 104 |
+
<div class="container">
|
| 105 |
+
<!-- 左侧主体内容 -->
|
| 106 |
+
<main class="left-main">
|
| 107 |
+
<textarea class="Input" id="taskInput" placeholder="例如:明天上午10点 设计评审会, 下午4-6点 团队建设 high priority, 用pad"></textarea>
|
| 108 |
+
<button onclick="submitTasks()">提交到 Notion</button>
|
| 109 |
+
<label style="margin-left: 20px; color: #ccc;">
|
| 110 |
+
<input type="checkbox" id="debugMode" style="vertical-align: middle;"> 调试模式
|
| 111 |
+
</label>
|
| 112 |
+
<div id="response"></div>
|
| 113 |
+
</main>
|
| 114 |
+
|
| 115 |
+
<!-- 右侧边栏 -->
|
| 116 |
+
<aside class="right-aside">
|
| 117 |
+
<textarea class="Output" id="jsonOutput" placeholder="AI 转换结果将在此处显示..." readonly></textarea>
|
| 118 |
+
</aside>
|
| 119 |
+
</div>
|
| 120 |
+
<script>
|
| 121 |
+
async function submitTasks() {
|
| 122 |
+
const input = document.getElementById('taskInput').value;
|
| 123 |
+
const debugMode = document.getElementById('debugMode').checked;
|
| 124 |
+
const responseDiv = document.getElementById('response');
|
| 125 |
+
const jsonOutputArea = document.getElementById('jsonOutput');
|
| 126 |
+
responseDiv.innerHTML = '正在处理...';
|
| 127 |
+
responseDiv.style.display = 'block';
|
| 128 |
+
jsonOutputArea.value = ''; // 清空之前的内容
|
| 129 |
+
// 构建 URL, 如果是调试模式,则添加 ?debug=true
|
| 130 |
+
const url = debugMode ? '/?debug=true' : '/';
|
| 131 |
+
|
| 132 |
+
try {
|
| 133 |
+
const response = await fetch(url, {
|
| 134 |
+
method: 'POST',
|
| 135 |
+
headers: {
|
| 136 |
+
'Content-Type': 'text/plain'
|
| 137 |
+
},
|
| 138 |
+
body: input
|
| 139 |
+
});
|
| 140 |
+
|
| 141 |
+
if (!response.ok) {
|
| 142 |
+
const text = await response.text();
|
| 143 |
+
// 抛出错误,将被 catch 块捕获
|
| 144 |
+
throw new Error(`HTTP Error ${response.status}: ${text}`);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
const data = await response.json();
|
| 148 |
+
|
| 149 |
+
if (data.ok) {
|
| 150 |
+
if (data.debug) {
|
| 151 |
+
// 调试模式的特殊显示
|
| 152 |
+
responseDiv.innerHTML = `✅ **调试模式开启:未推送到 Notion**<br><br>AI 转换后的 JSON 数据`;
|
| 153 |
+
responseDiv.style.borderLeftColor = 'orange';
|
| 154 |
+
} else {
|
| 155 |
+
// 正常模式的显示
|
| 156 |
+
responseDiv.innerHTML = `✅ 提交成功! 共插入 ${data.inserted} 个任务。`;
|
| 157 |
+
responseDiv.style.borderLeftColor = '#00bcd4';
|
| 158 |
+
}
|
| 159 |
+
// 在右侧文本区域显示 AI 转换结果
|
| 160 |
+
jsonOutputArea.value = JSON.stringify(data.items, null, 2);
|
| 161 |
+
} else {
|
| 162 |
+
// 业务逻辑错误
|
| 163 |
+
responseDiv.innerHTML = `❌ 提交失败: ${data.error || '未知错误'}`;
|
| 164 |
+
responseDiv.style.borderLeftColor = 'red';
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
} catch (error) {
|
| 168 |
+
// 网络或代码/JSON 解析错误
|
| 169 |
+
responseDiv.innerHTML = `❌ 发生错误: ${error.message}`;
|
| 170 |
+
responseDiv.style.borderLeftColor = 'red';
|
| 171 |
+
console.error('Error:', error);
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
function _submitTasks() {
|
| 175 |
+
const input = document.getElementById('taskInput').value;
|
| 176 |
+
const debugMode = document.getElementById('debugMode').checked; // 获取调试模式状态
|
| 177 |
+
const responseDiv = document.getElementById('response');
|
| 178 |
+
responseDiv.innerHTML = '正在处理...';
|
| 179 |
+
|
| 180 |
+
// 构建 URL, 如果是调试模式,则添加 ?debug=true
|
| 181 |
+
const url = debugMode ? '/?debug=true' : '/';
|
| 182 |
+
|
| 183 |
+
fetch(url, {
|
| 184 |
+
method: 'POST',
|
| 185 |
+
headers: {
|
| 186 |
+
'Content-Type': 'text/plain'
|
| 187 |
+
},
|
| 188 |
+
body: input
|
| 189 |
+
})
|
| 190 |
+
.then(response => {
|
| 191 |
+
if (!response.ok) {
|
| 192 |
+
return response.text().then(text => Promise.reject(new Error(`HTTP Error ${response.status}: ${text}`)));
|
| 193 |
+
}
|
| 194 |
+
// 无论是否调试模式,都期望返回 JSON
|
| 195 |
+
return response.json();
|
| 196 |
+
})
|
| 197 |
+
.then(data => {
|
| 198 |
+
if (data.ok) {
|
| 199 |
+
if (data.debug) {
|
| 200 |
+
// 调试模式的特殊显示
|
| 201 |
+
responseDiv.innerHTML = `✅ **调试模式开启:未推送到 Notion**<br><br>AI 转换后的 JSON 数据:\n${JSON.stringify(data.ai_output, null, 2)}`;
|
| 202 |
+
responseDiv.style.borderLeftColor = 'orange';
|
| 203 |
+
} else {
|
| 204 |
+
responseDiv.innerHTML = `✅ 提交成功! 共插入 ${data.inserted} 个任务。`;
|
| 205 |
+
responseDiv.style.borderLeftColor = '#00bcd4';
|
| 206 |
+
}
|
| 207 |
+
} else {
|
| 208 |
+
responseDiv.innerHTML = `❌ 提交失败: ${data.error || '未知错误'}`;
|
| 209 |
+
responseDiv.style.borderLeftColor = 'red';
|
| 210 |
+
}
|
| 211 |
+
})
|
| 212 |
+
.catch(error => {
|
| 213 |
+
responseDiv.innerHTML = `❌ 发生错误: ${error.message}`;
|
| 214 |
+
responseDiv.style.borderLeftColor = 'red';
|
| 215 |
+
console.error('Error:', error);
|
| 216 |
+
});
|
| 217 |
+
}
|
| 218 |
+
</script>
|
| 219 |
+
</body>
|
| 220 |
+
</html>
|