CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/next/components/openai/chatgpt-help.tsx
Views: 687
1
import { Alert, Button, Col, Input, Row } from "antd";
2
import { useRouter } from "next/router";
3
import { CSSProperties, useRef, useState } from "react";
4
5
import OpenAIAvatar from "@cocalc/frontend/components/openai-avatar";
6
import ProgressEstimate from "@cocalc/frontend/components/progress-estimate";
7
import Markdown from "@cocalc/frontend/editors/slate/static-markdown";
8
import { FileContext } from "@cocalc/frontend/lib/file-context";
9
import InPlaceSignInOrUp from "components/auth/in-place-sign-in-or-up";
10
import A from "components/misc/A";
11
import Loading from "components/share/loading";
12
import apiPost from "lib/api/post";
13
import { useCustomize } from "lib/customize";
14
15
type State = "input" | "wait";
16
17
const PROMPT = [
18
"ASSUME I HAVE FULL ACCESS TO COCALC.", // otherwise it says things like "as a large language model I don't have access to cocalc."
19
"ENCLOSE MATH IN $.", // so math gets typeset nicely
20
"INCLUDE THE LANGUAGE DIRECTLY AFTER THE TRIPLE BACKTICKS IN ALL MARKDOWN CODE BLOCKS.", // otherwise often we can't evaluate code.
21
"BE BRIEF.", // since it's slow.
22
"How can I do the following using CoCalc?", // give the context of how the question the user asks should be answered.
23
].join(" ");
24
25
export default function ChatGPTHelp({
26
style,
27
prompt,
28
size,
29
placeholder,
30
tag = "",
31
}: {
32
style?: CSSProperties;
33
prompt?: string;
34
size?;
35
placeholder?: string;
36
tag?: string;
37
}) {
38
const [state, setState] = useState<State>("input");
39
const [focus, setFocus] = useState<boolean>(false);
40
const [output, setOutput] = useState<string | null>(null);
41
const [input, setInput] = useState<string>("");
42
const [error, setError] = useState<string>("");
43
const router = useRouter();
44
45
const counterRef = useRef<number>(0);
46
const { account, jupyterApiEnabled, siteName } = useCustomize();
47
48
const chatgpt = async (value?) => {
49
if (value == null) {
50
value = input;
51
}
52
if (!value.trim()) return;
53
const system = `${PROMPT} ${prompt ?? ""}`;
54
const counter = counterRef.current + 1;
55
try {
56
counterRef.current += 1;
57
setInput(value);
58
setState("wait");
59
let output;
60
try {
61
({ output } = await apiPost("/llm/evaluate", {
62
input: value,
63
system,
64
tag: `next:${tag}`,
65
}));
66
} catch (err) {
67
if (counterRef.current != counter) return;
68
setError(`${err}`);
69
return;
70
}
71
if (counterRef.current != counter) return;
72
setOutput(output);
73
} finally {
74
if (counterRef.current != counter) return;
75
setState("input");
76
}
77
};
78
79
function renderAlertErrorDescription() {
80
return (
81
<>
82
{error}
83
<hr />
84
OpenAI <A href="https://status.openai.com/">status</A> and{" "}
85
<A href="https://downdetector.com/status/openai/">downdetector</A>.
86
</>
87
);
88
}
89
90
return (
91
<FileContext.Provider value={{ jupyterApiEnabled }}>
92
<Row style={{ margin: "5px 0", ...style }}>
93
<Col
94
xs={{ span: 24 }}
95
md={{ span: 17 }}
96
style={{ marginBottom: "5px" }}
97
>
98
<Input.TextArea
99
value={input}
100
maxLength={2000}
101
onChange={(e) => setInput(e.target.value)}
102
size={size}
103
autoSize={{ minRows: focus ? 2 : 1, maxRows: 5 }}
104
disabled={state == "wait" || account?.account_id == null}
105
onFocus={() => setFocus(true)}
106
onBlur={() => setFocus(false)}
107
placeholder={
108
placeholder ?? `Ask ChatGPT: how can I do this on ${siteName}?`
109
}
110
allowClear
111
onPressEnter={(e) => {
112
if (e.shiftKey) {
113
chatgpt();
114
}
115
}}
116
/>
117
{account?.account_id == null && (
118
<InPlaceSignInOrUp
119
title="ChatGPT"
120
why="to use ChatGPT"
121
onSuccess={() => {
122
router.reload();
123
}}
124
/>
125
)}
126
</Col>
127
<Col
128
xs={{ span: 24, offset: 0 }}
129
md={{ span: 6, offset: 1 }}
130
style={{
131
marginBottom: "5px",
132
display: "flex",
133
justifyContent: "center",
134
}}
135
>
136
<Button
137
disabled={account?.account_id == null}
138
size={size}
139
type="primary"
140
onClick={() => {
141
if (input?.trim()) {
142
chatgpt();
143
}
144
}}
145
>
146
<OpenAIAvatar
147
size={size == "large" ? 24 : 18}
148
backgroundColor="transparent"
149
style={{ marginRight: "5px", marginTop: "-4px" }}
150
/>
151
{input?.trim() && focus
152
? "Shift+Enter"
153
: size == "large"
154
? "Ask ChatGPT"
155
: "ChatGPT"}
156
</Button>
157
</Col>
158
<Col xs={{ span: 24 }} md={{ span: 24 }}>
159
{error && (
160
<Alert
161
style={{ margin: "15px 0" }}
162
type="error"
163
message="Error"
164
showIcon
165
closable
166
banner
167
onClose={() => setError("")}
168
description={renderAlertErrorDescription()}
169
/>
170
)}
171
{state == "wait" && (
172
<div style={{ textAlign: "center", margin: "15px 0" }}>
173
<OpenAIAvatar size={18} /> ChatGPT is figuring out how to do this
174
using {siteName}...{" "}
175
<Button
176
style={{ float: "right" }}
177
onClick={() => {
178
counterRef.current += 1; // so result of outstanding request is totally ignored
179
setState("input");
180
}}
181
>
182
<Loading delay={0}>Cancel...</Loading>
183
</Button>
184
<ProgressEstimate seconds={30} />
185
</div>
186
)}
187
{output != null && (
188
<Alert
189
type="success"
190
closable
191
banner
192
onClose={() => setOutput("")}
193
style={{ margin: "15px 0" }}
194
description={
195
<div>
196
<Markdown value={output} />
197
</div>
198
}
199
/>
200
)}
201
</Col>
202
</Row>
203
</FileContext.Provider>
204
);
205
}
206
207