Skip to content

Commit 32df2f1

Browse files
committed
chore: add crontab ai gen
1 parent efe8209 commit 32df2f1

9 files changed

Lines changed: 88 additions & 13 deletions

File tree

internal/api/api.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,36 @@
11
package api
2+
3+
import (
4+
"devtools/internal/openai"
5+
"strings"
6+
)
7+
8+
func Crontab(ctx *Context) error {
9+
desc := ctx.QueryParam("desc")
10+
if strings.TrimSpace(desc) == "" {
11+
return nil
12+
}
13+
resp, err := ctx.oai.Chat(ctx.Request().Context(), openai.ReqChat{
14+
Stream: false,
15+
Model: ctx.cfg.OpenAI.Model,
16+
Messages: buildPrompt(desc),
17+
Temperature: 0,
18+
})
19+
if err != nil {
20+
return ctx.ErrMsg(err, "调用 AI 生成异常")
21+
}
22+
return ctx.Data(resp.Message.Content)
23+
}
24+
25+
func buildPrompt(desc string) []openai.Message {
26+
return []openai.Message{
27+
{
28+
Role: openai.RSystem,
29+
Content: "You are a professional Crontab generator. Don't do any interpretation, just reply to the expression. If the user sends a crontab, it is returned as is.",
30+
},
31+
{
32+
Role: openai.RUser,
33+
Content: desc,
34+
},
35+
}
36+
}

internal/api/context.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111

1212
type Context struct {
1313
echo.Context
14-
*config.Config
15-
*openai.OpenAI
14+
cfg *config.Config
15+
oai *openai.OpenAI
1616
}
1717

1818
func Middleware(cfg *config.Config) echo.MiddlewareFunc {

internal/route/route.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package route
22

33
import (
4+
"devtools/internal/api"
45
"github.com/labstack/echo/v4"
56
)
67

78
func Register(e *echo.Echo) {
89
g := e.Group("/api")
9-
_ = g
10+
g.GET("/crontab", api.Wrap(api.Crontab))
1011
}

internal/utils/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
func Md5(str string) string {
1111
h := md5.New()
12-
h.Write([]byte(str))
12+
h.Write(Str2Bytes(str))
1313
return hex.EncodeToString(h.Sum(nil))
1414
}
1515

web/astro.config.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,10 @@ export default defineConfig({
1111
integrations: [react()],
1212
vite: {
1313
plugins: [tailwindcss()],
14+
server: {
15+
proxy: {
16+
"/api": "http://127.0.0.1:3000",
17+
},
18+
},
1419
},
1520
})

web/bun.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"class-variance-authority": "^0.7.1",
2121
"clsx": "^2.1.1",
2222
"cron-parser": "^5.3.0",
23-
"lucide-react": "^0.518.0",
23+
"lucide-react": "^0.522.0",
2424
"nanostores": "^1.0.1",
2525
"prettier": "^3.5.3",
2626
"prettier-plugin-astro": "^0.14.1",

web/src/components/tsx/TCrontab.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { Button } from "@/components/ui/button.tsx"
22
import { Input } from "@/components/ui/input.tsx"
3+
import { respData } from "@/lib/utils"
34
import { CronExpressionParser } from "cron-parser"
4-
import { Calculator, Timer } from "lucide-react"
5+
import { Atom, Calculator, Timer } from "lucide-react"
56
import React, { useState } from "react"
67
import { toast } from "sonner"
78

89
function TCrontab() {
910
const [input, setInput] = useState("")
1011
const [list, setList] = useState<string[]>([])
1112

12-
function onClick() {
13-
const expr = input.trim()
13+
const [loading, setLoading] = useState(false)
14+
15+
function onClick(data?: string) {
16+
const expr = (data ?? input).trim()
1417
if (!expr) {
1518
return
1619
}
@@ -24,6 +27,22 @@ function TCrontab() {
2427
}
2528
}
2629

30+
function onAIClick() {
31+
const desc = input.trim()
32+
if (!desc) {
33+
return
34+
}
35+
setLoading(true)
36+
fetch(`/api/crontab?desc=${desc}`)
37+
.then(respData)
38+
.then((data) => {
39+
setInput(data)
40+
onClick(data)
41+
})
42+
.catch((err) => toast.error(err.message ?? err))
43+
.finally(() => setLoading(false))
44+
}
45+
2746
function onKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
2847
if (e.key !== "Enter" || e.nativeEvent.isComposing) {
2948
return
@@ -37,15 +56,19 @@ function TCrontab() {
3756
<div className="flex flex-col gap-3 sm:flex-row">
3857
<Input
3958
value={input}
59+
disabled={loading}
4060
onKeyDown={onKeyDown}
4161
className="font-mono"
4262
onChange={(e) => setInput(e.currentTarget.value)}
43-
placeholder="请输入 Crontab 表达式"
44-
></Input>
45-
<Button variant="secondary" onClick={onClick}>
63+
placeholder="Crontab 表达式 / 每两小时执行一次"
64+
/>
65+
<Button onClick={() => onClick()} disabled={loading}>
4666
<Calculator />
4767
计算执行时间
4868
</Button>
69+
<Button variant="secondary" onClick={onAIClick} disabled={loading}>
70+
<Atom />让 AI 生成
71+
</Button>
4972
</div>
5073
{list.length > 0 && (
5174
<div className="flex flex-col gap-2">

web/src/lib/utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,14 @@ export function copyToClipboard(value: string) {
1212
.then(() => toast.success("已拷贝至剪贴板"))
1313
.catch((err) => toast.error(err.message ?? err))
1414
}
15+
16+
export async function respData(resp: Response): Promise<any> {
17+
const res = await resp.json()
18+
if (!resp.ok) {
19+
return Promise.reject(`${resp.status} ${res.message}`)
20+
}
21+
if (res.code !== 0) {
22+
return Promise.reject(res.message)
23+
}
24+
return res.data
25+
}

0 commit comments

Comments
 (0)