mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 13:00:14 +09:00
update quota
This commit is contained in:
parent
806cf3a048
commit
8958afca68
@ -14,6 +14,7 @@ import (
|
||||
|
||||
type AnonymousRequestBody struct {
|
||||
Message string `json:"message" required:"true"`
|
||||
Web bool `json:"web"`
|
||||
}
|
||||
|
||||
type AnonymousResponseCache struct {
|
||||
@ -57,18 +58,22 @@ func TestKey(key string) bool {
|
||||
return res.(map[string]interface{})["choices"] != nil
|
||||
}
|
||||
|
||||
func GetAnonymousResponse(message string) (string, string, error) {
|
||||
func GetAnonymousResponse(message string, web bool) (string, string, error) {
|
||||
if !web {
|
||||
resp, err := GetChatGPTResponse([]types.ChatGPTMessage{{Role: "user", Content: message}}, 1000)
|
||||
return "", resp, err
|
||||
}
|
||||
keyword, source := ChatWithWeb([]types.ChatGPTMessage{{Role: "user", Content: message}}, false)
|
||||
resp, err := GetChatGPTResponse(source, 1000)
|
||||
return keyword, resp, err
|
||||
}
|
||||
|
||||
func GetAnonymousResponseWithCache(c *gin.Context, message string) (string, string, error) {
|
||||
func GetAnonymousResponseWithCache(c *gin.Context, message string, web bool) (string, string, error) {
|
||||
cache := c.MustGet("cache").(*redis.Client)
|
||||
res, err := cache.Get(c, fmt.Sprintf(":chatgpt:%s", message)).Result()
|
||||
res, err := cache.Get(c, fmt.Sprintf(":chatgpt-%v:%s", web, message)).Result()
|
||||
form := utils.UnmarshalJson[AnonymousResponseCache](res)
|
||||
if err != nil || len(res) == 0 || res == "{}" || form.Message == "" {
|
||||
key, res, err := GetAnonymousResponse(message)
|
||||
key, res, err := GetAnonymousResponse(message, web)
|
||||
if err != nil {
|
||||
return "", "There was something wrong...", err
|
||||
}
|
||||
@ -76,7 +81,7 @@ func GetAnonymousResponseWithCache(c *gin.Context, message string) (string, stri
|
||||
cache.Set(c, fmt.Sprintf(":chatgpt:%s", message), utils.ToJson(AnonymousResponseCache{
|
||||
Keyword: key,
|
||||
Message: res,
|
||||
}), time.Hour*6)
|
||||
}), time.Hour*48)
|
||||
return key, res, nil
|
||||
}
|
||||
return form.Keyword, form.Message, nil
|
||||
@ -103,7 +108,7 @@ func AnonymousAPI(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
key, res, err := GetAnonymousResponseWithCache(c, message)
|
||||
key, res, err := GetAnonymousResponseWithCache(c, message, body.Web)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": false,
|
||||
|
73
api/buffer.go
Normal file
73
api/buffer.go
Normal file
@ -0,0 +1,73 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"chat/auth"
|
||||
"chat/types"
|
||||
"chat/utils"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
Enable bool `json:"enable"`
|
||||
Quota float32 `json:"quota"`
|
||||
Data string `json:"data"`
|
||||
Cursor int `json:"cursor"`
|
||||
Times int `json:"times"`
|
||||
}
|
||||
|
||||
func NewBuffer(enable bool, history []types.ChatGPTMessage) *Buffer {
|
||||
buffer := &Buffer{Data: "", Cursor: 0, Times: 0, Enable: enable}
|
||||
if enable {
|
||||
buffer.Quota = auth.CountInputToken(utils.CountTokenPrice(history))
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
func (b *Buffer) GetCursor() int {
|
||||
return b.Cursor
|
||||
}
|
||||
|
||||
func (b *Buffer) GetQuota() float32 {
|
||||
if !b.Enable {
|
||||
return 0.
|
||||
}
|
||||
return b.Quota + auth.CountOutputToken(b.ReadTimes())
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(data string) string {
|
||||
b.Data += data
|
||||
b.Cursor += len(data)
|
||||
b.Times++
|
||||
return data
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteBytes(data []byte) []byte {
|
||||
b.Data += string(data)
|
||||
b.Cursor += len(data)
|
||||
b.Times++
|
||||
return data
|
||||
}
|
||||
|
||||
func (b *Buffer) IsEmpty() bool {
|
||||
return b.Cursor == 0
|
||||
}
|
||||
|
||||
func (b *Buffer) Reset() {
|
||||
b.Data = ""
|
||||
b.Cursor = 0
|
||||
b.Times = 0
|
||||
}
|
||||
|
||||
func (b *Buffer) Read() string {
|
||||
return b.Data
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadWithDefault(_default string) string {
|
||||
if b.IsEmpty() {
|
||||
return _default
|
||||
}
|
||||
return b.Data
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadTimes() int {
|
||||
return b.Times
|
||||
}
|
34
api/chat.go
34
api/chat.go
@ -7,7 +7,6 @@ import (
|
||||
"chat/types"
|
||||
"chat/utils"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/gorilla/websocket"
|
||||
@ -15,6 +14,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultMessage = "There was something wrong... Please try again later."
|
||||
|
||||
type WebsocketAuthForm struct {
|
||||
Token string `json:"token" binding:"required"`
|
||||
Id int64 `json:"id" binding:"required"`
|
||||
@ -25,39 +26,47 @@ func SendSegmentMessage(conn *websocket.Conn, message types.ChatGPTSegmentRespon
|
||||
}
|
||||
|
||||
func TextChat(db *sql.DB, user *auth.User, conn *websocket.Conn, instance *conversation.Conversation) string {
|
||||
keyword, segment := ChatWithWeb(conversation.CopyMessage(instance.GetMessageSegment(12)), true)
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{Keyword: keyword, End: false})
|
||||
var keyword string
|
||||
var segment []types.ChatGPTMessage
|
||||
|
||||
msg := ""
|
||||
if instance.IsEnableWeb() {
|
||||
keyword, segment = ChatWithWeb(conversation.CopyMessage(instance.GetMessageSegment(12)), true)
|
||||
} else {
|
||||
segment = conversation.CopyMessage(instance.GetMessageSegment(12))
|
||||
}
|
||||
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{Keyword: keyword, End: false})
|
||||
|
||||
if instance.IsEnableGPT4() && !auth.ReduceGPT4(db, user) {
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{
|
||||
Message: "You have run out of GPT-4 usage. Please buy more.",
|
||||
Quota: 0,
|
||||
End: true,
|
||||
})
|
||||
return "You have run out of GPT-4 usage. Please buy more."
|
||||
}
|
||||
|
||||
buffer := NewBuffer(instance.IsEnableGPT4(), segment)
|
||||
StreamRequest(instance.IsEnableGPT4(), segment, 2000, func(resp string) {
|
||||
msg += resp
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{
|
||||
Message: resp,
|
||||
Message: buffer.Write(resp),
|
||||
Quota: buffer.GetQuota(),
|
||||
End: false,
|
||||
})
|
||||
})
|
||||
if msg == "" {
|
||||
msg = "There was something wrong... Please try again later."
|
||||
if buffer.IsEmpty() {
|
||||
if instance.IsEnableGPT4() {
|
||||
auth.IncreaseGPT4(db, user, 1)
|
||||
}
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{
|
||||
Message: msg,
|
||||
Message: defaultMessage,
|
||||
Quota: buffer.GetQuota(),
|
||||
End: false,
|
||||
})
|
||||
}
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{End: true})
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{End: true, Quota: buffer.GetQuota()})
|
||||
|
||||
return msg
|
||||
return buffer.ReadWithDefault(defaultMessage)
|
||||
}
|
||||
|
||||
func ImageChat(conn *websocket.Conn, instance *conversation.Conversation, user *auth.User, db *sql.DB, cache *redis.Client) string {
|
||||
@ -84,10 +93,9 @@ func ImageChat(conn *websocket.Conn, instance *conversation.Conversation, user *
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
markdown := fmt.Sprintln("")
|
||||
markdown := GetImageMarkdown(url)
|
||||
SendSegmentMessage(conn, types.ChatGPTSegmentResponse{
|
||||
Message: markdown,
|
||||
Keyword: "image",
|
||||
End: true,
|
||||
})
|
||||
return markdown
|
||||
|
@ -70,3 +70,7 @@ func GetImageWithUserLimit(user *auth.User, prompt string, db *sql.DB, cache *re
|
||||
return GetImageWithCache(context.Background(), prompt, cache)
|
||||
}
|
||||
}
|
||||
|
||||
func GetImageMarkdown(url string) string {
|
||||
return fmt.Sprintln("")
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "./components/ui/dropdown-menu.tsx";
|
||||
import { Toaster } from "./components/ui/toaster.tsx";
|
||||
import { login } from "./conf.ts";
|
||||
import {login, tokenField} from "./conf.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function Settings() {
|
||||
@ -67,7 +67,7 @@ function NavBar() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
useEffect(() => {
|
||||
validateToken(dispatch, localStorage.getItem("token") ?? "");
|
||||
validateToken(dispatch, localStorage.getItem(tokenField) ?? "");
|
||||
}, []);
|
||||
const auth = useSelector(selectAuthenticated);
|
||||
|
||||
|
@ -13,8 +13,8 @@
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
&:last-child {
|
||||
animation: FlexInAnimationFromBottom 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s 1 normal forwards running;
|
||||
@ -29,6 +29,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
.message-quota {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
border: 1px solid hsl(var(--input));
|
||||
border-radius: var(--radius);
|
||||
transition: 0.2s linear;
|
||||
padding: 4px 8px;
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
white-space: nowrap;
|
||||
|
||||
.quota {
|
||||
font-size: 14px;
|
||||
color: hsl(var(--text-secondary));
|
||||
}
|
||||
|
||||
.icon {
|
||||
transform: translateY(1px);
|
||||
color: hsl(var(--text-secondary));
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: hsl(var(--border-hover));
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius);
|
||||
|
@ -64,3 +64,9 @@ strong {
|
||||
color: hsl(var(--text));
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.icon-tooltip {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Message } from "../conversation/types.ts";
|
||||
import Markdown from "./Markdown.tsx";
|
||||
import {Copy, File, Loader2, MousePointerSquare} from "lucide-react";
|
||||
import {Cloud, CloudFog, Copy, File, Loader2, MousePointerSquare} from "lucide-react";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@ -9,6 +9,7 @@ import {
|
||||
} from "./ui/context-menu.tsx";
|
||||
import {copyClipboard, saveAsFile, useInputValue} from "../utils.ts";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "./ui/tooltip.tsx";
|
||||
|
||||
type MessageProps = {
|
||||
message: Message;
|
||||
@ -22,6 +23,24 @@ function MessageSegment({ message }: MessageProps) {
|
||||
<ContextMenuTrigger asChild>
|
||||
<div className={`message ${message.role}`}>
|
||||
<MessageContent message={message} />
|
||||
{
|
||||
(message.quota && message.quota > 0) ?
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className={`message-quota`}>
|
||||
<Cloud className={`h-4 w-4 icon`} />
|
||||
<span className={`quota`}>{message.quota.toFixed(2)}</span>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className={`icon-tooltip`}>
|
||||
<CloudFog className={`h-4 w-4 mr-2`} />
|
||||
<p>{ t('quota-description') }</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
@ -41,79 +60,81 @@ function MessageSegment({ message }: MessageProps) {
|
||||
|
||||
function MessageContent({ message }: MessageProps) {
|
||||
return (
|
||||
<div className={`message-content`}>
|
||||
{message.keyword && message.keyword.length ? (
|
||||
<div className={`bing`}>
|
||||
<svg
|
||||
width="256px"
|
||||
height="388px"
|
||||
viewBox="0 0 256 388"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
>
|
||||
<title>bing</title>
|
||||
<defs>
|
||||
<radialGradient
|
||||
cx="93.7173476%"
|
||||
cy="77.8181394%"
|
||||
fx="93.7173476%"
|
||||
fy="77.8181394%"
|
||||
r="143.12136%"
|
||||
gradientTransform="translate(0.937173,0.778181),scale(1.000000,0.719543),rotate(-130.909000),translate(-0.937173,-0.778181)"
|
||||
id="bingRadialGradient-1"
|
||||
>
|
||||
<stop stopColor="#00CACC" offset="0%"></stop>
|
||||
<stop stopColor="#048FCE" offset="100%"></stop>
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
cx="13.8926614%"
|
||||
cy="71.4480037%"
|
||||
fx="13.8926614%"
|
||||
fy="71.4480037%"
|
||||
r="150.086127%"
|
||||
gradientTransform="translate(0.138927,0.714480),scale(0.600049,1.000000),rotate(-23.195400),translate(-0.138927,-0.714480)"
|
||||
id="bingRadialGradient-2"
|
||||
>
|
||||
<stop stopColor="#00BBEC" offset="0%"></stop>
|
||||
<stop stopColor="#2756A9" offset="100%"></stop>
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
x1="50.0002111%"
|
||||
y1="-3.51563173e-08%"
|
||||
x2="50.0002111%"
|
||||
y2="99.9999984%"
|
||||
id="bingLinearGradient-3"
|
||||
>
|
||||
<stop stopColor="#00BBEC" offset="0%"></stop>
|
||||
<stop stopColor="#2756A9" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path
|
||||
d="M129.424369,122.046918 C122.29107,122.875748 116.850956,128.668861 116.345268,135.974502 C116.127195,139.122391 116.195602,139.335928 123.330791,157.696785 C139.564206,199.470843 143.497083,209.524887 144.158483,210.939907 C145.760962,214.366717 148.01426,217.590572 150.829558,220.484105 C152.989881,222.704521 154.414727,223.898444 156.824493,225.508104 C161.059724,228.33663 163.161466,229.118595 179.641677,233.998219 C195.695191,238.75161 204.46574,241.910837 212.02347,245.661923 C221.814465,250.521516 228.645788,256.049313 232.966812,262.608139 C236.06708,267.314287 238.812837,275.75338 240.007515,284.248408 C240.474653,287.569017 240.477677,294.909429 240.013185,297.911432 C239.00521,304.426794 236.991907,309.886561 233.912048,314.455516 C232.274042,316.885312 232.843981,316.478646 235.225401,313.517461 C241.964883,305.138083 248.830221,290.816683 252.333376,277.8298 C256.572764,262.112277 257.149506,245.234469 253.992924,229.259946 C247.845679,198.151822 228.207374,171.305385 200.548737,156.199752 C198.810955,155.250359 192.191658,151.780841 183.217776,147.115133 C181.856046,146.406867 179.999212,145.437443 179.091392,144.960857 C178.183573,144.483892 176.326738,143.514468 174.965009,142.806581 C173.603279,142.098693 169.683253,140.056288 166.253797,138.268239 C162.82434,136.479812 158.987083,134.478981 157.726265,133.821738 C153.882961,131.817883 151.304255,130.471649 149.381658,129.465187 C140.489411,124.810061 136.725475,122.928282 135.652494,122.601739 C134.52698,122.259322 131.66784,121.819775 130.950504,121.878734 C130.799326,121.891206 130.112604,121.966794 129.424369,122.046918 Z"
|
||||
fill="url(#bingRadialGradient-1)"
|
||||
></path>
|
||||
<path
|
||||
d="M148.810208,277.993827 C148.31737,278.285599 147.625734,278.70814 147.272735,278.93226 C146.919358,279.156758 146.135504,279.643927 145.530417,280.015445 C143.309245,281.378686 137.401993,285.018657 132.325839,288.152183 C128.989734,290.211596 128.494629,290.518486 124.256752,293.148592 C122.743468,294.087403 121.134186,295.076858 120.680276,295.347465 C120.226366,295.618073 118.28714,296.812373 116.37059,298.001382 C114.45404,299.190013 111.111889,301.253583 108.943251,302.586589 C106.774613,303.919216 102.895782,306.311974 100.323879,307.903493 C97.751598,309.494634 94.3678729,311.581258 92.8047,312.5401 C91.2411491,313.498564 89.7970283,314.424524 89.5952066,314.597622 C89.2954977,314.854624 75.3902128,323.468326 68.413004,327.719053 C63.1138629,330.947066 56.9836248,333.106255 50.7082565,333.95436 C47.7867558,334.348932 42.2582032,334.350444 39.3450173,333.956627 C31.445665,332.890072 24.1685583,329.944005 17.9362755,325.290768 C15.4916258,323.465303 10.8891851,318.866491 9.12970847,316.49074 C4.9833696,310.893024 2.30102759,304.888641 0.91181812,298.094734 C0.59216513,296.531183 0.289753151,295.211028 0.240053625,295.160383 C0.110210494,295.029237 0.344758621,297.391004 0.76784823,300.4788 C1.20781187,303.690183 2.14533768,308.335104 3.15510733,312.307665 C10.9691579,343.051452 33.2044235,368.056927 63.3054801,379.953822 C71.9732286,383.377987 80.7199673,385.536043 90.2369541,386.594284 C93.8130523,386.994903 103.935504,387.153639 107.667693,386.870182 C124.783605,385.573837 139.68666,380.535855 154.975217,370.873738 C156.336946,370.013161 158.895243,368.4001 160.660238,367.288569 C162.42561,366.177416 164.653585,364.764664 165.612049,364.149751 C166.570135,363.534459 167.725507,362.807675 168.179417,362.5348 C168.633327,362.261547 169.541146,361.69123 170.196878,361.2668 C170.852609,360.842748 173.658459,359.067927 176.432184,357.322963 L187.52406,350.317031 L191.332593,347.911423 L191.469787,347.824874 L191.889304,347.559936 L192.088858,347.433703 L194.892818,345.662661 L204.583281,339.541871 C216.930684,331.783076 220.612606,329.05924 226.349027,323.439981 C228.740652,321.097867 232.34623,317.099228 232.525375,316.591651 C232.561657,316.488472 233.202649,315.499773 233.949465,314.394667 C236.985104,309.902812 239.009368,304.400338 240.013185,297.911432 C240.477677,294.909429 240.474653,287.569017 240.007515,284.248408 C239.104609,277.828288 237.053134,270.546079 234.84141,265.909094 C231.21429,258.305634 223.48762,251.396833 212.387807,245.832753 C209.323066,244.296414 206.15817,242.890466 205.804793,242.908804 C205.637364,242.917678 195.308177,249.231218 182.851171,256.939747 C170.394164,264.648276 159.582722,271.339004 158.826458,271.808409 C158.069815,272.278192 156.770069,273.072251 155.937838,273.572648 L148.810208,277.993827 Z"
|
||||
fill="url(#bingRadialGradient-2)"
|
||||
></path>
|
||||
<path
|
||||
d="M0.0533481895,241.013222 L0.106718678,294.701938 L0.801609893,297.819592 C2.97446184,307.567124 6.73918016,314.594977 13.2839464,321.122433 C16.3624068,324.192844 18.7164636,326.044009 22.0524167,328.018006 C29.1114124,332.195412 36.7087125,334.256336 45.0304163,334.252562 C53.7461636,334.248021 61.2857518,332.074092 69.0551294,327.325236 C70.3662143,326.523997 75.5035957,323.360991 80.4712807,320.296249 L89.5033664,314.724233 L89.5033664,251.024559 L89.5033664,187.324884 L89.5007208,129.051339 C89.4988311,91.8738258 89.4308013,69.7947641 89.313261,68.0626506 C88.5724924,57.1775095 84.020167,47.1707083 76.3653061,39.6016406 C74.0160114,37.2786885 72.0087553,35.7271562 66.0289385,31.6121866 C63.053392,29.5643772 57.6064751,25.8130645 53.9249307,23.2760076 C50.2433864,20.7387618 44.1777765,16.5590127 40.4455878,13.9874496 C36.7134746,11.4160755 31.3904475,7.74730564 28.6166092,5.83487543 C22.8380472,1.85062504 22.3858004,1.57350956 20.6389849,0.948501166 C18.366904,0.13569962 15.9591415,-0.162861118 13.6700153,0.0843541512 C6.99810902,0.80490922 1.65713716,5.62213122 0.268845335,12.1713194 C0.0528069748,13.1902541 0.013004155,26.7505903 0.0102532201,100.349957 L0.00714146286,187.324884 L0,187.324884 L0.0533481895,241.013222 Z"
|
||||
fill="url(#bingLinearGradient-3)"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
<span className={`keyword`}>{message.keyword}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{message.content.length ? (
|
||||
<Markdown children={message.content} />
|
||||
) : (
|
||||
<Loader2 className={`h-5 w-5 m-1 animate-spin`} />
|
||||
)}
|
||||
</div>
|
||||
<>
|
||||
<div className={`message-content`}>
|
||||
{message.keyword && message.keyword.length ? (
|
||||
<div className={`bing`}>
|
||||
<svg
|
||||
width="256px"
|
||||
height="388px"
|
||||
viewBox="0 0 256 388"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
>
|
||||
<title>bing</title>
|
||||
<defs>
|
||||
<radialGradient
|
||||
cx="93.7173476%"
|
||||
cy="77.8181394%"
|
||||
fx="93.7173476%"
|
||||
fy="77.8181394%"
|
||||
r="143.12136%"
|
||||
gradientTransform="translate(0.937173,0.778181),scale(1.000000,0.719543),rotate(-130.909000),translate(-0.937173,-0.778181)"
|
||||
id="bingRadialGradient-1"
|
||||
>
|
||||
<stop stopColor="#00CACC" offset="0%"></stop>
|
||||
<stop stopColor="#048FCE" offset="100%"></stop>
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
cx="13.8926614%"
|
||||
cy="71.4480037%"
|
||||
fx="13.8926614%"
|
||||
fy="71.4480037%"
|
||||
r="150.086127%"
|
||||
gradientTransform="translate(0.138927,0.714480),scale(0.600049,1.000000),rotate(-23.195400),translate(-0.138927,-0.714480)"
|
||||
id="bingRadialGradient-2"
|
||||
>
|
||||
<stop stopColor="#00BBEC" offset="0%"></stop>
|
||||
<stop stopColor="#2756A9" offset="100%"></stop>
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
x1="50.0002111%"
|
||||
y1="-3.51563173e-08%"
|
||||
x2="50.0002111%"
|
||||
y2="99.9999984%"
|
||||
id="bingLinearGradient-3"
|
||||
>
|
||||
<stop stopColor="#00BBEC" offset="0%"></stop>
|
||||
<stop stopColor="#2756A9" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path
|
||||
d="M129.424369,122.046918 C122.29107,122.875748 116.850956,128.668861 116.345268,135.974502 C116.127195,139.122391 116.195602,139.335928 123.330791,157.696785 C139.564206,199.470843 143.497083,209.524887 144.158483,210.939907 C145.760962,214.366717 148.01426,217.590572 150.829558,220.484105 C152.989881,222.704521 154.414727,223.898444 156.824493,225.508104 C161.059724,228.33663 163.161466,229.118595 179.641677,233.998219 C195.695191,238.75161 204.46574,241.910837 212.02347,245.661923 C221.814465,250.521516 228.645788,256.049313 232.966812,262.608139 C236.06708,267.314287 238.812837,275.75338 240.007515,284.248408 C240.474653,287.569017 240.477677,294.909429 240.013185,297.911432 C239.00521,304.426794 236.991907,309.886561 233.912048,314.455516 C232.274042,316.885312 232.843981,316.478646 235.225401,313.517461 C241.964883,305.138083 248.830221,290.816683 252.333376,277.8298 C256.572764,262.112277 257.149506,245.234469 253.992924,229.259946 C247.845679,198.151822 228.207374,171.305385 200.548737,156.199752 C198.810955,155.250359 192.191658,151.780841 183.217776,147.115133 C181.856046,146.406867 179.999212,145.437443 179.091392,144.960857 C178.183573,144.483892 176.326738,143.514468 174.965009,142.806581 C173.603279,142.098693 169.683253,140.056288 166.253797,138.268239 C162.82434,136.479812 158.987083,134.478981 157.726265,133.821738 C153.882961,131.817883 151.304255,130.471649 149.381658,129.465187 C140.489411,124.810061 136.725475,122.928282 135.652494,122.601739 C134.52698,122.259322 131.66784,121.819775 130.950504,121.878734 C130.799326,121.891206 130.112604,121.966794 129.424369,122.046918 Z"
|
||||
fill="url(#bingRadialGradient-1)"
|
||||
></path>
|
||||
<path
|
||||
d="M148.810208,277.993827 C148.31737,278.285599 147.625734,278.70814 147.272735,278.93226 C146.919358,279.156758 146.135504,279.643927 145.530417,280.015445 C143.309245,281.378686 137.401993,285.018657 132.325839,288.152183 C128.989734,290.211596 128.494629,290.518486 124.256752,293.148592 C122.743468,294.087403 121.134186,295.076858 120.680276,295.347465 C120.226366,295.618073 118.28714,296.812373 116.37059,298.001382 C114.45404,299.190013 111.111889,301.253583 108.943251,302.586589 C106.774613,303.919216 102.895782,306.311974 100.323879,307.903493 C97.751598,309.494634 94.3678729,311.581258 92.8047,312.5401 C91.2411491,313.498564 89.7970283,314.424524 89.5952066,314.597622 C89.2954977,314.854624 75.3902128,323.468326 68.413004,327.719053 C63.1138629,330.947066 56.9836248,333.106255 50.7082565,333.95436 C47.7867558,334.348932 42.2582032,334.350444 39.3450173,333.956627 C31.445665,332.890072 24.1685583,329.944005 17.9362755,325.290768 C15.4916258,323.465303 10.8891851,318.866491 9.12970847,316.49074 C4.9833696,310.893024 2.30102759,304.888641 0.91181812,298.094734 C0.59216513,296.531183 0.289753151,295.211028 0.240053625,295.160383 C0.110210494,295.029237 0.344758621,297.391004 0.76784823,300.4788 C1.20781187,303.690183 2.14533768,308.335104 3.15510733,312.307665 C10.9691579,343.051452 33.2044235,368.056927 63.3054801,379.953822 C71.9732286,383.377987 80.7199673,385.536043 90.2369541,386.594284 C93.8130523,386.994903 103.935504,387.153639 107.667693,386.870182 C124.783605,385.573837 139.68666,380.535855 154.975217,370.873738 C156.336946,370.013161 158.895243,368.4001 160.660238,367.288569 C162.42561,366.177416 164.653585,364.764664 165.612049,364.149751 C166.570135,363.534459 167.725507,362.807675 168.179417,362.5348 C168.633327,362.261547 169.541146,361.69123 170.196878,361.2668 C170.852609,360.842748 173.658459,359.067927 176.432184,357.322963 L187.52406,350.317031 L191.332593,347.911423 L191.469787,347.824874 L191.889304,347.559936 L192.088858,347.433703 L194.892818,345.662661 L204.583281,339.541871 C216.930684,331.783076 220.612606,329.05924 226.349027,323.439981 C228.740652,321.097867 232.34623,317.099228 232.525375,316.591651 C232.561657,316.488472 233.202649,315.499773 233.949465,314.394667 C236.985104,309.902812 239.009368,304.400338 240.013185,297.911432 C240.477677,294.909429 240.474653,287.569017 240.007515,284.248408 C239.104609,277.828288 237.053134,270.546079 234.84141,265.909094 C231.21429,258.305634 223.48762,251.396833 212.387807,245.832753 C209.323066,244.296414 206.15817,242.890466 205.804793,242.908804 C205.637364,242.917678 195.308177,249.231218 182.851171,256.939747 C170.394164,264.648276 159.582722,271.339004 158.826458,271.808409 C158.069815,272.278192 156.770069,273.072251 155.937838,273.572648 L148.810208,277.993827 Z"
|
||||
fill="url(#bingRadialGradient-2)"
|
||||
></path>
|
||||
<path
|
||||
d="M0.0533481895,241.013222 L0.106718678,294.701938 L0.801609893,297.819592 C2.97446184,307.567124 6.73918016,314.594977 13.2839464,321.122433 C16.3624068,324.192844 18.7164636,326.044009 22.0524167,328.018006 C29.1114124,332.195412 36.7087125,334.256336 45.0304163,334.252562 C53.7461636,334.248021 61.2857518,332.074092 69.0551294,327.325236 C70.3662143,326.523997 75.5035957,323.360991 80.4712807,320.296249 L89.5033664,314.724233 L89.5033664,251.024559 L89.5033664,187.324884 L89.5007208,129.051339 C89.4988311,91.8738258 89.4308013,69.7947641 89.313261,68.0626506 C88.5724924,57.1775095 84.020167,47.1707083 76.3653061,39.6016406 C74.0160114,37.2786885 72.0087553,35.7271562 66.0289385,31.6121866 C63.053392,29.5643772 57.6064751,25.8130645 53.9249307,23.2760076 C50.2433864,20.7387618 44.1777765,16.5590127 40.4455878,13.9874496 C36.7134746,11.4160755 31.3904475,7.74730564 28.6166092,5.83487543 C22.8380472,1.85062504 22.3858004,1.57350956 20.6389849,0.948501166 C18.366904,0.13569962 15.9591415,-0.162861118 13.6700153,0.0843541512 C6.99810902,0.80490922 1.65713716,5.62213122 0.268845335,12.1713194 C0.0528069748,13.1902541 0.013004155,26.7505903 0.0102532201,100.349957 L0.00714146286,187.324884 L0,187.324884 L0.0533481895,241.013222 Z"
|
||||
fill="url(#bingLinearGradient-3)"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
<span className={`keyword`}>{message.keyword}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{message.content.length ? (
|
||||
<Markdown children={message.content} />
|
||||
) : (
|
||||
<Loader2 className={`h-5 w-5 m-1 animate-spin`} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,12 @@ export let rest_api: string = "http://localhost:8094";
|
||||
export let ws_api: string = "ws://localhost:8094";
|
||||
|
||||
if (deploy) {
|
||||
rest_api = "https://nioapi.fystart.cn";
|
||||
ws_api = "wss://nioapi.fystart.cn";
|
||||
rest_api = "https://api.chatnio.net";
|
||||
ws_api = "wss://api.chatnio.net";
|
||||
}
|
||||
|
||||
export const tokenField = deploy ? "token" : "token-dev";
|
||||
|
||||
export function login() {
|
||||
location.href = "https://deeptrain.lightxi.com/login?app=chatnio";
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { ws_api } from "../conf.ts";
|
||||
import {tokenField, ws_api} from "../conf.ts";
|
||||
|
||||
export const endpoint = `${ws_api}/chat`;
|
||||
|
||||
export type StreamMessage = {
|
||||
keyword?: string;
|
||||
quota?: number;
|
||||
message: string;
|
||||
end: boolean;
|
||||
};
|
||||
@ -35,7 +36,7 @@ export class Connection {
|
||||
this.connection.onopen = () => {
|
||||
this.state = true;
|
||||
this.send({
|
||||
token: localStorage.getItem("token") || "",
|
||||
token: localStorage.getItem(tokenField) || "",
|
||||
id: this.id,
|
||||
});
|
||||
};
|
||||
|
@ -79,9 +79,10 @@ export class Conversation {
|
||||
this.triggerCallback();
|
||||
}
|
||||
|
||||
public updateMessage(idx: number, message: string, keyword?: string) {
|
||||
public updateMessage(idx: number, message: string, keyword?: string, quota?: number) {
|
||||
this.data[idx].content += message;
|
||||
if (keyword) this.data[idx].keyword = keyword;
|
||||
if (quota) this.data[idx].quota = quota;
|
||||
this.triggerCallback();
|
||||
}
|
||||
|
||||
@ -92,7 +93,7 @@ export class Conversation {
|
||||
});
|
||||
|
||||
return (message: StreamMessage) => {
|
||||
this.updateMessage(cursor, message.message, message.keyword);
|
||||
this.updateMessage(cursor, message.message, message.keyword, message.quota);
|
||||
if (message.end) {
|
||||
this.end = true;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { Conversation } from "./conversation.ts";
|
||||
export type Message = {
|
||||
content: string;
|
||||
keyword?: string;
|
||||
quota?: number;
|
||||
role: string;
|
||||
};
|
||||
|
||||
|
@ -51,7 +51,8 @@ const resources = {
|
||||
"copy": "Copy",
|
||||
"save": "Save as File",
|
||||
"use": "Use Message",
|
||||
}
|
||||
},
|
||||
"quota-description": "spending quota for the message",
|
||||
},
|
||||
},
|
||||
cn: {
|
||||
@ -94,7 +95,8 @@ const resources = {
|
||||
"copy": "复制",
|
||||
"save": "保存为文件",
|
||||
"use": "使用消息",
|
||||
}
|
||||
},
|
||||
"quota-description": "消息的配额支出",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useToast } from "../components/ui/use-toast.ts";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { ToastAction } from "../components/ui/toast.tsx";
|
||||
import { login } from "../conf.ts";
|
||||
import {login, tokenField} from "../conf.ts";
|
||||
import { useEffect } from "react";
|
||||
import Loader from "../components/Loader.tsx";
|
||||
import "../assets/auth.less";
|
||||
@ -16,7 +16,7 @@ function Auth() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const search = new URLSearchParams(useLocation().search);
|
||||
const token = (search.get("token") || "").trim();
|
||||
const token = (search.get(tokenField) || "").trim();
|
||||
|
||||
if (!token.length) {
|
||||
toast({
|
||||
|
@ -137,7 +137,8 @@ function SideBar() {
|
||||
{t("conversation.cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
if (
|
||||
await deleteConversation(dispatch, conversation.id)
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import axios from "axios";
|
||||
import {tokenField} from "../conf.ts";
|
||||
|
||||
export const authSlice = createSlice({
|
||||
name: "auth",
|
||||
@ -12,7 +13,7 @@ export const authSlice = createSlice({
|
||||
setToken: (state, action) => {
|
||||
state.token = action.payload as string;
|
||||
axios.defaults.headers.common["Authorization"] = state.token;
|
||||
localStorage.setItem("token", state.token);
|
||||
localStorage.setItem(tokenField, state.token);
|
||||
},
|
||||
setAuthenticated: (state, action) => {
|
||||
state.authenticated = action.payload as boolean;
|
||||
@ -25,7 +26,7 @@ export const authSlice = createSlice({
|
||||
state.authenticated = false;
|
||||
state.username = "";
|
||||
axios.defaults.headers.common["Authorization"] = "";
|
||||
localStorage.removeItem("token");
|
||||
localStorage.removeItem(tokenField);
|
||||
|
||||
location.reload();
|
||||
},
|
||||
|
@ -4,6 +4,29 @@ import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// Price Calculation
|
||||
// 10 nio points = ¥1
|
||||
// from 2023-9-6, 1 USD = 7.3124 CNY
|
||||
//
|
||||
// GPT-4 price (8k-context)
|
||||
// Input Output
|
||||
// $0.03 / 1K tokens $0.06 / 1K tokens
|
||||
// ¥0.21 / 1K tokens ¥0.43 / 1K tokens
|
||||
// 2.1 nio / 1K tokens 4.3 nio / 1K tokens
|
||||
|
||||
// Dalle price (512x512)
|
||||
// $0.018 / per image
|
||||
// ¥0.13 / per image
|
||||
// 1 nio / per image
|
||||
|
||||
func CountInputToken(n int) float32 {
|
||||
return float32(n) / 1000 * 2.1
|
||||
}
|
||||
|
||||
func CountOutputToken(n int) float32 {
|
||||
return float32(n) / 1000 * 4.3
|
||||
}
|
||||
|
||||
func ReduceUsage(db *sql.DB, user *User, _t string) bool {
|
||||
id := user.GetID(db)
|
||||
var count int
|
||||
@ -82,7 +105,7 @@ func BuyDalle(db *sql.DB, user *User, value int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func CountGPT4Prize(value int) float32 {
|
||||
func CountGPT4price(value int) float32 {
|
||||
if value <= 20 {
|
||||
return float32(value) * 0.5
|
||||
}
|
||||
@ -91,7 +114,7 @@ func CountGPT4Prize(value int) float32 {
|
||||
}
|
||||
|
||||
func BuyGPT4(db *sql.DB, user *User, value int) bool {
|
||||
if !Pay(user.Username, CountGPT4Prize(value)) {
|
||||
if !Pay(user.Username, CountGPT4price(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -14,11 +14,13 @@ type Conversation struct {
|
||||
Name string `json:"name"`
|
||||
Message []types.ChatGPTMessage `json:"message"`
|
||||
EnableGPT4 bool `json:"enable_gpt4"`
|
||||
EnableWeb bool `json:"enable_web"`
|
||||
}
|
||||
|
||||
type FormMessage struct {
|
||||
Type string `json:"type"` // ping
|
||||
Message string `json:"message"`
|
||||
Web bool `json:"web"`
|
||||
GPT4 bool `json:"gpt4"`
|
||||
}
|
||||
|
||||
@ -29,6 +31,7 @@ func NewConversation(db *sql.DB, id int64) *Conversation {
|
||||
Name: "new chat",
|
||||
Message: []types.ChatGPTMessage{},
|
||||
EnableGPT4: false,
|
||||
EnableWeb: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,10 +39,18 @@ func (c *Conversation) IsEnableGPT4() bool {
|
||||
return c.EnableGPT4
|
||||
}
|
||||
|
||||
func (c *Conversation) IsEnableWeb() bool {
|
||||
return c.EnableWeb
|
||||
}
|
||||
|
||||
func (c *Conversation) SetEnableGPT4(enable bool) {
|
||||
c.EnableGPT4 = enable
|
||||
}
|
||||
|
||||
func (c *Conversation) SetEnableWeb(enable bool) {
|
||||
c.EnableWeb = enable
|
||||
}
|
||||
|
||||
func (c *Conversation) GetName() string {
|
||||
return c.Name
|
||||
}
|
||||
@ -131,6 +142,7 @@ func (c *Conversation) AddMessageFromUserForm(data []byte) (string, error) {
|
||||
|
||||
c.AddMessageFromUser(form.Message)
|
||||
c.SetEnableGPT4(form.GPT4)
|
||||
c.SetEnableWeb(form.Web)
|
||||
return form.Message, nil
|
||||
}
|
||||
|
||||
|
3
go.mod
3
go.mod
@ -15,6 +15,7 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
@ -22,6 +23,7 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
@ -33,6 +35,7 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pkoukk/tiktoken-go v0.1.5 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -61,6 +61,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -147,6 +149,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
@ -192,6 +196,8 @@ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZ
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pkoukk/tiktoken-go v0.1.5 h1:hAlT4dCf6Uk50x8E7HQrddhH3EWMKUN+LArExQQsQx4=
|
||||
github.com/pkoukk/tiktoken-go v0.1.5/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
|
@ -34,7 +34,8 @@ type ChatGPTStreamResponse struct {
|
||||
}
|
||||
|
||||
type ChatGPTSegmentResponse struct {
|
||||
Keyword string `json:"keyword"`
|
||||
Message string `json:"message"`
|
||||
End bool `json:"end"`
|
||||
Quota float32 `json:"quota"`
|
||||
Keyword string `json:"keyword"`
|
||||
Message string `json:"message"`
|
||||
End bool `json:"end"`
|
||||
}
|
||||
|
@ -37,6 +37,14 @@ func Unmarshal[T interface{}](data []byte) (form T, err error) {
|
||||
return form, err
|
||||
}
|
||||
|
||||
func Marshal[T interface{}](data T) string {
|
||||
res, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(res)
|
||||
}
|
||||
|
||||
func ToInt(value string) int {
|
||||
if res, err := strconv.Atoi(value); err == nil {
|
||||
return res
|
||||
|
60
utils/tokenizer.go
Normal file
60
utils/tokenizer.go
Normal file
@ -0,0 +1,60 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"chat/types"
|
||||
"fmt"
|
||||
"github.com/pkoukk/tiktoken-go"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Using https://github.com/pkoukk/tiktoken-go
|
||||
// To count number of tokens of openai chat messages
|
||||
// OpenAI Cookbook: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
|
||||
|
||||
func GetWeightByModel(model string) int {
|
||||
switch model {
|
||||
case "gpt-3.5-turbo-0613",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-32k-0613":
|
||||
return 3
|
||||
case "gpt-3.5-turbo-0301":
|
||||
return 4 // every message follows <|start|>{role/name}\n{content}<|end|>\n
|
||||
default:
|
||||
if strings.Contains(model, "gpt-3.5-turbo") {
|
||||
// warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.
|
||||
return GetWeightByModel("gpt-3.5-turbo-0613")
|
||||
} else if strings.Contains(model, "gpt-4") {
|
||||
// warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.
|
||||
return GetWeightByModel("gpt-4-0613")
|
||||
} else {
|
||||
// not implemented: See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens
|
||||
panic(fmt.Errorf("not implemented for model %s", model))
|
||||
}
|
||||
}
|
||||
}
|
||||
func NumTokensFromMessages(messages []types.ChatGPTMessage, model string) (tokens int) {
|
||||
weight := GetWeightByModel(model)
|
||||
tkm, err := tiktoken.EncodingForModel(model)
|
||||
if err != nil {
|
||||
// can not encode messages, use length of messages as a proxy for number of tokens
|
||||
// using rune instead of byte to account for unicode characters (e.g. emojis, non-english characters)
|
||||
|
||||
data := Marshal(messages)
|
||||
return len([]rune(data)) * weight
|
||||
}
|
||||
|
||||
for _, message := range messages {
|
||||
tokens += weight
|
||||
tokens += len(tkm.Encode(message.Content, nil, nil))
|
||||
tokens += len(tkm.Encode(message.Role, nil, nil))
|
||||
}
|
||||
tokens += 3 // every reply is primed with <|start|>assistant<|message|>
|
||||
return tokens
|
||||
}
|
||||
|
||||
func CountTokenPrice(messages []types.ChatGPTMessage) int {
|
||||
return NumTokensFromMessages(messages, "gpt-4")
|
||||
}
|
Loading…
Reference in New Issue
Block a user