├── .env.template ├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── api │ ├── endpoint │ │ ├── [id] │ │ │ └── route.ts │ │ ├── add │ │ │ └── route.ts │ │ ├── list │ │ │ └── route.ts │ │ └── route.ts │ └── translate │ │ └── route.ts ├── endpoints │ └── page.tsx ├── favicon.ico ├── globals.css ├── layout.tsx ├── page.tsx └── playground │ └── page.tsx ├── components.json ├── components ├── main-nav.tsx └── ui │ ├── button.tsx │ ├── card.tsx │ ├── toast.tsx │ ├── toaster.tsx │ └── use-toast.ts ├── conf └── db │ └── nts.sql ├── lib ├── db.ts └── utils.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── next.svg ├── snapshot.png └── vercel.svg ├── tailwind.config.js └── tsconfig.json /.env.template: -------------------------------------------------------------------------------- 1 | AZURE_OPENAI_ENDPOINT= 2 | AZURE_OPENAI_API_KEY= 3 | 4 | MYSQL_HOST=127.0.0.1 5 | MYSQL_PORT=3306 6 | MYSQL_DATABASE=nts 7 | MYSQL_USERNAME= 8 | MYSQL_PASSWORD= -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | package-lock.json 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

Json Translator

3 | Translate the input of natural language into a specified structure. 4 | 5 | ![](./public/snapshot.png) 6 | 7 | - powered by [microsoft/TypeChat](https://github.com/microsoft/TypeChat) 8 | 9 | ## Online Demo 10 | https://nts.cooder.org/ 11 | 12 | ## Getting Started 13 | 14 | First, copy `.env.template` to `.env.local`, and set your environment variable(OpenAI API key...) in `.env.local`: 15 | 16 | ```bash 17 | cp .env.template .env.local 18 | ``` 19 | 20 | Second, import the `conf/db/nts.sql` into your database. 21 | 22 | At last, run the development server: 23 | 24 | ```bash 25 | npm run dev 26 | # or 27 | yarn dev 28 | # or 29 | pnpm dev 30 | ``` 31 | 32 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 33 | 34 | ## API ENDPOINT 35 | 36 | - /api/translate 37 | 38 | ```bash 39 | curl -X 'POST' \ 40 | 'http://127.0.0.1:3000/api/translate' \ 41 | -H 'accept: */*' \ 42 | -H 'Content-Type: application/json' \ 43 | -d '{ 44 | "schema": "// The following is a schema definition for determining whether a user wants to share a post or not:\nexport interface ShareOrNot {\nisShare: boolean;\nurl: string;\ncomment: string;\n}", 45 | "typeName": "ShareOrNot", 46 | "prompt": "https://github.com/shengxia/RWKV_Role_Playing_API 一个基于Flask实现的RWKV角色扮演API" 47 | }' 48 | 49 | ``` 50 | 51 | - /api/endpoint/[id] 52 | 53 | ```bash 54 | curl -X 'POST' 'http://127.0.0.1:3000/api/endpoint/2dafcb4800ec144356799d5f4da07f32' \ 55 | -H 'Content-Type: application/json' \ 56 | -d '{"prompt": "it is very rainy outside"}' 57 | ``` -------------------------------------------------------------------------------- /app/api/endpoint/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import type { NextRequest } from "next/server"; 3 | import { createLanguageModel, createJsonTranslator, processRequests } from "typechat"; 4 | import { executeQuery } from "@/lib/db"; 5 | 6 | const model = createLanguageModel(process.env); 7 | 8 | async function getEndpoint(id: string) { 9 | const query = `SELECT * FROM endpoints WHERE type_id = ? limit 1`; 10 | const values = [id]; 11 | const recs = await executeQuery({ query, values }); 12 | const rec = (recs as any[])[0]; 13 | return { 14 | id: rec.type_id, 15 | typeName: rec.type_name, 16 | schema: rec.type_schema, 17 | prompt: rec.prompt, 18 | }; 19 | } 20 | 21 | export async function GET(req: NextRequest, context: { params: { id: string } }) { 22 | const { id } = context.params; 23 | const record = await getEndpoint(id); 24 | return NextResponse.json({success: true, data: record}); 25 | } 26 | 27 | export async function POST(req: NextRequest, context: { params: { id: string } }) { 28 | const { id } = context.params; 29 | const { prompt } = await req.json(); 30 | 31 | if (!prompt) { 32 | return NextResponse.json( 33 | { error: "Missing required parameters: prompt" }, 34 | { status: 400, statusText: "Bad Request" } 35 | ); 36 | } 37 | 38 | const record = await getEndpoint(id); 39 | 40 | const { typeName, schema } = record; 41 | const translator = createJsonTranslator(model, schema, typeName); 42 | const resp = await translator.translate(prompt); 43 | const data = resp.success ? resp.data : { error: resp.message }; 44 | 45 | return NextResponse.json(data); 46 | } -------------------------------------------------------------------------------- /app/api/endpoint/add/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import type { NextRequest } from "next/server"; 3 | import { md5sum } from "@/lib/utils"; 4 | import { executeQuery, DBError } from "@/lib/db"; 5 | 6 | export async function POST(req: NextRequest) { 7 | const { schema, typeName, prompt } = await req.json(); 8 | if (!schema || !typeName || !prompt) { 9 | return NextResponse.json({ error: "Missing required parameters: schema, typeName, prompt" }, { status: 400, statusText: "Bad Request" }); 10 | } 11 | 12 | const id = md5sum(schema); 13 | const query = "INSERT INTO `endpoints` (`type_id`, `type_schema`, `type_name`, `prompt`) VALUES (?, ?, ?, ?)"; 14 | const values = [id, schema, typeName, prompt]; 15 | const resp = await executeQuery({query, values}); 16 | if ((resp as any).error) { 17 | return NextResponse.json({ error: (resp as DBError).error }, { status: 500 }); 18 | } 19 | 20 | return NextResponse.json({ success: true, endpointId: id }); 21 | } 22 | -------------------------------------------------------------------------------- /app/api/endpoint/list/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import type { NextRequest } from "next/server"; 3 | 4 | import { executeQuery } from "@/lib/db"; 5 | 6 | export async function POST(req: NextRequest) { 7 | const query = "select * from endpoints"; 8 | const resp = await executeQuery({query}); 9 | const data = (resp as any).map((r: any) => { 10 | return { 11 | id: r.type_id, 12 | typeName: r.type_name, 13 | schema: r.type_schema, 14 | prompt: r.prompt, 15 | } 16 | }); 17 | 18 | return NextResponse.json({ success: true, data: data }); 19 | } 20 | -------------------------------------------------------------------------------- /app/api/endpoint/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import type { NextRequest } from "next/server"; 3 | 4 | import { createLanguageModel, createJsonTranslator, processRequests } from "typechat"; 5 | 6 | const model = createLanguageModel(process.env); 7 | 8 | export async function POST(req: NextRequest) { 9 | const { schema, typeName, prompt } = await req.json(); 10 | if (!schema || !typeName || !prompt) { 11 | return NextResponse.json({ error: "Missing required parameters: schema, typeName, prompt" }, { status: 400, statusText: "Bad Request" }); 12 | } 13 | 14 | const translator = createJsonTranslator(model, schema, typeName); 15 | const resp = await translator.translate(prompt); 16 | const data = resp.success ? resp.data : { error: resp.message }; 17 | return NextResponse.json(data); 18 | } 19 | -------------------------------------------------------------------------------- /app/api/translate/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import type { NextRequest } from "next/server"; 3 | 4 | import { createLanguageModel, createJsonTranslator, processRequests } from "typechat"; 5 | 6 | const model = createLanguageModel(process.env); 7 | 8 | export async function POST(req: NextRequest) { 9 | const { schema, typeName, prompt } = await req.json(); 10 | if (!schema || !typeName || !prompt) { 11 | return NextResponse.json({ error: "Missing required parameters: schema, typeName, prompt" }, { status: 400, statusText: "Bad Request" }); 12 | } 13 | 14 | const translator = createJsonTranslator(model, schema, typeName); 15 | const resp = await translator.translate(prompt); 16 | const data = resp.success ? resp.data : { error: resp.message }; 17 | return NextResponse.json(data); 18 | } 19 | -------------------------------------------------------------------------------- /app/endpoints/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useEffect, useState } from "react"; 3 | 4 | import CodeEditor from "@uiw/react-textarea-code-editor"; 5 | import { cn } from "@/lib/utils"; 6 | import { Button } from "@/components/ui/button"; 7 | import Link from "next/link"; 8 | import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"; 9 | import { useToast } from "@/components/ui/use-toast"; 10 | 11 | function Container({ className, ...props }: React.HTMLAttributes) { 12 | return
div]:w-full", className)} {...props} />; 13 | } 14 | 15 | export default function Home() { 16 | const { toast } = useToast(); 17 | const [endpoints, setEndpoints] = useState([]); 18 | 19 | useEffect(() => { 20 | fetch("/api/endpoint/list", { method: "POST" }) 21 | .then((response) => response.json()) 22 | .then((data) => setEndpoints(data.data)); 23 | }, []); 24 | 25 | const copyToClipboard = (id: string, prompt: string) => { 26 | const body = { prompt: prompt }; 27 | const currentHost = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : ""); 28 | const curl = `curl -X 'POST' '${currentHost}/api/endpoint/${id}' \\\n -H 'Content-Type: application/json' \\\n -d '${JSON.stringify(body)}' `; 29 | navigator.clipboard.writeText(curl); 30 | toast({ 31 | title: "Copied to clipboard", 32 | description: "The cURL command has been copied to your clipboard.", 33 | }); 34 | }; 35 | 36 | return ( 37 |
38 |
39 | {endpoints && 40 | endpoints.map((endpoint: any, index: number) => ( 41 | 42 | 43 | 44 | {endpoint.typeName} 45 | 46 | 47 |
48 | 64 |
65 |
66 | 67 | 70 | 73 | 74 |
75 |
76 | ))} 77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cooder-org/json-translator/db4132782f79b12fcca068985df3fa67d2bd86b3/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --muted: 210 40% 96.1%; 11 | --muted-foreground: 215.4 16.3% 46.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 222.2 84% 4.9%; 18 | 19 | --border: 214.3 31.8% 91.4%; 20 | --input: 214.3 31.8% 91.4%; 21 | 22 | --primary: 222.2 47.4% 11.2%; 23 | --primary-foreground: 210 40% 98%; 24 | 25 | --secondary: 210 40% 96.1%; 26 | --secondary-foreground: 222.2 47.4% 11.2%; 27 | 28 | --accent: 210 40% 96.1%; 29 | --accent-foreground: 222.2 47.4% 11.2%; 30 | 31 | --destructive: 0 84.2% 60.2%; 32 | --destructive-foreground: 210 40% 98%; 33 | 34 | --ring: 215 20.2% 65.1%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | --background: 222.2 84% 4.9%; 41 | --foreground: 210 40% 98%; 42 | 43 | --muted: 217.2 32.6% 17.5%; 44 | --muted-foreground: 215 20.2% 65.1%; 45 | 46 | --popover: 222.2 84% 4.9%; 47 | --popover-foreground: 210 40% 98%; 48 | 49 | --card: 222.2 84% 4.9%; 50 | --card-foreground: 210 40% 98%; 51 | 52 | --border: 217.2 32.6% 17.5%; 53 | --input: 217.2 32.6% 17.5%; 54 | 55 | --primary: 210 40% 98%; 56 | --primary-foreground: 222.2 47.4% 11.2%; 57 | 58 | --secondary: 217.2 32.6% 17.5%; 59 | --secondary-foreground: 210 40% 98%; 60 | 61 | --accent: 217.2 32.6% 17.5%; 62 | --accent-foreground: 210 40% 98%; 63 | 64 | --destructive: 0 62.8% 30.6%; 65 | --destructive-foreground: 0 85.7% 97.3%; 66 | 67 | --ring: 217.2 32.6% 17.5%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | body { 76 | @apply bg-background text-foreground; 77 | } 78 | } -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { Metadata } from "next"; 3 | import { Inter } from "next/font/google"; 4 | import { MainNav } from "@/components/main-nav"; 5 | import { Toaster } from "@/components/ui/toaster" 6 | 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | export const metadata: Metadata = { 10 | title: "Json Translator", 11 | description: "Generated by cooder.org", 12 | }; 13 | 14 | export default function RootLayout({ children }: { children: React.ReactNode }) { 15 | return ( 16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 | {children} 26 | 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import Link from "next/link"; 3 | 4 | export default function Home() { 5 | return ( 6 | <> 7 |
8 |
9 |

Make LLM As Your API Server

10 |

Types are all you need

11 |
12 |
13 | 16 | 19 |
20 |
21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/playground/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { use, useEffect, useState } from "react"; 4 | import CodeEditor from "@uiw/react-textarea-code-editor"; 5 | import { Triangle } from "react-loader-spinner"; 6 | import { Button } from "@/components/ui/button"; 7 | import { useToast } from "@/components/ui/use-toast"; 8 | 9 | export default function Home() { 10 | const { toast } = useToast(); 11 | const [loading, setLoading] = useState(false); 12 | const [formData, setFormData] = useState({ 13 | typeName: "ShareOrNot", 14 | schema: 15 | "// The following is a schema definition for determining whether a user wants to share a post or not:\n\nexport interface ShareOrNot {\n isShare: boolean;\n url: string;\n comment: string;\n}", 16 | output: "", 17 | prompt: "https://github.com/shengxia/RWKV_Role_Playing_API A Flask-based RWKV role-playing API", 18 | }); 19 | 20 | useEffect(() => { 21 | const params = new URLSearchParams(window.location.search); 22 | const id = params.get("id"); 23 | if (id) { 24 | const fetchData = async () => { 25 | setLoading(true); 26 | const response = await fetch(`/api/endpoint/${id}`); 27 | const rsp = await response.json(); 28 | setFormData(rsp.data); 29 | setLoading(false); 30 | }; 31 | fetchData(); 32 | } 33 | }, []); 34 | 35 | const handleChange = (event: React.ChangeEvent) => { 36 | const { name, value } = event.target; 37 | setFormData((prevState) => ({ ...prevState, [name]: value })); 38 | }; 39 | 40 | const onClick = async () => { 41 | setLoading(true); 42 | const response = await fetch("/api/translate", { 43 | method: "POST", 44 | headers: { 45 | "Content-Type": "application/json", 46 | }, 47 | body: JSON.stringify(formData), 48 | }); 49 | const data = await response.json(); 50 | setFormData((prevState) => ({ ...prevState, output: JSON.stringify(data, null, 2) })); 51 | setLoading(false); 52 | }; 53 | 54 | const onAddEndpoint = async () => { 55 | setLoading(true); 56 | const response = await fetch("/api/endpoint/add", { 57 | method: "POST", 58 | headers: { 59 | "Content-Type": "application/json", 60 | }, 61 | body: JSON.stringify(formData), 62 | }); 63 | const data = await response.json(); 64 | if (data.success) { 65 | toast({ 66 | title: "Success", 67 | description: `Add endpoint success. id: ${data.endpointId}`, 68 | }); 69 | } else { 70 | toast({ 71 | variant: "destructive", 72 | title: "Error", 73 | description: `Add endpoint error: ${data?.error?.code}`, 74 | }); 75 | } 76 | setLoading(false); 77 | }; 78 | 79 | return ( 80 | <> 81 | {loading && ( 82 | 89 | )} 90 |
91 |
92 |
93 |
94 |
95 | 98 |
99 | 107 |
108 |
109 | 110 |
111 |
112 | 115 |
116 |
117 | 123 | setFormData({ 124 | ...formData, 125 | schema: e.target.value, 126 | }) 127 | } 128 | padding={15} 129 | style={{ 130 | minHeight: 300, 131 | fontSize: 12, 132 | fontFamily: "ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace", 133 | }} 134 | /> 135 |
136 |
137 | 138 |
139 |
140 | 143 |
144 |
145 |