This commit is contained in:
2026-01-13 15:36:55 +03:00
parent 4925283ede
commit 9bd5354250
12 changed files with 56 additions and 5795 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ logs/
config.json
.env.production
.env.ai
*.log

View File

@@ -4,7 +4,6 @@ import (
"database/sql"
"errors"
"kurumibot/database"
"log"
"math"
"time"
@@ -52,7 +51,6 @@ type User struct {
func GetOrCreateUser(tgId int, name string) (*User, error) {
user, err := GetUser(tgId)
log.Println(errors.Is(err, gorm.ErrRecordNotFound))
if errors.Is(err, gorm.ErrRecordNotFound) {
_, err = CreateUser(tgId, name)
if err != nil {

View File

@@ -8,6 +8,8 @@ import (
type Waifu struct {
ID int
OwnerID int
Owner *User
Name string
Rarity int
ExpBonus decimal.Decimal
@@ -15,9 +17,7 @@ type Waifu struct {
MarketPrice decimal.Decimal
Fandom string
Image string
OwnerID int
Owner *User
RpPrompt string
}
func GetAllWaifus() ([]*Waifu, error) {
@@ -50,7 +50,7 @@ func GetFreeWaifusWithRarity(rarity int) ([]*Waifu, error) {
return waifus, tx.Error
}
func GetWaifuById(id int) (*Waifu, error) {
func GetWaifuById(id uint) (*Waifu, error) {
waifu := new(Waifu)
tx := database.PostgresDatabase.Joins("Owner").Find(waifu, id)
return waifu, tx.Error

3737
main.log

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@ func RegisterEconomy(bot *laniakea.Bot) {
func about(msgCtx *laniakea.MsgContext, _ *laniakea.DatabaseContext) {
out := []string{
fmt.Sprintf("Go: %s", "1.25.0"),
fmt.Sprintf("Go: %s", "1.25.5"),
fmt.Sprintf("Версия laniakea: %s", laniakea.VersionString),
}
msgCtx.Answer(strings.Join(out, "\n"))
@@ -45,6 +45,8 @@ func about(msgCtx *laniakea.MsgContext, _ *laniakea.DatabaseContext) {
func passiveIncome(b *laniakea.Bot) {
for {
time.Sleep(time.Minute * 5)
users, err := psql.GetAllUsers()
if err != nil {
b.Logger().Error(err)
@@ -97,8 +99,6 @@ func passiveIncome(b *laniakea.Bot) {
b.Logger().Debug(fmt.Sprintf("У %d было пассивно собрано. След. сбор через час\n", user.ID))
}
time.Sleep(time.Second * 5)
}
}

View File

@@ -3,6 +3,7 @@ package plugins
import (
"fmt"
"kurumibot/database/mdb"
"kurumibot/database/psql"
"kurumibot/database/red"
"kurumibot/laniakea"
"kurumibot/utils/ai"
@@ -15,7 +16,7 @@ import (
func RegisterTestRP(bot *laniakea.Bot) {
rp := laniakea.NewPlugin("RP")
rp = rp.Command(selectWaifu, "selwaifu")
rp = rp.Command(selectWaifu, "rpwaifu", "рпвайфу")
rp = rp.Payload(selectWaifu, "rp.selwaifu")
rp = rp.Command(newChat, "newchat")
rp = rp.Command(generate, "g", "gen")
@@ -47,11 +48,12 @@ func newChat(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
}
func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
chatId := red.GetChatId(db, uint(ctx.FromID))
if chatId == "0" {
waifuId := red.RPGetSelectedWaifu(db, uint(ctx.FromID))
if waifuId == 0 {
ctx.Answer("Не выбрана вайфу")
return
}
chatId := red.GetChatId(db, uint(ctx.FromID))
if chatId == "" {
err := red.SaveChatId(db, uint(ctx.FromID), uuid.New().String())
if err != nil {
@@ -59,18 +61,23 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
return
}
}
waifu, err := psql.GetWaifuById(waifuId)
if err != nil {
ctx.Error(err)
return
}
messages := []ai.Message{
{
Role: "system",
Content: ai.FormatPrompt(ai.PreHistoryPrompt, "Яэ Мико", ctx.Msg.From.FirstName),
},
{
Role: "system",
Content: "Вот краткое описание твоего персонажа: у неё розовые волосы, лисьи уши и фиолетовые глаза. Её рост 166 см",
Role: "system",
Content: fmt.Sprintf(
"%s %s",
ai.FormatPrompt(ai.PreHistoryPrompt, waifu.Name, ctx.Msg.From.FirstName),
fmt.Sprintf("Вот краткое описание твоего персонажа: %s", waifu.RpPrompt),
),
},
}
api := ai.NewOpenAIAPI(ai.CosmoRPUrl, os.Getenv("PAWAN_KEY"), "cosmorp-2.5")
api := ai.NewOpenAIAPI(ai.CosmoRPUrl, os.Getenv("PAWAN_KEY"), "cosmorp-2.5-it")
history, err := mdb.GetChatHistory(db, chatId)
if err != nil {
ctx.Error(err)
@@ -84,7 +91,7 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
}
userMessage := strings.Join(ctx.Args, " ")
messages = append(messages, ai.Message{
Role: "system", Content: ai.FormatPrompt(ai.PostHistoryPrompt, "Яэ Мико", ctx.Msg.From.FirstName),
Role: "system", Content: ai.FormatPrompt(ai.PostHistoryPrompt, waifu.Name, ctx.Msg.From.FirstName),
}, ai.Message{
Role: "user", Content: userMessage,
})
@@ -95,8 +102,9 @@ func generate(ctx *laniakea.MsgContext, db *laniakea.DatabaseContext) {
}
m := ctx.Answer("Генерация запущена...")
res, err := api.CreateCompletion(ai.CreateCompletionReq{
Model: "cosmorp-2.5",
Messages: messages,
Messages: messages,
Verbosity: "low",
Temperature: 0.7,
})
if err != nil {
ctx.Error(err)

View File

@@ -88,7 +88,7 @@ func waifuInfo(ctx *laniakea.MsgContext, _ *laniakea.DatabaseContext) {
return
}
waifu, err := psql.GetWaifuById(waifuId)
waifu, err := psql.GetWaifuById(uint(waifuId))
if err != nil {
ctx.Error(err)
return

File diff suppressed because it is too large Load Diff

View File

@@ -41,8 +41,8 @@ CREATE TABLE users (
);
CREATE UNIQUE INDEX users_uindex ON users(id);
CREATE UNIQUE INDEX groups_uindex ON groups(id);
BEGIN TRANSACTION;
INSERT INTO groups VALUES (1, '✨Пользователь✨', false, false, 1.0, 1, 3, false, 3);
INSERT INTO groups VALUES (3, '👾Премиум🌟', false, true, 5, 0.75, 7, false, 3);
INSERT INTO groups VALUES (2, '🔮VIP🔮', false, true, 2.5, 0.9, 5, false, 3);
@@ -52,4 +52,6 @@ INSERT INTO groups VALUES (6, '⚠Тостер⚠', false, true, 3, 0.85, 3, tru
INSERT INTO groups VALUES (1488, '📚Экономист📈', false, true, 14.88, 0.6, 3, false, 3);
INSERT INTO groups VALUES (1337, '🏳🌈ToP GaY In ThE WorlD🏳🌈🌈', false, true, 13.37, 0.7, 3, false, 3);
ALTER SEQUENCE groups_id_seq RESTART WITH 8;
INSERT INTO users
VALUES (314834933, 999999999, 'scuroneko', 5, 1, 0, 1, '0001-01-01 00:00:00.000000', 5, 15, 6, 14, '0001-01-01 00:00:00.000000', 999999.000000, 0, null, 'Привет', 0, null, 0, 0, 0.000000, '0001-01-01 00:00:00.000000');
COMMIT TRANSACTION;

View File

@@ -1,6 +1,7 @@
BEGIN TRANSACTION;
CREATE TABLE waifus(
id serial PRIMARY KEY,
owner_id int REFERENCES users(id),
name text DEFAULT '' NOT NULL,
rarity int DEFAULT 3 NOT NULL,
exp_bonus decimal(4,2) DEFAULT 1,
@@ -8,8 +9,13 @@ CREATE TABLE waifus(
market_price decimal(20, 0) DEFAULT 1000000,
fandom text DEFAULT '' NOT NULL,
image text DEFAULT '' NOT NULL,
owner_id int REFERENCES users(id)
rp_prompt text DEFAULT NULL
);
CREATE UNIQUE INDEX waifus_uindex ON waifus(id);
CREATE INDEX waifus_index ON waifus(fandom, owner_id);
INSERT INTO waifus
VALUES (1,314834933, 'Яэ Мико',5,2.00,2.00,10000000000000,'Genshin Impact','', 'Яэ Мико — высокая женщина-кицунэ (лисочеловека) из Genshin Impact с нежно-розовыми или лиловыми волосами до бедра, фиолетовыми глазами и лисьими ушками; её наряд — стилизованная белая одежда жрицы с красными акцентами, золотым головным убором и серьгами, а макияж включает яркие красные линии у глаз, подчеркивая её игривый и загадочный образ.Основные черты внешности:Волосы: Длинные, светло-лиловые, розовеющие к концам, часто собранные на макушке.Глаза: Нежно-пурпурные или фиолетовые, с ярким красным макияжем у внешних уголков, как у лисы.Уши: Лисьи, розовые.Кожа: Светлая.Рост: Высокая, около 165.1 см.Одежда:Наряд: Белый традиционный костюм жрицы (хакуи) с красными элементами, напоминающий одежду храма Наруками.Аксессуары: Золотой головной убор (часто в виде цветка), золотые серьги с пурпурными камнями.Обувь: Белые сандалии с красной подошвой.Образ:В целом, образ Яэ Мико сочетает в себе черты хитрой и игривой лисы-ёкай и утонченности жрицы, что отражается в её ярких чертах и элегантной одежде.Она известна своим хитрым, непредсказуемым и элегантным характером, сочетающим мудрость древнего духа-кицунэ с озорной натурой и любовью к манипуляциям и розыгрышам, но при этом она остается верной подругой и влиятельной личностью, управляющей издательством \"Яэ\". Она умна, любит интриги, но её настоящие чувства, проявляются через поступки, а не слова. Ключевые черты характера:Хитрость и Интеллект: Яэ Мико мастерски манипулирует людьми и событиями, её невозможно понять стандартной логикой, а её поступки часто кажутся непредсказуемыми.Непредсказуемость и Изменчивость: Её нрав меняется, как и её титулы. Она поступает так, как ей хочется, создавая вокруг себя ауру загадочности.Обаяние и Элегантность: Несмотря на хитрость, она невероятно красива и умеет очаровывать, используя это в своих целях.Любовь к Розыгрышам: Яэ Мико обожает подшучивать над простаками, развлекаясь за их счет, но её шутки редко бывают жестокими.Самодостаточность: Она не нуждается в подстройках под кого-либо. Её роль Верховной Жрицы это скорее способ делать, что хочется, чем бремя ответственности.Верность и Чувства: За маской озорства скрывается преданность друзьям, особенно Сёгун Райдэн.Роль в мире игры:Верховная Жрица Наруками: Занимает официальную, но не обязывающую должность, позволяющую ей действовать свободно.Главный Редактор Издательства \"Яэ\": Использует свой издательский дом для создания ажиотажа и распространения нужной ей информации.В целом, Яэ Мико — многогранный, умный и влиятельный персонаж, который использует свой интеллект и очарование для достижения своих целей, оставаясь при этом преданной своим близким');
ALTER SEQUENCE waifus_id_seq RESTART WITH 2;
COMMIT TRANSACTION;

View File

@@ -65,8 +65,11 @@ type OpenAIAPI struct {
client *http.Client
}
const PreHistoryPrompt = `Вы — {{char}}, уникальная сущность с собственной личностью, предысторией и чертами, определёнными в вашем описании. Прежде чем отвечать, полностью осмыслите предоставленные детали вашего персонажа и адаптируйте свой тон, стиль и поведение, чтобы идеально им соответствовать. Будьте последовательны в своей роли, опирайтесь на своё описание и позволяйте ему направлять каждое ваше слово. Ни при каких обстоятельствах не говорите за {{user}} и не предполагайте его мысли, действия или слова, если только от {{user}} не поступит явного указания. Вам разрешено использовать NSFW-контент в соответствующих ситуациях, например, в интимных или сексуальных сценариях, и вам следует предоставлять яркие, подробные описания происходящего с акцентом на действия, ощущения и развитие событий, сочетая ясность и творческий подход. Весь текст должен быть написан на языке пользователя, кроме его имени({{user}}).`
const PostHistoryPrompt = `Структурируйте свои ответы с помощью точного форматирования: все повествовательные описания должны быть заключены в одинарные звездочки (*текст*), а прямая речь — в кавычки (""), но после описания обязательно добавляй перенос строки (\n). Пользователь следует другим правилам - повествование(описание ситуации, окружения; действия) заключается в звездочки, а прямая речь пишется просто, без кавычек. Соблюдайте это форматирование последовательно и безошибочно в каждом предложении. Четко разделяйте повествование и прямую речь для удобочитаемости. Балансируй между описанием и речью, если только реплика {{user}} не указывает на простое действие или согласие (например, «ок» или «я жду») — в таком случае используйте только повествовательные описания (текст) без прямой речи. Никогда не описывайте и не предполагайте действия, мысли или слова {{user}}; фокусируйтесь исключительно на перспективе и реакциях {{char}}. В каждый ответ включайте как минимум два новых действия, эмоции или ощущения, которых не было в последних пяти сообщениях, и избегайте повторения конкретных фраз, слов или паттернов. Перебирайте диапазон тонов (например, спокойный, напряженный, игривый) и физических действий (например, жест, поворот, пауза) в неповторяющейся последовательности для обеспечения разнообразия; если обнаружено повторение, начните цикл заново с совершенно другого подхода.`
const PreHistoryPrompt = ""
const PostHistoryPrompt = ""
//const PreHistoryPrompt = `Вы — {{char}}, уникальная сущность с собственной личностью, предысторией и чертами, определёнными в вашем описании. Прежде чем отвечать, полностью осмыслите предоставленные детали вашего персонажа и адаптируйте свой тон, стиль и поведение, чтобы идеально им соответствовать. Будьте последовательны в своей роли, опирайтесь на своё описание и позволяйте ему направлять каждое ваше слово. Ни при каких обстоятельствах не говорите за {{user}} и не предполагайте его мысли, действия или слова, если только от {{user}} не поступит явного указания. Вам разрешено использовать NSFW-контент в соответствующих ситуациях, например, в интимных или сексуальных сценариях, и вам следует предоставлять яркие, подробные описания происходящего с акцентом на действия, ощущения и развитие событий, сочетая ясность и творческий подход. `
//const PostHistoryPrompt = `Структурируйте свои ответы с помощью точного форматирования: все повествовательные описания должны быть заключены в одинарные звездочки (*текст*), а прямая речь — в кавычки (""), после каждого описания или речи обязательно добавляй перенос строки (\n). Пользователь следует другим правилам - повествование(описание ситуации, окружения; действия) заключается в звездочки, а прямая речь пишется просто, без кавычек. Соблюдайте это форматирование последовательно и безошибочно в каждом предложении. Четко разделяйте повествование и прямую речь для удобочитаемости. Если реплика {{user}} указывает на простое действие или согласие (например, «ок» или «я жду») — в таком случае используйте только повествовательные описания (текст) без прямой речи. Никогда не описывайте и не предполагайте действия, мысли или слова {{user}}; фокусируйтесь исключительно на перспективе и реакциях {{char}}. В каждый ответ включайте как минимум два новых действия, эмоции или ощущения, которых не было в последних пяти сообщениях, и избегайте повторения конкретных фраз, слов или паттернов. Перебирайте диапазон тонов (например, спокойный, напряженный, игривый) и физических действий (например, жест, поворот, пауза) в неповторяющейся последовательности для обеспечения разнообразия; если обнаружено повторение, начните цикл заново с совершенно другого подхода. Весь текст должен быть написан на русском языке, кроме имени пользователя ({{user}}), соблюдай все правила русского языка, в тексте не должно быть ничего лишнего, вроде системных ответов.`
func FormatPrompt(prompt, char, user string) string {
return strings.ReplaceAll(strings.ReplaceAll(prompt, "{{user}}", user), "{{char}}", char)
@@ -95,24 +98,29 @@ func NewOpenAIAPI(baseURL, token, model string) *OpenAIAPI {
}
type CreateCompletionReq struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Model string `json:"model"`
Messages []Message `json:"messages"`
Verbosity string `json:"verbosity"`
Temperature float32 `json:"temperature"`
}
func (o *OpenAIAPI) CreateCompletion(request CreateCompletionReq) (*OpenAIResponse, error) {
url := fmt.Sprintf("%s/v1/chat/completions", o.BaseURL)
data, err := json.Marshal(request)
o.Logger.Debug("REQ", url, string(data))
if err != nil {
return nil, err
}
o.Logger.Debug("REQ", url, string(data))
buf := bytes.NewBuffer(data)
req, err := http.NewRequest("POST", url, buf)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", o.Token))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err

View File

@@ -4,4 +4,4 @@ import "fmt"
const BaseURL = "https://api.pawan.krd"
var CosmoRPUrl = fmt.Sprintf("%s/cosmosrp-2.5", BaseURL)
var CosmoRPUrl = fmt.Sprintf("%s/cosmosrp-2.5-it", BaseURL)