diff --git a/Dockerfile b/Dockerfile index dfaa5a5..fd1d7b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ COPY ./utils ./utils COPY ./main.go ./ RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg/mod \ - CGO_ENABLED=0 go build -trimpath \ + CGO_ENABLED=0 GOEXPERIMENT=greenteagc go build -trimpath \ -ldflags="-s -w -X 'kurumibot/utils.BuildTime=$BUILD_TIME' -X 'kurumibot/utils.GitCommit=$GIT_COMMIT'" \ -v -o /usr/local/bin/kurumi ./ diff --git a/Makefile b/Makefile index ee8b22f..d2e3844 100644 --- a/Makefile +++ b/Makefile @@ -5,5 +5,7 @@ build: @echo "Building commit $(GIT_COMMIT)" @echo "Build time $(BUILD_TIME)" go mod tidy - docker build --build-arg GIT_COMMIT --build-arg BUILD_TIME -t git.nix13.pw/scuroneko/kurumibotgo:latest -t git.nix13.pw/scuroneko/kurumibotgo:0.2.0 -f ./Dockerfile . + docker build --build-arg GIT_COMMIT --build-arg BUILD_TIME \ + -t git.nix13.pw/scuroneko/kurumibotgo:latest \ + -t git.nix13.pw/scuroneko/kurumibotgo:0.2.0 -f ./Dockerfile . docker push git.nix13.pw/scuroneko/kurumibotgo --all-tags \ No newline at end of file diff --git a/plugins/rp.go b/plugins/rp.go index a62f3b8..2a8e7e4 100644 --- a/plugins/rp.go +++ b/plugins/rp.go @@ -27,7 +27,7 @@ func RegisterRP(bot *laniakea.Bot) { rp.Payload(rpModelSet, "rp.model_set") rp.Payload(rpScenarioList, "rp.scenario_list") rp.Payload(rpSettingList, "rp.setting_list") - rp.Payload(chatStat, "rp.tokens") + rp.Payload(chatStat, "rp.chat_stat") rp.Payload(newChatStage1, "rp.new_chat_s1") rp.Payload(newChatStage2, "rp.new_chat_s2") @@ -98,7 +98,7 @@ func rpInfo(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { } kb := laniakea.NewInlineKeyboard(2) - kb.AddCallbackButton("Статистика чата", "rp.tokens") + kb.AddCallbackButton("Статистика чата", "rp.chat_stat") kb.AddCallbackButton("Сменить вайфу", "rp.waifu_list") kb.AddCallbackButton("Сменить пресет", "rp.preset_list") kb.AddCallbackButton("Сменить модель", "rp.model_list") @@ -167,7 +167,7 @@ func rpPresetsList(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { kb := laniakea.NewInlineKeyboard(2) for i, preset := range presets { out[i] = fmt.Sprintf( - "*%s*\n%s", + "*%s* - %s", laniakea.EscapeMarkdown(preset.Name), preset.Description, ) kb.AddCallbackButton(preset.Name, "rp.preset_set", preset.ID) @@ -212,7 +212,7 @@ func rpModelList(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { out := make([]string, len(models)) kb := laniakea.NewInlineKeyboard(2) for i, model := range models { - out[i] = fmt.Sprintf("*%s* - размер контекста %dK", model.Name, model.ContextSize) + out[i] = fmt.Sprintf("*%s* - размер контекста _%dK_", model.Name, model.ContextSize) kb.AddCallbackButton(model.Name, "rp.model_set", model.ID) } kb.AddLine() @@ -266,7 +266,7 @@ func rpSettingList(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { out := make([]string, len(settings)) for i, setting := range settings { - out[i] = fmt.Sprintf("*%s* - _%s_\n", setting.Name, setting.Description) + out[i] = fmt.Sprintf("*%s* - %s\n", setting.Name, setting.Description) } kb := laniakea.NewInlineKeyboard(1) kb.AddCallbackButton("На главную", "rp.info") @@ -296,12 +296,14 @@ func chatStat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { } tokens := redisRpRep.GetChatTokens(ctx.FromID, waifuId) - kb := laniakea.NewInlineKeyboard(1).AddCallbackButton("На главную", "rp.info") + kb := laniakea.NewInlineKeyboard(1) + kb = kb.AddCallbackButton("Сжать чат", "rp.compress_chat") + kb = kb.AddCallbackButton("На главную", "rp.info") out := []string{ "Статистика чата", - fmt.Sprintf("ID: `%s`", chatId), - fmt.Sprintf("Кол-во сообщений: %d", messageCount), - fmt.Sprintf("Кол-во токенов: %d", tokens), + fmt.Sprintf("*ID*: `%s`", chatId), + fmt.Sprintf("*Кол-во сообщений*: %d", messageCount), + fmt.Sprintf("*Кол-во токенов*: %d", tokens), } ctx.EditCallback(strings.Join(out, "\n"), kb) } @@ -352,7 +354,7 @@ func newChatStage2(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { } kb := laniakea.NewInlineKeyboard(2) for _, scenario := range scenarios { - out = append(out, fmt.Sprintf("*%s* - _%s_", scenario.Name, scenario.Description)) + out = append(out, fmt.Sprintf("*%s* - %s", scenario.Name, scenario.Description)) kb.AddCallbackButton(scenario.Name, "rp.new_chat", settingId, scenario.ID) } kb.AddCallbackButton("Без сценария", "rp.new_chat", 0) @@ -535,18 +537,12 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { } userMessage := strings.TrimSpace(strings.Join(ctx.Args, " ")) - messages = append(messages, afterHistory, ai.Message{ - Role: "user", - Content: userMessage, - }) + messages = append(messages, afterHistory) kb := laniakea.NewInlineKeyboard(1).AddCallbackButton("Отменить", "rp.cancel") m := ctx.Keyboard("Генерация запущена...", kb) api := ai.NewOpenAIAPI(ai.GPTBaseUrl, "", rpUser.Model.Key) - res, err := api.CreateCompletion(ai.CreateCompletionReq{ - Messages: messages, - Temperature: 1.0, - }) + res, err := api.CreateCompletion(messages, userMessage, 1.0) if err != nil { ctx.Error(err) return @@ -568,9 +564,9 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { ctx.Error(err) } - rpUser.UsedTokens = rpUser.UsedTokens + res.Usage.TotalTokens + rpUser.UsedTokens = rpUser.UsedTokens + int64(len(userMessage)) + int64(len(answerContent)) tokens := redisRpRep.GetChatTokens(ctx.FromID, waifuId) - tokens += int(res.Usage.CompletionTokens) + tokens += len(userMessage) + len(answerContent) err = redisRpRep.SetChatTokens(ctx.FromID, waifuId, tokens) if err != nil { ctx.Error(err) @@ -581,12 +577,11 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { } m.Delete() - kb = laniakea.NewInlineKeyboard(1).AddCallbackButton("Сжать чат", "rp.compress_chat") - ctx.Keyboard(laniakea.EscapeMarkdown(answerContent), kb) + ctx.Answer(laniakea.EscapeMarkdown(answerContent)) } func compress(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { - m := ctx.Answer("Запущено сжатие чата.") + m := ctx.EditCallback("Запущено сжатие чата…", nil) redisRpRep := red.NewRPRepository(db) waifuId := redisRpRep.GetSelectedWaifu(ctx.FromID) if waifuId == 0 { @@ -615,17 +610,21 @@ func compress(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { Content: m.Message, }) } - api := ai.NewOpenAIAPI(ai.GPTBaseUrl, "", "deepseek-ai/deepseek-v3.1-terminus") + + //compressModel := "anthropic/claude-sonnet-4" + compressModel := "gpt-5.1" + //compressModel := "deepseek-ai/deepseek-v3.2" + api := ai.NewOpenAIAPI(ai.GPTBaseUrl, "", compressModel) res, err := api.CompressChat(messages) if err != nil { ctx.Error(err) } if len(res.Choices) == 0 { - m.Edit("Не удалось сжать диалог") + m.Edit("Не удалось сжать чат") return } - - compressedHistory := res.Choices[0].Message.Content + compressedHistory := strings.TrimSpace(res.Choices[0].Message.Content) + compressedHistory = strings.ReplaceAll(compressedHistory, "*", "") chatId = uuid.New().String() err = redisRpRep.SetChatId(ctx.FromID, waifuId, chatId) @@ -637,9 +636,21 @@ func compress(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) { err = mdb.UpdateChatHistory(db, chatId, "assistant", compressedHistory) if err != nil { ctx.Error(err) - return } - m.Edit("Сжатие завершено") + offset := 20 + if len(history) < 20 { + offset = len(history) + } + for _, m := range history[len(history)-offset:] { + err = mdb.UpdateChatHistory(db, chatId, m.Role, m.Message) + if err != nil { + ctx.Error(err) + } + } + kb := laniakea.NewInlineKeyboard(1) + kb = kb.AddCallbackButton("Назад", "rp.chat_stat") + kb = kb.AddCallbackButton("На главную", "rp.info") + ctx.EditCallback("Сжатие завершено", kb) } func generalClose(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) { diff --git a/utils/ai/consts.go b/utils/ai/consts.go index c428c13..0895e96 100644 --- a/utils/ai/consts.go +++ b/utils/ai/consts.go @@ -1,11 +1,7 @@ package ai -import ( - "fmt" -) - -const PawanBaseURL = "https://api.pawan.krd" const GPTBaseUrl = "https://chat.gpt-chatbot.ru/api/openai" -const CompressPrompt = "Сделай выжимку нашего диалога. Сохрани все ключевые моменты(например одежду, расположение, основные действия и фразы и тд.) из каждого сообщения. Если в истории уже есть выжимка, проанализируй размер и информацию. Если выжимка короткая, скопируй её с минимальными изменениями, а если длинная, то сократи её, не упуская деталей. Всегда копируй основные моменты из выжимки, если таковая имеется. Для моих сообщений используй обращение во втором лице(ты, вы), для своих в первом лице(я)." +const CompressPrompt = "Сделай выжимку нашего диалога. Сохрани все ключевые моменты(например сколько одежды, расположение, основные действия и фразы и тд.) из каждого сообщения, но никогда не копируй описание персонажа(цвет волос и глаз, характер и т.д.). Если в истории уже есть выжимка, проанализируй размер и информацию. Если выжимка короткая, скопируй её с минимальными изменениями, а если длинная, то сократи, не упуская деталей. Всегда копируй основные моменты из выжимки, если таковая имеется. Для моих сообщений используй обращение во втором лице(ты, вы), для своих в первом(я). Всегда называй вещи своими именами и не используй Markdown форматирование. После твоего сообщения будет добавлено 20 сообщений из этого диалога, по 10 твоих и моих." -var CosmoRPUrl = fmt.Sprintf("%s/cosmosrp-2.5", PawanBaseURL) +//const PawanBaseURL = "https://api.pawan.krd" +//var CosmoRPUrl = fmt.Sprintf("%s/cosmosrp-2.5", PawanBaseURL) diff --git a/utils/ai/openai.go b/utils/ai/openai.go index a89ba3c..4c3a16b 100644 --- a/utils/ai/openai.go +++ b/utils/ai/openai.go @@ -22,21 +22,18 @@ type OpenAIResponse struct { Usage Usage `json:"usage"` ServiceTier string `json:"service_tier"` } - type Choice struct { Index int64 `json:"index"` Message Message `json:"message"` Logprobs interface{} `json:"logprobs"` FinishReason string `json:"finish_reason"` } - type Message struct { Role string `json:"role"` Content string `json:"content"` Refusal interface{} `json:"refusal"` Annotations []interface{} `json:"annotations"` } - type Usage struct { PromptTokens int64 `json:"prompt_tokens"` CompletionTokens int64 `json:"completion_tokens"` @@ -44,19 +41,16 @@ type Usage struct { PromptTokensDetails PromptTokensDetails `json:"prompt_tokens_details"` CompletionTokensDetails CompletionTokensDetails `json:"completion_tokens_details"` } - type CompletionTokensDetails struct { ReasoningTokens int64 `json:"reasoning_tokens"` AudioTokens int64 `json:"audio_tokens"` AcceptedPredictionTokens int64 `json:"accepted_prediction_tokens"` RejectedPredictionTokens int64 `json:"rejected_prediction_tokens"` } - type PromptTokensDetails struct { CachedTokens int64 `json:"cached_tokens"` AudioTokens int64 `json:"audio_tokens"` } - type OpenAIAPI struct { Token string Model string @@ -67,7 +61,11 @@ type OpenAIAPI struct { func NewOpenAIAPI(baseURL, token, model string) *OpenAIAPI { logger := laniakea.CreateLogger() - logger = logger.Prefix("AI").Level(laniakea.DEBUG) + level := laniakea.FATAL + if os.Getenv("DEBUG") == "true" { + level = laniakea.DEBUG + } + logger = logger.Prefix("AI").Level(level) proxy, err := url.Parse(os.Getenv("HTTPS_PROXY")) if err != nil { logger.Error(err) @@ -160,9 +158,16 @@ func (o *OpenAIAPI) DoRequest(url string, params any, retries int) ([]byte, erro return responseBody, err } -func (o *OpenAIAPI) CreateCompletion(request CreateCompletionReq) (*OpenAIResponse, error) { +func (o *OpenAIAPI) CreateCompletion(history []Message, message string, temp float32) (*OpenAIResponse, error) { u := fmt.Sprintf("%s/v1/chat/completions", o.BaseURL) - request.Model = o.Model + request := CreateCompletionReq{ + Model: o.Model, + Messages: append(history, Message{ + Role: "user", + Content: message, + }), + Temperature: temp, + } data, err := json.Marshal(request) if err != nil { return nil, err diff --git a/utils/ai/utils.go b/utils/ai/utils.go index bd5c09e..e6fcdf0 100644 --- a/utils/ai/utils.go +++ b/utils/ai/utils.go @@ -6,3 +6,53 @@ func FormatPrompt(prompt, char, user string) string { s := strings.ReplaceAll(prompt, "{{user}}", user) return strings.ReplaceAll(s, "{{char}}", char) } + +type ProviderConfig struct { + NeedCode bool `json:"needCode"` + HideUserAPIKey bool `json:"hideUserApiKey"` + DisableGPT4 bool `json:"disableGPT4"` + HideBalanceQuery bool `json:"hideBalanceQuery"` + DisableFastLink bool `json:"disableFastLink"` + CustomModels string `json:"customModels"` + DefaultModel string `json:"defaultModel"` + VisionModels string `json:"visionModels"` +} + +/* POST https://chat.gpt-chatbot.ru/api/config ++gpt-5.1@OpenAI ++gpt-5@OpenAI ++anthropic/claude-sonnet-4@OpenAI ++mercury-coder@OpenAI ++Olmo-3.1-32B-Instruct@OpenAI ++gpt-4.1-mini@OpenAI ++chatgpt-4o-latest@OpenAI ++google/gemini-2.5-pro-preview-05-06@OpenAI ++x-ai/grok-4@OpenAI ++deepseek-ai/DeepSeek-V3.2@OpenAI ++deepseek-ai/DeepSeek-V3.1-Terminus@OpenAI ++deepseek-ai/deepseek-r1-0528@OpenAI ++o1-preview@OpenAI ++o3-mini@OpenAI ++qwen/qwen3-coder-480b-a35b-instruct@OpenAI ++moonshotai/kimi-k2-thinking@OpenAI ++moonshotai/kimi-k2-instruct-0905@OpenAI ++openai/gpt-oss-120b@OpenAI ++openai/gpt-oss-20b@OpenAI ++meta/llama-3.1-405b-instruct@OpenAI ++meta/llama-4-maverick-17b-128e-instruct@OpenAI ++meta/llama-4-scout-17b-16e-instruct@OpenAI ++meta-llama-3.3-70b-instruct@OpenAI ++meta-llama-3.1-8b-instruct@OpenAI ++google/gemma-3-27b-it@OpenAI ++nvidia/nemotron-3-nano-30b-a3b@OpenAI ++qwen/qwq-32b@OpenAI ++deepseek-ai/deepseek-r1-distill-qwen-32b@OpenAI ++qwen/qwen3-235b-a22b@OpenAI ++minimaxai/minimax-m2@OpenAI ++zai-org/GLM-4.6@OpenAI ++meta-llama/Llama-3.1-8B-Instruct@OpenAI ++mistralai/mistral-large-3-675b-instruct-2512@OpenAI ++mistralai/magistral-small-2506@OpenAI ++mistralai/mistral-small-3.1-24b-instruct-2503@OpenAI ++mistralai/ministral-14b-instruct-2512@OpenAI +*/