Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
573f07ec
Unverified
Commit
573f07ec
authored
Mar 18, 2023
by
boojack
Committed by
GitHub
Mar 18, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: support messages to ask AI (#1380)
parent
8b20cb9f
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
99 additions
and
108 deletions
+99
-108
openai.go
api/openai.go
+0
-5
chat_completion.go
plugin/openai/chat_completion.go
+7
-3
openai.go
server/openai.go
+5
-41
package.json
web/package.json
+2
-1
AskAIDialog.tsx
web/src/components/AskAIDialog.tsx
+49
-47
api.ts
web/src/helpers/api.ts
+2
-10
message.ts
web/src/store/zustand/message.ts
+26
-0
yarn.lock
web/yarn.lock
+8
-1
No files found.
api/openai.go
deleted
100644 → 0
View file @
8b20cb9f
package
api
type
OpenAICompletionRequest
struct
{
Prompt
string
`json:"prompt"`
}
plugin/openai/chat_completion.go
View file @
573f07ec
...
@@ -24,7 +24,7 @@ type ChatCompletionResponse struct {
...
@@ -24,7 +24,7 @@ type ChatCompletionResponse struct {
Choices
[]
ChatCompletionChoice
`json:"choices"`
Choices
[]
ChatCompletionChoice
`json:"choices"`
}
}
func
PostChatCompletion
(
prompt
string
,
apiKey
string
,
apiHost
string
)
(
string
,
error
)
{
func
PostChatCompletion
(
messages
[]
ChatCompletionMessage
,
apiKey
string
,
apiHost
string
)
(
string
,
error
)
{
if
apiHost
==
""
{
if
apiHost
==
""
{
apiHost
=
"https://api.openai.com"
apiHost
=
"https://api.openai.com"
}
}
...
@@ -35,7 +35,11 @@ func PostChatCompletion(prompt string, apiKey string, apiHost string) (string, e
...
@@ -35,7 +35,11 @@ func PostChatCompletion(prompt string, apiKey string, apiHost string) (string, e
values
:=
map
[
string
]
interface
{}{
values
:=
map
[
string
]
interface
{}{
"model"
:
"gpt-3.5-turbo"
,
"model"
:
"gpt-3.5-turbo"
,
"messages"
:
[]
map
[
string
]
string
{{
"role"
:
"user"
,
"content"
:
prompt
}},
"messages"
:
messages
,
"max_tokens"
:
2000
,
"temperature"
:
0
,
"frequency_penalty"
:
0.0
,
"presence_penalty"
:
0.0
,
}
}
jsonValue
,
err
:=
json
.
Marshal
(
values
)
jsonValue
,
err
:=
json
.
Marshal
(
values
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
server/openai.go
View file @
573f07ec
...
@@ -31,15 +31,15 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
...
@@ -31,15 +31,15 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"OpenAI API key not set"
)
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"OpenAI API key not set"
)
}
}
completionRequest
:=
api
.
OpenAICompletionRequest
{}
messages
:=
[]
openai
.
ChatCompletionMessage
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
&
completionRequest
);
err
!=
nil
{
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
&
messages
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted post chat completion request"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted post chat completion request"
)
.
SetInternal
(
err
)
}
}
if
completionRequest
.
Prompt
==
""
{
if
len
(
messages
)
==
0
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"
Prompt is requir
ed"
)
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"
No messages provid
ed"
)
}
}
result
,
err
:=
openai
.
PostChatCompletion
(
completionRequest
.
Prompt
,
openAIConfig
.
Key
,
openAIConfig
.
Host
)
result
,
err
:=
openai
.
PostChatCompletion
(
messages
,
openAIConfig
.
Key
,
openAIConfig
.
Host
)
if
err
!=
nil
{
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to post chat completion"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to post chat completion"
)
.
SetInternal
(
err
)
}
}
...
@@ -47,42 +47,6 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
...
@@ -47,42 +47,6 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
result
))
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
result
))
})
})
g
.
POST
(
"/openai/text-completion"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
openAIConfigSetting
,
err
:=
s
.
Store
.
FindSystemSetting
(
ctx
,
&
api
.
SystemSettingFind
{
Name
:
api
.
SystemSettingOpenAIConfigName
,
})
if
err
!=
nil
&&
common
.
ErrorCode
(
err
)
!=
common
.
NotFound
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find openai key"
)
.
SetInternal
(
err
)
}
openAIConfig
:=
api
.
OpenAIConfig
{}
if
openAIConfigSetting
!=
nil
{
err
=
json
.
Unmarshal
([]
byte
(
openAIConfigSetting
.
Value
),
&
openAIConfig
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to unmarshal openai system setting value"
)
.
SetInternal
(
err
)
}
}
if
openAIConfig
.
Key
==
""
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"OpenAI API key not set"
)
}
textCompletion
:=
api
.
OpenAICompletionRequest
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
&
textCompletion
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted post text completion request"
)
.
SetInternal
(
err
)
}
if
textCompletion
.
Prompt
==
""
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Prompt is required"
)
}
result
,
err
:=
openai
.
PostTextCompletion
(
textCompletion
.
Prompt
,
openAIConfig
.
Key
,
openAIConfig
.
Host
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to post text completion"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
composeResponse
(
result
))
})
g
.
GET
(
"/openai/enabled"
,
func
(
c
echo
.
Context
)
error
{
g
.
GET
(
"/openai/enabled"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
ctx
:=
c
.
Request
()
.
Context
()
openAIConfigSetting
,
err
:=
s
.
Store
.
FindSystemSetting
(
ctx
,
&
api
.
SystemSettingFind
{
openAIConfigSetting
,
err
:=
s
.
Store
.
FindSystemSetting
(
ctx
,
&
api
.
SystemSettingFind
{
...
...
web/package.json
View file @
573f07ec
...
@@ -29,7 +29,8 @@
...
@@ -29,7 +29,8 @@
"react-router-dom"
:
"^6.8.2"
,
"react-router-dom"
:
"^6.8.2"
,
"react-use"
:
"^17.4.0"
,
"react-use"
:
"^17.4.0"
,
"semver"
:
"^7.3.8"
,
"semver"
:
"^7.3.8"
,
"tailwindcss"
:
"^3.2.4"
"tailwindcss"
:
"^3.2.4"
,
"zustand"
:
"^4.3.6"
},
},
"devDependencies"
:
{
"devDependencies"
:
{
"@types/lodash-es"
:
"^4.17.5"
,
"@types/lodash-es"
:
"^4.17.5"
,
...
...
web/src/components/AskAIDialog.tsx
View file @
573f07ec
...
@@ -4,24 +4,21 @@ import { toast } from "react-hot-toast";
...
@@ -4,24 +4,21 @@ import { toast } from "react-hot-toast";
import
*
as
api
from
"../helpers/api"
;
import
*
as
api
from
"../helpers/api"
;
import
useLoading
from
"../hooks/useLoading"
;
import
useLoading
from
"../hooks/useLoading"
;
import
{
marked
}
from
"../labs/marked"
;
import
{
marked
}
from
"../labs/marked"
;
import
{
useMessageStore
}
from
"../store/zustand/message"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
{
generateDialog
}
from
"./Dialog"
;
import
showSettingDialog
from
"./SettingDialog"
;
import
showSettingDialog
from
"./SettingDialog"
;
type
Props
=
DialogProps
;
type
Props
=
DialogProps
;
interface
History
{
question
:
string
;
answer
:
string
;
}
const
AskAIDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
AskAIDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
destroy
,
hide
}
=
props
;
const
{
destroy
,
hide
}
=
props
;
const
fetchingState
=
useLoading
(
false
);
const
fetchingState
=
useLoading
(
false
);
const
[
historyList
,
setHistoryList
]
=
useState
<
History
[]
>
([]
);
const
messageStore
=
useMessageStore
(
);
const
[
isEnabled
,
setIsEnabled
]
=
useState
<
boolean
>
(
true
);
const
[
isEnabled
,
setIsEnabled
]
=
useState
<
boolean
>
(
true
);
const
[
isInIME
,
setIsInIME
]
=
useState
(
false
);
const
[
isInIME
,
setIsInIME
]
=
useState
(
false
);
const
[
question
,
setQuestion
]
=
useState
<
string
>
(
""
);
const
[
question
,
setQuestion
]
=
useState
<
string
>
(
""
);
const
messageList
=
messageStore
.
messageList
;
useEffect
(()
=>
{
useEffect
(()
=>
{
api
.
checkOpenAIEnabled
().
then
(({
data
})
=>
{
api
.
checkOpenAIEnabled
().
then
(({
data
})
=>
{
...
@@ -47,10 +44,18 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
...
@@ -47,10 +44,18 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
};
};
const
handleSendQuestionButtonClick
=
async
()
=>
{
const
handleSendQuestionButtonClick
=
async
()
=>
{
if
(
!
question
)
{
return
;
}
fetchingState
.
setLoading
();
fetchingState
.
setLoading
();
setQuestion
(
""
);
setQuestion
(
""
);
messageStore
.
addMessage
({
role
:
"user"
,
content
:
question
,
});
try
{
try
{
await
askQuestion
(
question
);
await
fetchChatCompletion
(
);
}
catch
(
error
:
any
)
{
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
console
.
error
(
error
);
toast
.
error
(
error
.
response
.
data
.
error
);
toast
.
error
(
error
.
response
.
data
.
error
);
...
@@ -58,21 +63,15 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
...
@@ -58,21 +63,15 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
fetchingState
.
setFinish
();
fetchingState
.
setFinish
();
};
};
const
askQuestion
=
async
(
question
:
string
)
=>
{
const
fetchChatCompletion
=
async
()
=>
{
if
(
question
===
""
)
{
const
messageList
=
messageStore
.
getState
().
messageList
;
return
;
}
const
{
const
{
data
:
{
data
:
answer
},
data
:
{
data
:
answer
},
}
=
await
api
.
postChatCompletion
(
question
);
}
=
await
api
.
postChatCompletion
(
messageList
);
setHistoryList
([
messageStore
.
addMessage
({
{
role
:
"assistant"
,
question
,
content
:
answer
.
replace
(
/^
\n\n
/
,
""
),
answer
:
answer
.
replace
(
/^
\n\n
/
,
""
),
});
},
...
historyList
,
]);
};
};
return
(
return
(
...
@@ -87,7 +86,36 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
...
@@ -87,7 +86,36 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
</
button
>
</
button
>
</
div
>
</
div
>
<
div
className=
"dialog-content-container !w-112 max-w-full"
>
<
div
className=
"dialog-content-container !w-112 max-w-full"
>
<
div
className=
"w-full relative"
>
{
messageList
.
map
((
message
,
index
)
=>
(
<
div
key=
{
index
}
className=
"w-full flex flex-col justify-start items-start mt-4 space-y-2"
>
{
message
.
role
===
"user"
?
(
<
div
className=
"w-full flex flex-row justify-end items-start pl-6"
>
<
span
className=
"word-break shadow rounded-lg rounded-tr-none px-3 py-2 opacity-80 bg-gray-100 dark:bg-zinc-700"
>
{
message
.
content
}
</
span
>
</
div
>
)
:
(
<
div
className=
"w-full flex flex-row justify-start items-start pr-8 space-x-2"
>
<
Icon
.
Bot
className=
"mt-2 flex-shrink-0 mr-1 w-6 h-auto opacity-80"
/>
<
div
className=
"memo-content-wrapper !w-auto flex flex-col justify-start items-start shadow rounded-lg rounded-tl-none px-3 py-2 bg-gray-100 dark:bg-zinc-700"
>
<
div
className=
"memo-content-text"
>
{
marked
(
message
.
content
)
}
</
div
>
</
div
>
</
div
>
)
}
</
div
>
))
}
{
fetchingState
.
isLoading
&&
(
<
p
className=
"w-full py-2 mt-4 flex flex-row justify-center items-center"
>
<
Icon
.
Loader
className=
"w-5 h-auto animate-spin"
/>
</
p
>
)
}
{
!
isEnabled
&&
(
<
div
className=
"w-full flex flex-col justify-center items-center mt-4 space-y-2"
>
<
p
>
You have not set up your OpenAI API key.
</
p
>
<
Button
onClick=
{
()
=>
handleGotoSystemSetting
()
}
>
Go to settings
</
Button
>
</
div
>
)
}
<
div
className=
"w-full relative mt-4"
>
<
Textarea
<
Textarea
className=
"w-full"
className=
"w-full"
placeholder=
"Ask anything…"
placeholder=
"Ask anything…"
...
@@ -104,32 +132,6 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
...
@@ -104,32 +132,6 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
onClick=
{
handleSendQuestionButtonClick
}
onClick=
{
handleSendQuestionButtonClick
}
/>
/>
</
div
>
</
div
>
{
fetchingState
.
isLoading
&&
(
<
p
className=
"w-full py-2 mt-4 flex flex-row justify-center items-center"
>
<
Icon
.
Loader
className=
"w-5 h-auto animate-spin"
/>
</
p
>
)
}
{
historyList
.
map
((
history
,
index
)
=>
(
<
div
key=
{
index
}
className=
"w-full flex flex-col justify-start items-start mt-4 space-y-2"
>
<
div
className=
"w-full flex flex-row justify-start items-start pr-6"
>
<
span
className=
"word-break rounded-lg rounded-tl-none px-3 py-2 opacity-80 bg-gray-100 dark:bg-zinc-700"
>
{
history
.
question
}
</
span
>
</
div
>
<
div
className=
"w-full flex flex-row justify-end items-start pl-8 space-x-2"
>
<
div
className=
"memo-content-wrapper !w-auto flex flex-col justify-start items-start rounded-lg rounded-tr-none px-3 py-2 bg-gray-100 dark:bg-zinc-700"
>
<
div
className=
"memo-content-text"
>
{
marked
(
history
.
answer
)
}
</
div
>
</
div
>
<
Icon
.
Bot
className=
"mt-2 flex-shrink-0 mr-1 w-6 h-auto opacity-80"
/>
</
div
>
</
div
>
))
}
{
!
isEnabled
&&
(
<
div
className=
"w-full flex flex-col justify-center items-center mt-4 space-y-2"
>
<
p
>
You have not set up your OpenAI API key.
</
p
>
<
Button
onClick=
{
()
=>
handleGotoSystemSetting
()
}
>
Go to settings
</
Button
>
</
div
>
)
}
</
div
>
</
div
>
</>
</>
);
);
...
...
web/src/helpers/api.ts
View file @
573f07ec
...
@@ -250,16 +250,8 @@ export function deleteIdentityProvider(id: IdentityProviderId) {
...
@@ -250,16 +250,8 @@ export function deleteIdentityProvider(id: IdentityProviderId) {
return
axios
.
delete
(
`/api/idp/
${
id
}
`
);
return
axios
.
delete
(
`/api/idp/
${
id
}
`
);
}
}
export
function
postChatCompletion
(
prompt
:
string
)
{
export
function
postChatCompletion
(
messages
:
any
[])
{
return
axios
.
post
<
ResponseObject
<
string
>>
(
`/api/openai/chat-completion`
,
{
return
axios
.
post
<
ResponseObject
<
string
>>
(
`/api/openai/chat-completion`
,
messages
);
prompt
,
});
}
export
function
postTextCompletion
(
prompt
:
string
)
{
return
axios
.
post
<
ResponseObject
<
string
>>
(
`/api/openai/text-completion`
,
{
prompt
,
});
}
}
export
function
checkOpenAIEnabled
()
{
export
function
checkOpenAIEnabled
()
{
...
...
web/src/store/zustand/message.ts
0 → 100644
View file @
573f07ec
import
{
create
}
from
"zustand"
;
import
{
persist
}
from
"zustand/middleware"
;
export
interface
Message
{
role
:
"user"
|
"assistant"
;
content
:
string
;
}
interface
MessageState
{
messageList
:
Message
[];
getState
:
()
=>
MessageState
;
addMessage
:
(
message
:
Message
)
=>
void
;
}
export
const
useMessageStore
=
create
<
MessageState
>
()(
persist
(
(
set
,
get
)
=>
({
messageList
:
[],
getState
:
()
=>
get
(),
addMessage
:
(
message
:
Message
)
=>
set
((
state
)
=>
({
messageList
:
[...
state
.
messageList
,
message
]
})),
}),
{
name
:
"message-storage"
,
}
)
);
web/yarn.lock
View file @
573f07ec
...
@@ -3058,7 +3058,7 @@ uri-js@^4.2.2:
...
@@ -3058,7 +3058,7 @@ uri-js@^4.2.2:
dependencies:
dependencies:
punycode "^2.1.0"
punycode "^2.1.0"
use-sync-external-store@^1.0.0:
use-sync-external-store@
1.2.0, use-sync-external-store@
^1.0.0:
version "1.2.0"
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
...
@@ -3144,3 +3144,10 @@ yocto-queue@^0.1.0:
...
@@ -3144,3 +3144,10 @@ yocto-queue@^0.1.0:
version "0.1.0"
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zustand@^4.3.6:
version "4.3.6"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.6.tgz#ce7804eb75361af0461a2d0536b65461ec5de86f"
integrity sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==
dependencies:
use-sync-external-store "1.2.0"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment