jzhang533 commited on
Commit
82b23c6
Β·
1 Parent(s): 472b022

adapt to using ERNIE models

Browse files

Signed-off-by: Zhang Jun <jzhang533@gmail.com>

.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ *.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,4 +1,4 @@
1
-
2
 
3
 
4
  # DeepSite v2 πŸš€
 
1
+ This is being adapted for using ERNIE4.5 models
2
 
3
 
4
  # DeepSite v2 πŸš€
app/api/ask-ai/route.ts CHANGED
@@ -1,4 +1,11 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
 
 
 
 
 
 
 
2
  import type { NextRequest } from "next/server";
3
  import { NextResponse } from "next/server";
4
  import OpenAI from "openai";
@@ -45,6 +52,7 @@ export async function POST(request: NextRequest) {
45
  try {
46
  const chatCompletion = await openai.chat.completions.create({
47
  model,
 
48
  stream: true,
49
  messages: [
50
  { role: "system", content: INITIAL_SYSTEM_PROMPT },
@@ -71,14 +79,13 @@ export async function POST(request: NextRequest) {
71
  encoder.encode("\n[ERROR] Model returned empty response.\n")
72
  );
73
  }
74
- } catch (error: any) {
75
  await writer.write(
76
  encoder.encode(
77
  JSON.stringify({
78
  ok: false,
79
  message:
80
- error.message ||
81
- "An error occurred while processing your request.",
82
  })
83
  )
84
  );
@@ -88,12 +95,12 @@ export async function POST(request: NextRequest) {
88
  })();
89
 
90
  return response;
91
- } catch (error: any) {
92
  return NextResponse.json(
93
  {
94
  ok: false,
95
  message:
96
- error?.message || "An error occurred while processing your request.",
97
  },
98
  { status: 500 }
99
  );
@@ -113,8 +120,8 @@ export async function PUT(request: NextRequest) {
113
  } = body;
114
 
115
  const openai = new OpenAI({
116
- apiKey: apiKey || process.env.OPENAI_API_KEY || "",
117
- baseURL: baseUrl || process.env.OPENAI_BASE_URL,
118
  });
119
 
120
  if (!prompt || !html) {
@@ -127,6 +134,7 @@ export async function PUT(request: NextRequest) {
127
  try {
128
  const response = await openai.chat.completions.create({
129
  model,
 
130
  messages: [
131
  { role: "system", content: FOLLOW_UP_SYSTEM_PROMPT },
132
  {
@@ -213,12 +221,12 @@ export async function PUT(request: NextRequest) {
213
  html: newHtml,
214
  updatedLines,
215
  });
216
- } catch (error: any) {
217
  return NextResponse.json(
218
  {
219
  ok: false,
220
  message:
221
- error.message || "An error occurred while processing your request.",
222
  },
223
  { status: 500 }
224
  );
 
1
+ /* eslint-disab const { prompt, model, redesignMarkdown, html, apiKey, baseUrl, maxTokens } = body;
2
+
3
+ const openai = new OpenAI({
4
+ apiKey: process.env.OPENAI_API_KEY || apiKey || "",
5
+ baseURL: process.env.OPENAI_BASE_URL || baseUrl,
6
+ });
7
+
8
+ if (!model || (!prompt && !redesignMarkdown)) {script-eslint/no-explicit-any */
9
  import type { NextRequest } from "next/server";
10
  import { NextResponse } from "next/server";
11
  import OpenAI from "openai";
 
52
  try {
53
  const chatCompletion = await openai.chat.completions.create({
54
  model,
55
+ max_completion_tokens: 64000,
56
  stream: true,
57
  messages: [
58
  { role: "system", content: INITIAL_SYSTEM_PROMPT },
 
79
  encoder.encode("\n[ERROR] Model returned empty response.\n")
80
  );
81
  }
82
+ } catch (error: unknown) {
83
  await writer.write(
84
  encoder.encode(
85
  JSON.stringify({
86
  ok: false,
87
  message:
88
+ error instanceof Error ? error.message : "An error occurred while processing your request.",
 
89
  })
90
  )
91
  );
 
95
  })();
96
 
97
  return response;
98
+ } catch (error: unknown) {
99
  return NextResponse.json(
100
  {
101
  ok: false,
102
  message:
103
+ error instanceof Error ? error.message : "An error occurred while processing your request.",
104
  },
105
  { status: 500 }
106
  );
 
120
  } = body;
121
 
122
  const openai = new OpenAI({
123
+ apiKey: process.env.OPENAI_API_KEY || apiKey || "",
124
+ baseURL: process.env.OPENAI_BASE_URL || baseUrl,
125
  });
126
 
127
  if (!prompt || !html) {
 
134
  try {
135
  const response = await openai.chat.completions.create({
136
  model,
137
+ max_completion_tokens: 64000,
138
  messages: [
139
  { role: "system", content: FOLLOW_UP_SYSTEM_PROMPT },
140
  {
 
221
  html: newHtml,
222
  updatedLines,
223
  });
224
+ } catch (error: unknown) {
225
  return NextResponse.json(
226
  {
227
  ok: false,
228
  message:
229
+ error instanceof Error ? error.message : "An error occurred while processing your request.",
230
  },
231
  { status: 500 }
232
  );
components/editor/ask-ai/index.tsx CHANGED
@@ -54,24 +54,24 @@ export function AskAI({
54
  const [hasAsked, setHasAsked] = useState(false);
55
  const [previousPrompt, setPreviousPrompt] = useState("");
56
  const [provider, setProvider] = useLocalStorage("provider", "auto");
57
- const [openProvider, setOpenProvider] = useState(false);
58
- const [providerError, setProviderError] = useState("");
59
  const [think, setThink] = useState<string | undefined>(undefined);
60
  const [openThink, setOpenThink] = useState(false);
61
  const [isThinking, setIsThinking] = useState(true);
62
  const [controller, setController] = useState<AbortController | null>(null);
63
  const [isFollowUp, setIsFollowUp] = useState(true);
 
 
 
 
 
 
64
 
65
- const getModel = () =>
66
- typeof window !== "undefined"
67
- ? localStorage.getItem("openai_model") || "gpt-4o-mini"
68
- : "gpt-4o-mini";
69
 
70
  const callAi = async (redesignMarkdown?: string) => {
71
  if (isAiWorking) return;
72
  if (!redesignMarkdown && !prompt.trim()) return;
73
  setisAiWorking(true);
74
- setProviderError("");
75
  setThink("");
76
  setOpenThink(false);
77
  setIsThinking(true);
@@ -88,8 +88,6 @@ export function AskAI({
88
  const selectedElementHtml = selectedElement
89
  ? selectedElement.outerHTML
90
  : "";
91
- const apiKey = localStorage.getItem("openai_api_key");
92
- const baseUrl = localStorage.getItem("openai_base_url");
93
  const model = getModel();
94
  const request = await fetch("/api/ask-ai", {
95
  method: "PUT",
@@ -100,8 +98,6 @@ export function AskAI({
100
  model,
101
  html,
102
  selectedElementHtml,
103
- apiKey,
104
- baseUrl,
105
  }),
106
  headers: {
107
  "Content-Type": "application/json",
@@ -124,8 +120,6 @@ export function AskAI({
124
  if (audio.current) audio.current.play();
125
  }
126
  } else {
127
- const apiKey = localStorage.getItem("openai_api_key");
128
- const baseUrl = localStorage.getItem("openai_base_url");
129
  const model = getModel();
130
  const request = await fetch("/api/ask-ai", {
131
  method: "POST",
@@ -135,8 +129,6 @@ export function AskAI({
135
  model,
136
  html: isSameHtml ? "" : html,
137
  redesignMarkdown,
138
- apiKey,
139
- baseUrl,
140
  }),
141
  headers: {
142
  "Content-Type": "application/json",
@@ -413,16 +405,11 @@ export function AskAI({
413
  </div>
414
  <div className="flex items-center justify-end gap-2">
415
  <Settings
416
- provider={provider as string}
417
  model={getModel()}
418
- onChange={setProvider}
419
  onModelChange={(newModel: string) => {
 
420
  localStorage.setItem("openai_model", newModel);
421
  }}
422
- open={openProvider}
423
- error={providerError}
424
- isFollowUp={!isSameHtml && isFollowUp}
425
- onClose={setOpenProvider}
426
  />
427
  <Button
428
  size="iconXs"
 
54
  const [hasAsked, setHasAsked] = useState(false);
55
  const [previousPrompt, setPreviousPrompt] = useState("");
56
  const [provider, setProvider] = useLocalStorage("provider", "auto");
 
 
57
  const [think, setThink] = useState<string | undefined>(undefined);
58
  const [openThink, setOpenThink] = useState(false);
59
  const [isThinking, setIsThinking] = useState(true);
60
  const [controller, setController] = useState<AbortController | null>(null);
61
  const [isFollowUp, setIsFollowUp] = useState(true);
62
+ const [currentModel, setCurrentModel] = useState(() => {
63
+ if (typeof window !== "undefined") {
64
+ return localStorage.getItem("openai_model") || MODELS[0].value;
65
+ }
66
+ return MODELS[0].value;
67
+ });
68
 
69
+ const getModel = () => currentModel;
 
 
 
70
 
71
  const callAi = async (redesignMarkdown?: string) => {
72
  if (isAiWorking) return;
73
  if (!redesignMarkdown && !prompt.trim()) return;
74
  setisAiWorking(true);
 
75
  setThink("");
76
  setOpenThink(false);
77
  setIsThinking(true);
 
88
  const selectedElementHtml = selectedElement
89
  ? selectedElement.outerHTML
90
  : "";
 
 
91
  const model = getModel();
92
  const request = await fetch("/api/ask-ai", {
93
  method: "PUT",
 
98
  model,
99
  html,
100
  selectedElementHtml,
 
 
101
  }),
102
  headers: {
103
  "Content-Type": "application/json",
 
120
  if (audio.current) audio.current.play();
121
  }
122
  } else {
 
 
123
  const model = getModel();
124
  const request = await fetch("/api/ask-ai", {
125
  method: "POST",
 
129
  model,
130
  html: isSameHtml ? "" : html,
131
  redesignMarkdown,
 
 
132
  }),
133
  headers: {
134
  "Content-Type": "application/json",
 
405
  </div>
406
  <div className="flex items-center justify-end gap-2">
407
  <Settings
 
408
  model={getModel()}
 
409
  onModelChange={(newModel: string) => {
410
+ setCurrentModel(newModel);
411
  localStorage.setItem("openai_model", newModel);
412
  }}
 
 
 
 
413
  />
414
  <Button
415
  size="iconXs"
components/editor/ask-ai/settings.tsx CHANGED
@@ -1,142 +1,39 @@
1
- import { PiGearSixFill } from "react-icons/pi";
2
- import { useState, useEffect } from "react";
3
-
4
  import {
5
- Popover,
6
- PopoverContent,
7
- PopoverTrigger,
8
- } from "@/components/ui/popover";
9
- import { PROVIDERS, MODELS } from "@/lib/providers";
10
- import { Button } from "@/components/ui/button";
11
- import { useMemo } from "react";
12
- import { useUpdateEffect } from "react-use";
13
- import { Input } from "@/components/ui/input";
14
- import { toast } from "sonner";
15
 
16
  export function Settings({
17
- open,
18
- onClose,
19
- provider,
20
  model,
21
- error,
22
- onChange,
23
  }: {
24
- open: boolean;
25
- provider: string;
26
  model: string;
27
- error?: string;
28
- isFollowUp?: boolean;
29
- onClose: React.Dispatch<React.SetStateAction<boolean>>;
30
- onChange: (provider: string) => void;
31
  onModelChange: (model: string) => void;
32
  }) {
33
- const [apiKey, setApiKey] = useState("");
34
- const [baseUrl, setBaseUrl] = useState("");
35
- const [customModel, setCustomModel] = useState("");
36
-
37
- useEffect(() => {
38
- setApiKey(localStorage.getItem("openai_api_key") || "");
39
- setBaseUrl(localStorage.getItem("openai_base_url") || "");
40
- setCustomModel(localStorage.getItem("openai_model") || "");
41
- }, [open]);
42
-
43
- const modelAvailableProviders = useMemo(() => {
44
- const availableProviders = MODELS.find(
45
- (m: { value: string }) => m.value === model
46
- )?.providers;
47
- if (!availableProviders) return Object.keys(PROVIDERS);
48
- return Object.keys(PROVIDERS).filter((id) =>
49
- availableProviders.includes(id)
50
- );
51
- }, [model]);
52
-
53
- useUpdateEffect(() => {
54
- if (provider !== "auto" && !modelAvailableProviders.includes(provider)) {
55
- onChange("auto");
56
- }
57
- }, [model, provider]);
58
-
59
- const handleSaveSettings = () => {
60
- localStorage.setItem("openai_api_key", apiKey);
61
- localStorage.setItem("openai_base_url", baseUrl);
62
- localStorage.setItem("openai_model", customModel);
63
- toast.success("Settings saved!");
64
- onClose(false);
65
  };
66
 
67
  return (
68
- <div className="">
69
- <Popover open={open} onOpenChange={onClose}>
70
- <PopoverTrigger asChild>
71
- <Button variant="black" size="sm">
72
- <PiGearSixFill className="size-4" />
73
- Settings
74
- </Button>
75
- </PopoverTrigger>
76
- <PopoverContent
77
- className="!rounded-2xl p-0 !w-96 overflow-hidden !bg-neutral-900"
78
- align="center"
79
- >
80
- <header className="flex items-center justify-center text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
81
- Customize Settings
82
- </header>
83
- <main className="px-4 pt-5 pb-6 space-y-5">
84
- {error !== "" && (
85
- <p className="text-red-500 text-sm font-medium mb-2 flex items-center justify-between bg-red-500/10 p-2 rounded-md">
86
- {error}
87
- </p>
88
- )}
89
- <label className="block">
90
- <p className="text-neutral-300 text-sm mb-2.5">
91
- API Key
92
- </p>
93
- <Input
94
- type="password"
95
- placeholder="Enter your api key"
96
- value={apiKey}
97
- onChange={(e) => setApiKey(e.target.value)}
98
- className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
99
- />
100
- </label>
101
- <label className="block">
102
- <p className="text-neutral-300 text-sm mb-2.5">
103
- Base URL
104
- </p>
105
- <Input
106
- type="text"
107
- placeholder="e.g., http://127.0.0.1:11434/v1"
108
- value={baseUrl}
109
- onChange={(e) => setBaseUrl(e.target.value)}
110
- className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
111
- />
112
- </label>
113
- <label className="block">
114
- <p className="text-neutral-300 text-sm mb-2.5">
115
- Custom Model
116
- </p>
117
- <Input
118
- type="text"
119
- placeholder="e.g., gemma3:1b"
120
- value={customModel || "gemma3:1b"}
121
- onChange={(e) => setCustomModel(e.target.value)}
122
- className="!bg-neutral-800 !border-neutral-700 !text-neutral-200"
123
- />
124
- </label>
125
- <Button
126
- variant="default"
127
- size="sm"
128
- onClick={handleSaveSettings}
129
- className="mt-2 w-full"
130
- >
131
- Save Settings
132
- </Button>
133
- <div className="bg-amber-500/10 border-amber-500/10 p-3 text-xs text-amber-500 border rounded-lg">
134
- Accepts any OpenAI-compatible provider. Enter the corresponding API key and base URL (e.g., OpenRouter, DeepSeek, etc.).
135
- </div>
136
-
137
- </main>
138
- </PopoverContent>
139
- </Popover>
140
- </div>
141
  );
142
  }
 
 
 
 
1
  import {
2
+ Select,
3
+ SelectContent,
4
+ SelectItem,
5
+ SelectTrigger,
6
+ SelectValue,
7
+ } from "@/components/ui/select";
8
+ import { MODELS } from "@/lib/providers";
 
 
 
9
 
10
  export function Settings({
 
 
 
11
  model,
12
+ onModelChange,
 
13
  }: {
 
 
14
  model: string;
 
 
 
 
15
  onModelChange: (model: string) => void;
16
  }) {
17
+ const handleModelChange = (newModel: string) => {
18
+ onModelChange(newModel);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  };
20
 
21
  return (
22
+ <Select value={model} onValueChange={handleModelChange}>
23
+ <SelectTrigger className="bg-neutral-800 border-neutral-700 text-neutral-200 h-8 min-w-[180px]">
24
+ <SelectValue placeholder="Select a model" />
25
+ </SelectTrigger>
26
+ <SelectContent className="bg-neutral-800 border-neutral-700">
27
+ {MODELS.map((modelOption) => (
28
+ <SelectItem
29
+ key={modelOption.value}
30
+ value={modelOption.value}
31
+ className="text-neutral-200 hover:bg-neutral-700 focus:bg-neutral-700"
32
+ >
33
+ {modelOption.label}
34
+ </SelectItem>
35
+ ))}
36
+ </SelectContent>
37
+ </Select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  );
39
  }
components/editor/footer/index.tsx CHANGED
@@ -51,16 +51,7 @@ export function Footer({
51
  return (
52
  <footer className="border-t bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 py-2 flex items-center justify-between sticky bottom-0 z-20">
53
  <div className="flex items-center gap-2">
54
- {user &&
55
- (user?.isLocalUse ? (
56
- <>
57
- <div className="max-w-max bg-amber-500/10 rounded-full px-3 py-1 text-amber-500 border border-amber-500/20 text-sm font-semibold">
58
- Local Usage
59
- </div>
60
- </>
61
- ) : (
62
- <UserMenu className="!p-1 !pr-3 !h-auto" />
63
- ))}
64
  {user && <p className="text-neutral-700">|</p>}
65
  <Button size="sm" variant="secondary" onClick={onReset}>
66
  <MdAdd className="text-sm" />
@@ -81,6 +72,13 @@ export function Footer({
81
  50% { opacity: 0.2; }
82
  }
83
  `}</style>
 
 
 
 
 
 
 
84
  <span className="max-lg:hidden"><a
85
  href="https://github.com/MartinsMessias/deepsite-locally"
86
  target="_blank"
 
51
  return (
52
  <footer className="border-t bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 py-2 flex items-center justify-between sticky bottom-0 z-20">
53
  <div className="flex items-center gap-2">
54
+ {user && <UserMenu className="!p-1 !pr-3 !h-auto" />}
 
 
 
 
 
 
 
 
 
55
  {user && <p className="text-neutral-700">|</p>}
56
  <Button size="sm" variant="secondary" onClick={onReset}>
57
  <MdAdd className="text-sm" />
 
72
  50% { opacity: 0.2; }
73
  }
74
  `}</style>
75
+ <span className="max-lg:hidden"><a
76
+ href="https://github.com/MartinsMessias/deepsite-locally"
77
+ target="_blank"
78
+ className="text-xs lg:text-sm font-medium py-2 px-3 lg:px-4 rounded-lg flex items-center transition-all duration-100 cursor-pointer"
79
+ >
80
+ <span style={{ animation: 'blink-star 2s infinite' }}>✨</span> <span>Visit ERNIE Bot</span>
81
+ </a></span>
82
  <span className="max-lg:hidden"><a
83
  href="https://github.com/MartinsMessias/deepsite-locally"
84
  target="_blank"
lib/providers.ts CHANGED
@@ -8,10 +8,17 @@ export const PROVIDERS = {
8
 
9
  export const MODELS = [
10
  {
11
- value: "gpt-4o-mini", // Default model, can be overridden by user
12
- label: "GPT-4o Mini (Default)",
13
  providers: ["openai-compatible"],
14
  autoProvider: "openai-compatible",
15
- isThinker: false,
 
 
 
 
 
 
 
16
  },
17
  ];
 
8
 
9
  export const MODELS = [
10
  {
11
+ value: "ernie-x1.1-preview",
12
+ label: "ERNIE X1.1 Preview",
13
  providers: ["openai-compatible"],
14
  autoProvider: "openai-compatible",
15
+ isThinker: true,
16
+ },
17
+ {
18
+ value: "ernie-4.5-21b-a3b-thinking",
19
+ label: "ERNIE-4.5-21B-A3B-Thinking",
20
+ providers: ["openai-compatible"],
21
+ autoProvider: "openai-compatible",
22
+ isThinker: true,
23
  },
24
  ];