Skip to content

Commit a507261

Browse files
committed
update
1 parent 96cf0f7 commit a507261

File tree

8 files changed

+1003
-255
lines changed

8 files changed

+1003
-255
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules/
2+
generated/
23
.env
Lines changed: 64 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,94 @@
11
import express from "express";
2+
import { PrismaClient } from "./generated/prisma/index.js";
23

34
const app = express();
5+
const client = new PrismaClient();
6+
47
app.use(express.json());
58
app.use(express.static("./public"));
69

7-
// タスクをメモリ上で管理
8-
let todos = [];
9-
let nextId = 1;
10-
11-
const systemPrompt = `ユーザーの入力からタスクと期限を抽出してください。
12-
出力は必ず2行で、1行目が期限の日時、2行目がタスクのタイトルです。
13-
日時は「2024/1/21 10:00」のような形式にしてください。
14-
期限の情報がない場合は1行目を空にしてください。
10+
const systemPrompt = `ユーザーの発話からタスクと時間を抽出してください。
11+
出力は必ず2行で、1行目がISO8601形式の日時(タイムゾーンは+09:00)、2行目がタスクタイトルです。
12+
時間情報がない場合は1行目を空にしてください。
1513
1614
例:
1715
入力: 明日の10時に会議
1816
出力:
19-
2024/1/21 10:00
17+
2024-01-21T10:00:00+09:00
2018
会議
2119
2220
入力: 買い物に行く
2321
出力:
2422
2523
買い物に行く`;
2624

25+
// 自然言語でタスクを追加(AI解析 + DB保存)
26+
app.post("/todos/ai", async (request, response) => {
27+
try {
28+
const result = await fetch(
29+
"https://openrouter.ai/api/v1/chat/completions",
30+
{
31+
method: "POST",
32+
headers: {
33+
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
34+
"Content-Type": "application/json",
35+
},
36+
body: JSON.stringify({
37+
model: "google/gemini-3-flash-preview",
38+
messages: [
39+
{ role: "system", content: systemPrompt },
40+
{ role: "user", content: request.body.text },
41+
],
42+
}),
43+
},
44+
);
45+
const data = await result.json();
46+
47+
if (!data.choices || !data.choices[0]) {
48+
response.status(500).json({ error: "AIからの応答が不正です" });
49+
return;
50+
}
51+
52+
const content = data.choices[0].message.content;
53+
const lines = content.split("\n");
54+
const dueAt = lines[0] ? new Date(lines[0]) : null;
55+
const title = lines[1] || "";
56+
57+
const todo = await client.todo.create({
58+
data: { title, due_at: dueAt },
59+
});
60+
response.json(todo);
61+
} catch (error) {
62+
console.error("Parse error:", error);
63+
response.status(500).json({ error: "解析に失敗しました" });
64+
}
65+
});
66+
2767
// タスク一覧を取得
28-
app.get("/todos", (request, response) => {
68+
app.get("/todos", async (request, response) => {
69+
const todos = await client.todo.findMany({
70+
orderBy: { createdAt: "desc" },
71+
});
2972
response.json(todos);
3073
});
3174

3275
// タスクを追加
33-
app.post("/todos", (request, response) => {
34-
const todo = {
35-
id: nextId,
36-
title: request.body.title,
37-
dueAt: request.body.dueAt || null,
38-
};
39-
nextId += 1;
40-
todos.push(todo);
76+
app.post("/todos", async (request, response) => {
77+
const todo = await client.todo.create({
78+
data: {
79+
title: request.body.title,
80+
due_at: request.body.due_at ? new Date(request.body.due_at) : null,
81+
},
82+
});
4183
response.json(todo);
4284
});
4385

4486
// タスクを削除
45-
app.delete("/todos/:id", (request, response) => {
46-
const id = Number(request.params.id);
47-
todos = todos.filter((todo) => todo.id !== id);
87+
app.delete("/todos/:id", async (request, response) => {
88+
await client.todo.delete({
89+
where: { id: parseInt(request.params.id) },
90+
});
4891
response.json({ success: true });
4992
});
5093

51-
// 自然言語でタスクを追加(AI解析)
52-
app.post("/todos/ai", async (request, response) => {
53-
const aiResponse = await fetch(
54-
"https://openrouter.ai/api/v1/chat/completions",
55-
{
56-
method: "POST",
57-
headers: {
58-
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
59-
"Content-Type": "application/json",
60-
},
61-
body: JSON.stringify({
62-
model: "google/gemini-2.0-flash-exp:free",
63-
messages: [
64-
{ role: "system", content: systemPrompt },
65-
{ role: "user", content: request.body.text },
66-
],
67-
}),
68-
},
69-
);
70-
71-
const data = await aiResponse.json();
72-
const content = data.choices[0].message.content;
73-
// AIの応答を改行で分割し、1行目を期限、2行目をタイトルとして取得
74-
const firstNewLine = content.indexOf("\n");
75-
const dueAt = content.slice(0, firstNewLine) || null;
76-
const title = content.slice(firstNewLine + 1) || request.body.text;
77-
78-
const todo = {
79-
id: nextId,
80-
title: title,
81-
dueAt: dueAt,
82-
};
83-
nextId += 1;
84-
todos.push(todo);
85-
response.json(todo);
86-
});
87-
8894
app.listen(3000);

0 commit comments

Comments
 (0)