Skip to content

Commit

Permalink
add sparkdesk, slack-claude model
Browse files Browse the repository at this point in the history
  • Loading branch information
zmh-program committed Sep 30, 2023
1 parent b08c36b commit ec45794
Show file tree
Hide file tree
Showing 14 changed files with 367 additions and 9 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@
- GPT-4-Reverse (_gpt-4_)
- DALL-E
- Claude
- Slack-Claude (unstable)
- Claude-2
- Claude-2-100k
- SparkDesk 讯飞星火
- v1.5
- v2.0

- More models are under development...

## 📚 预览 | Screenshots
Expand Down
19 changes: 15 additions & 4 deletions adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package adapter

import (
"chat/adapter/chatgpt"
"chat/adapter/slack"
"chat/adapter/sparkdesk"
"chat/globals"
"chat/utils"
"github.com/spf13/viper"
Expand All @@ -14,11 +16,13 @@ type ChatProps struct {
Message []globals.Message
}

type Hook func(data string) error

func NewChatRequest(props *ChatProps, hook Hook) error {
func NewChatRequest(props *ChatProps, hook globals.Hook) error {
if globals.IsClaudeModel(props.Model) {
return nil // work in progress
instance := slack.NewChatInstanceFromConfig()
return instance.CreateStreamChatRequest(&slack.ChatProps{
Message: props.Message,
}, hook)

} else if globals.IsChatGPTModel(props.Model) {
instance := chatgpt.NewChatInstanceFromModel(&chatgpt.InstanceProps{
Model: props.Model,
Expand All @@ -33,6 +37,13 @@ func NewChatRequest(props *ChatProps, hook Hook) error {
Message: props.Message,
Token: utils.Multi(globals.IsGPT4Model(props.Model) || props.Reversible || props.Infinity, -1, 2000),
}, hook)

} else if globals.IsSparkDeskModel(props.Model) {
return sparkdesk.NewChatInstance().CreateStreamChatRequest(&sparkdesk.ChatProps{
Message: props.Message,
Token: 2048,
}, hook)

} else {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion adapter/chatgpt/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (c *ChatInstance) CreateChatRequest(props *ChatProps) (string, error) {
}

// CreateStreamChatRequest is the stream response body for chatgpt
func (c *ChatInstance) CreateStreamChatRequest(props *ChatProps, callback func(string) error) error {
func (c *ChatInstance) CreateStreamChatRequest(props *ChatProps, callback globals.Hook) error {
return utils.EventSource(
"POST",
c.GetChatEndpoint(),
Expand Down
24 changes: 24 additions & 0 deletions adapter/slack/chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package slack

import (
"chat/globals"
"context"
"github.com/spf13/viper"
)

type ChatProps struct {
Message []globals.Message
}

func (c *ChatInstance) CreateStreamChatRequest(props *ChatProps, hook globals.Hook) error {
if err := c.Instance.NewChannel(viper.GetString("slack.channel")); err != nil {
return err
}

resp, err := c.Instance.Reply(context.Background(), c.FormatMessage(props.Message), nil)
if err != nil {
return err
}

return c.ProcessPartialResponse(resp, hook)
}
77 changes: 77 additions & 0 deletions adapter/slack/struct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package slack

import (
"chat/globals"
"fmt"
"github.com/bincooo/claude-api"
"github.com/bincooo/claude-api/types"
"github.com/bincooo/claude-api/vars"
"github.com/spf13/viper"
"strings"
)

type ChatInstance struct {
BotId string
Token string
Instance types.Chat
}

func (c *ChatInstance) GetBotId() string {
return c.BotId
}

func (c *ChatInstance) GetToken() string {
return c.Token
}

func (c *ChatInstance) GetInstance() types.Chat {
return c.Instance
}

func NewChatInstance(botId, token string) *ChatInstance {
options := claude.NewDefaultOptions(token, botId, vars.Model4Slack)
if instance, err := claude.New(options); err != nil {
return nil
} else {
return &ChatInstance{
BotId: botId,
Token: token,
Instance: instance,
}
}
}

func NewChatInstanceFromConfig() *ChatInstance {
return NewChatInstance(
viper.GetString("slack.bot_id"),
viper.GetString("slack.token"),
)
}

func (c *ChatInstance) FormatMessage(message []globals.Message) string {
result := make([]string, len(message))
for i, item := range message {
result[i] = fmt.Sprintf("%s: %s", item.Role, item.Content)
}

return strings.Join(result, "\n\n")
}

func (c *ChatInstance) ProcessPartialResponse(res chan types.PartialResponse, hook globals.Hook) error {
for {
select {
case data, ok := <-res:
if !ok {
return nil
}

if data.Error != nil {
return data.Error
} else if data.Text != "" {
if err := hook(data.Text); err != nil {
return err
}
}
}
}
}
54 changes: 54 additions & 0 deletions adapter/sparkdesk/chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package sparkdesk

import (
"chat/globals"
"chat/utils"
"fmt"
)

type ChatProps struct {
Message []globals.Message
Token int
}

func (c *ChatInstance) CreateStreamChatRequest(props *ChatProps, hook globals.Hook) error {
var conn *utils.WebSocket
if conn = utils.NewWebsocketClient(c.GenerateUrl()); conn == nil {
return fmt.Errorf("sparkdesk error: websocket connection failed")
}
defer conn.DeferClose()

if err := conn.SendJSON(&ChatRequest{
Header: RequestHeader{
AppId: c.AppId,
},
Payload: RequestPayload{
Message: MessagePayload{
Text: props.Message,
},
},
Parameter: RequestParameter{
Chat: ChatParameter{
Domain: c.Model,
MaxToken: props.Token,
},
},
}); err != nil {
return err
}

for {
form := utils.ReadForm[ChatResponse](conn)
if form == nil {
return nil
}

if form.Header.Code != 0 {
return fmt.Errorf("sparkdesk error: %s (sid: %s)", form.Header.Message, form.Header.Sid)
}

if err := hook(form.Payload.Choices.Text[0].Content); err != nil {
return err
}
}
}
72 changes: 72 additions & 0 deletions adapter/sparkdesk/struct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sparkdesk

import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/spf13/viper"
"net/url"
"strings"
"time"
)

type ChatInstance struct {
AppId string
ApiSecret string
ApiKey string
Model string
Endpoint string
}

func NewChatInstance() *ChatInstance {
return &ChatInstance{
AppId: viper.GetString("sparkdesk.app_id"),
ApiSecret: viper.GetString("sparkdesk.api_secret"),
ApiKey: viper.GetString("sparkdesk.api_key"),
Model: viper.GetString("sparkdesk.model"),
Endpoint: viper.GetString("sparkdesk.endpoint"),
}
}

func (c *ChatInstance) CreateUrl(host, date, auth string) string {
v := make(url.Values)
v.Add("host", host)
v.Add("date", date)
v.Add("authorization", auth)
return fmt.Sprintf("%s?%s", c.Endpoint, v.Encode())
}

func (c *ChatInstance) Sign(data, key string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(data))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

// GenerateUrl will generate the signed url for sparkdesk api
func (c *ChatInstance) GenerateUrl() string {
uri, err := url.Parse(c.Endpoint)
if err != nil {
return ""
}

date := time.Now().UTC().Format(time.RFC1123)
data := strings.Join([]string{
fmt.Sprintf("host: %s", uri.Host),
fmt.Sprintf("date: %s", date),
fmt.Sprintf("GET %s HTTP/1.1", uri.Path),
}, "\n")

signature := c.Sign(data, c.ApiSecret)
authorization := base64.StdEncoding.EncodeToString([]byte(
fmt.Sprintf(
"hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"",
c.ApiKey,
"hmac-sha256",
"host date request-line",
signature,
),
))

return c.CreateUrl(uri.Host, date, authorization)
}
60 changes: 60 additions & 0 deletions adapter/sparkdesk/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package sparkdesk

import "chat/globals"

// ChatRequest is the request body for sparkdesk
type ChatRequest struct {
Header RequestHeader `json:"header"`
Payload RequestPayload `json:"payload"`
Parameter RequestParameter `json:"parameter"`
}

type RequestHeader struct {
AppId string `json:"app_id"`
}

type RequestPayload struct {
Message MessagePayload `json:"message"`
}

type MessagePayload struct {
Text []globals.Message `json:"text"`
}

type RequestParameter struct {
Chat ChatParameter `json:"chat"`
}

type ChatParameter struct {
Domain string `json:"domain"`
MaxToken int `json:"max_tokens"`
}

// ChatResponse is the websocket partial response body for sparkdesk
type ChatResponse struct {
Header struct {
Code int `json:"code" required:"true"`
Message string `json:"message"`
Sid string `json:"sid"`
Status int `json:"status"`
} `json:"header"`
Payload struct {
Choices struct {
Status int `json:"status"`
Seq int `json:"seq"`
Text []struct {
Role string `json:"role"`
Content string `json:"content"`
Index int `json:"index"`
} `json:"text"`
} `json:"choices"`
Usage struct {
Text struct {
QuestionTokens int `json:"question_tokens"`
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
}
}
}
9 changes: 6 additions & 3 deletions app/src/conf.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from "axios";

export const version: string = "3.0.0";
export const version: string = "3.1.0";
export const deploy: boolean = true;
export let rest_api: string = "http://localhost:8094";
export let ws_api: string = "ws://localhost:8094";
Expand All @@ -16,16 +16,19 @@ export const supportModels: string[] = [
"GPT-3.5-16k",
"GPT-4",
"GPT-4-32k",
"Claude-2",
"Claude-2-100k",
"SparkDesk 讯飞星火"
// "Claude-2",
// "Claude-2-100k",
];

export const supportModelConvertor: Record<string, string> = {
"GPT-3.5": "gpt-3.5-turbo",
"GPT-3.5-16k": "gpt-3.5-turbo-16k",
"GPT-4": "gpt-4",
"GPT-4-32k": "gpt-4-32k",
"Claude-2": "claude-2",
"Claude-2-100k": "claude-2-100k",
"SparkDesk 讯飞星火": "spark-desk",
};

export function login() {
Expand Down
1 change: 1 addition & 0 deletions globals/types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package globals

type Hook func(data string) error
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
Expand Down
Loading

0 comments on commit ec45794

Please sign in to comment.