diff --git a/internal/api/chat/create_conversation_message_stream_v2.go b/internal/api/chat/create_conversation_message_stream_v2.go index b82adf5d..85f68c99 100644 --- a/internal/api/chat/create_conversation_message_stream_v2.go +++ b/internal/api/chat/create_conversation_message_stream_v2.go @@ -8,6 +8,7 @@ import ( "paperdebugger/internal/models" "paperdebugger/internal/services" chatv2 "paperdebugger/pkg/gen/api/chat/v2" + "strings" "github.com/google/uuid" "github.com/openai/openai-go/v3" @@ -276,9 +277,40 @@ func (s *ChatServerV2) CreateConversationMessageStream( return s.sendStreamError(stream, err) } + // Check if user has an API key for requested model + var llmProvider *models.LLMProviderConfig + var customModel *models.CustomModel + customModel = nil + for i := range settings.CustomModels { + if settings.CustomModels[i].Slug == modelSlug { + customModel = &settings.CustomModels[i] + } + } + // Usage is the same as ChatCompletion, just passing the stream parameter - llmProvider := &models.LLMProviderConfig{ - APIKey: settings.OpenAIAPIKey, + + if customModel == nil { + // User did not specify API key for this model + llmProvider = &models.LLMProviderConfig{ + APIKey: "", + IsCustomModel: false, + } + } else { + customModel.BaseUrl = strings.ToLower(customModel.BaseUrl) + + if strings.Contains(customModel.BaseUrl, "paperdebugger.com") { + customModel.BaseUrl = "" + } + if !strings.HasPrefix(customModel.BaseUrl, "https://") { + customModel.BaseUrl = strings.Replace(customModel.BaseUrl, "http://", "", 1) + customModel.BaseUrl = "https://" + customModel.BaseUrl + } + + llmProvider = &models.LLMProviderConfig{ + APIKey: customModel.APIKey, + Endpoint: customModel.BaseUrl, + IsCustomModel: true, + } } openaiChatHistory, inappChatHistory, err := s.aiClientV2.ChatCompletionStreamV2(ctx, stream, conversation.ID.Hex(), modelSlug, conversation.OpenaiChatHistoryCompletion, llmProvider) @@ -307,7 +339,7 @@ func (s *ChatServerV2) CreateConversationMessageStream( for i, bsonMsg := range conversation.InappChatHistory { protoMessages[i] = mapper.BSONToChatMessageV2(bsonMsg) } - title, err := s.aiClientV2.GetConversationTitleV2(ctx, protoMessages, llmProvider) + title, err := s.aiClientV2.GetConversationTitleV2(ctx, protoMessages, llmProvider, modelSlug) if err != nil { s.logger.Error("Failed to get conversation title", "error", err, "conversationID", conversation.ID.Hex()) return diff --git a/internal/api/chat/list_supported_models_v2.go b/internal/api/chat/list_supported_models_v2.go index 1fb54575..5db6fb6f 100644 --- a/internal/api/chat/list_supported_models_v2.go +++ b/internal/api/chat/list_supported_models_v2.go @@ -2,7 +2,6 @@ package chat import ( "context" - "strings" "paperdebugger/internal/libs/contextutil" chatv2 "paperdebugger/pkg/gen/api/chat/v2" @@ -220,22 +219,24 @@ func (s *ChatServerV2) ListSupportedModels( return nil, err } - hasOwnAPIKey := strings.TrimSpace(settings.OpenAIAPIKey) != "" - var models []*chatv2.SupportedModel - for _, config := range allModels { - // Choose the appropriate slug based on whether user has their own API key. - // - // Some models are only available via OpenRouter; for those, slugOpenAI may be empty. - // In that case, keep using the OpenRouter slug to avoid returning an empty model slug. - slug := config.slugOpenRouter - if hasOwnAPIKey && strings.TrimSpace(config.slugOpenAI) != "" { - slug = config.slugOpenAI - } + for _, model := range settings.CustomModels { + models = append(models, &chatv2.SupportedModel{ + Name: model.Name, + Slug: model.Slug, + TotalContext: int64(model.ContextWindow), + MaxOutput: int64(model.MaxOutput), + InputPrice: int64(model.InputPrice), + OutputPrice: int64(model.OutputPrice), + IsCustom: true, + }) + } + + for _, config := range allModels { model := &chatv2.SupportedModel{ Name: config.name, - Slug: slug, + Slug: config.slugOpenRouter, TotalContext: config.totalContext, MaxOutput: config.maxOutput, InputPrice: config.inputPrice, @@ -243,9 +244,8 @@ func (s *ChatServerV2) ListSupportedModels( } // If model requires own key but user hasn't provided one, mark as disabled - if config.requireOwnKey && !hasOwnAPIKey { - model.Disabled = true - model.DisabledReason = stringPtr("Requires your own OpenAI API key. Configure it in Settings.") + if config.requireOwnKey { + continue } models = append(models, model) diff --git a/internal/api/mapper/user.go b/internal/api/mapper/user.go index 78c98ef3..ea9a4f7b 100644 --- a/internal/api/mapper/user.go +++ b/internal/api/mapper/user.go @@ -3,26 +3,69 @@ package mapper import ( "paperdebugger/internal/models" userv1 "paperdebugger/pkg/gen/api/user/v1" + + "go.mongodb.org/mongo-driver/v2/bson" ) func MapProtoSettingsToModel(settings *userv1.Settings) *models.Settings { + // Map the slice of custom models + customModels := make([]models.CustomModel, len(settings.CustomModels)) + for i, m := range settings.CustomModels { + var id bson.ObjectID + + id, err := bson.ObjectIDFromHex(m.Id) + if err != nil { + id = bson.NewObjectID() + } + + customModels[i] = models.CustomModel{ + Id: id, + Slug: m.Slug, + Name: m.Name, + BaseUrl: m.BaseUrl, + APIKey: m.ApiKey, + ContextWindow: m.ContextWindow, + MaxOutput: m.MaxOutput, + InputPrice: m.InputPrice, + OutputPrice: m.OutputPrice, + } + } + return &models.Settings{ ShowShortcutsAfterSelection: settings.ShowShortcutsAfterSelection, FullWidthPaperDebuggerButton: settings.FullWidthPaperDebuggerButton, - EnableCitationSuggestion: settings.EnableCitationSuggestion, + EnableCitationSuggestion: settings.EnableCitationSuggestion, FullDocumentRag: settings.FullDocumentRag, ShowedOnboarding: settings.ShowedOnboarding, OpenAIAPIKey: settings.OpenaiApiKey, + CustomModels: customModels, } } func MapModelSettingsToProto(settings *models.Settings) *userv1.Settings { + // Map the slice back to Proto + customModels := make([]*userv1.CustomModel, len(settings.CustomModels)) + for i, m := range settings.CustomModels { + customModels[i] = &userv1.CustomModel{ + Id: m.Id.Hex(), + Slug: m.Slug, + Name: m.Name, + BaseUrl: m.BaseUrl, + ApiKey: m.APIKey, + ContextWindow: m.ContextWindow, + MaxOutput: m.MaxOutput, + InputPrice: m.InputPrice, + OutputPrice: m.OutputPrice, + } + } + return &userv1.Settings{ ShowShortcutsAfterSelection: settings.ShowShortcutsAfterSelection, FullWidthPaperDebuggerButton: settings.FullWidthPaperDebuggerButton, - EnableCitationSuggestion: settings.EnableCitationSuggestion, + EnableCitationSuggestion: settings.EnableCitationSuggestion, FullDocumentRag: settings.FullDocumentRag, ShowedOnboarding: settings.ShowedOnboarding, OpenaiApiKey: settings.OpenAIAPIKey, + CustomModels: customModels, } } diff --git a/internal/models/llm_provider.go b/internal/models/llm_provider.go index 06f6b0e5..0c085fda 100644 --- a/internal/models/llm_provider.go +++ b/internal/models/llm_provider.go @@ -2,10 +2,13 @@ package models // LLMProviderConfig holds the configuration for LLM API calls. // If both Endpoint and APIKey are empty, the system default will be used. +// If IsCustomModel is true, the user-requested slug with corresponding +// API keys and endpoint should be used. type LLMProviderConfig struct { - Endpoint string - APIKey string - ModelName string + Endpoint string + APIKey string + ModelName string + IsCustomModel bool } // IsCustom returns true if the user has configured custom LLM provider settings. diff --git a/internal/models/user.go b/internal/models/user.go index 22e03ad2..413be929 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -2,13 +2,26 @@ package models import "go.mongodb.org/mongo-driver/v2/bson" +type CustomModel struct { + Id bson.ObjectID `bson:"_id"` + Slug string `bson:"slug"` + Name string `bson:"name"` + BaseUrl string `bson:"base_url"` + APIKey string `bson:"api_key"` + ContextWindow int32 `bson:"context_window"` + MaxOutput int32 `bson:"max_output"` + InputPrice int32 `bson:"input_price"` + OutputPrice int32 `bson:"output_price"` +} + type Settings struct { - ShowShortcutsAfterSelection bool `bson:"show_shortcuts_after_selection"` - FullWidthPaperDebuggerButton bool `bson:"full_width_paper_debugger_button"` - EnableCitationSuggestion bool `bson:"enable_citation_suggestion"` - FullDocumentRag bool `bson:"full_document_rag"` - ShowedOnboarding bool `bson:"showed_onboarding"` - OpenAIAPIKey string `bson:"openai_api_key"` + ShowShortcutsAfterSelection bool `bson:"show_shortcuts_after_selection"` + FullWidthPaperDebuggerButton bool `bson:"full_width_paper_debugger_button"` + EnableCitationSuggestion bool `bson:"enable_citation_suggestion"` + FullDocumentRag bool `bson:"full_document_rag"` + ShowedOnboarding bool `bson:"showed_onboarding"` + OpenAIAPIKey string `bson:"openai_api_key"` + CustomModels []CustomModel `bson:"custom_models"` } type User struct { diff --git a/internal/services/toolkit/client/client_v2.go b/internal/services/toolkit/client/client_v2.go index 87a1e26a..d32e01f1 100644 --- a/internal/services/toolkit/client/client_v2.go +++ b/internal/services/toolkit/client/client_v2.go @@ -32,18 +32,20 @@ func (a *AIClientV2) GetOpenAIClient(llmConfig *models.LLMProviderConfig) *opena var Endpoint string = llmConfig.Endpoint var APIKey string = llmConfig.APIKey - if Endpoint == "" { - if APIKey != "" { - // User provided their own API key, use the OpenAI-compatible endpoint - Endpoint = a.cfg.OpenAIBaseURL // standard openai base url - } else { - // suffix needed for cloudflare gateway - Endpoint = a.cfg.InferenceBaseURL + "/openrouter" + if !llmConfig.IsCustomModel { + if Endpoint == "" { + if APIKey != "" { + // User provided their own API key, use the OpenAI-compatible endpoint + Endpoint = a.cfg.OpenAIBaseURL // standard openai base url + } else { + // suffix needed for cloudflare gateway + Endpoint = a.cfg.InferenceBaseURL + "/openrouter" + } } - } - if APIKey == "" { - APIKey = a.cfg.InferenceAPIKey + if APIKey == "" { + APIKey = a.cfg.InferenceAPIKey + } } opts := []option.RequestOption{ diff --git a/internal/services/toolkit/client/completion_v2.go b/internal/services/toolkit/client/completion_v2.go index f10082bf..47caaad4 100644 --- a/internal/services/toolkit/client/completion_v2.go +++ b/internal/services/toolkit/client/completion_v2.go @@ -66,7 +66,7 @@ func (a *AIClientV2) ChatCompletionStreamV2(ctx context.Context, callbackStream }() oaiClient := a.GetOpenAIClient(llmProvider) - params := getDefaultParamsV2(modelSlug, a.toolCallHandler.Registry) + params := getDefaultParamsV2(modelSlug, a.toolCallHandler.Registry, llmProvider.IsCustomModel) for { params.Messages = openaiChatHistory diff --git a/internal/services/toolkit/client/get_conversation_title_v2.go b/internal/services/toolkit/client/get_conversation_title_v2.go index 6c92f0c2..a58617d7 100644 --- a/internal/services/toolkit/client/get_conversation_title_v2.go +++ b/internal/services/toolkit/client/get_conversation_title_v2.go @@ -13,7 +13,7 @@ import ( "github.com/samber/lo" ) -func (a *AIClientV2) GetConversationTitleV2(ctx context.Context, inappChatHistory []*chatv2.Message, llmProvider *models.LLMProviderConfig) (string, error) { +func (a *AIClientV2) GetConversationTitleV2(ctx context.Context, inappChatHistory []*chatv2.Message, llmProvider *models.LLMProviderConfig, modelSlug string) (string, error) { messages := lo.Map(inappChatHistory, func(message *chatv2.Message, _ int) string { if _, ok := message.Payload.MessageType.(*chatv2.MessagePayload_Assistant); ok { return fmt.Sprintf("Assistant: %s", message.Payload.GetAssistant().GetContent()) @@ -29,7 +29,13 @@ func (a *AIClientV2) GetConversationTitleV2(ctx context.Context, inappChatHistor message := strings.Join(messages, "\n") message = fmt.Sprintf("%s\nBased on above conversation, generate a short, clear, and descriptive title that summarizes the main topic or purpose of the discussion. The title should be concise, specific, and use natural language. Avoid vague or generic titles. Use abbreviation and short words if possible. Use 3-5 words if possible. Give me the title only, no other text including any other words.", message) - _, resp, err := a.ChatCompletionV2(ctx, "gpt-5-nano", OpenAIChatHistory{ + // Default model if user is not using their own + modelToUse := "gpt-5-nano" + if llmProvider.IsCustomModel { + modelToUse = modelSlug + } + + _, resp, err := a.ChatCompletionV2(ctx, modelToUse, OpenAIChatHistory{ openai.SystemMessage("You are a helpful assistant that generates a title for a conversation."), openai.UserMessage(message), }, llmProvider) diff --git a/internal/services/toolkit/client/utils_v2.go b/internal/services/toolkit/client/utils_v2.go index 69e73071..7890c34c 100644 --- a/internal/services/toolkit/client/utils_v2.go +++ b/internal/services/toolkit/client/utils_v2.go @@ -53,7 +53,7 @@ func appendAssistantTextResponseV2(openaiChatHistory *OpenAIChatHistory, inappCh }) } -func getDefaultParamsV2(modelSlug string, toolRegistry *registry.ToolRegistryV2) openaiv3.ChatCompletionNewParams { +func getDefaultParamsV2(modelSlug string, toolRegistry *registry.ToolRegistryV2, isCustomModel bool) openaiv3.ChatCompletionNewParams { var reasoningModels = []string{ "gpt-5", "gpt-5-mini", @@ -66,6 +66,18 @@ func getDefaultParamsV2(modelSlug string, toolRegistry *registry.ToolRegistryV2) "o1", "codex-mini-latest", } + + // Other model providers generally do not support the Store param + if isCustomModel { + return openaiv3.ChatCompletionNewParams{ + Model: modelSlug, + Temperature: openaiv3.Float(0.7), + MaxCompletionTokens: openaiv3.Int(4000), + Tools: toolRegistry.GetTools(), + ParallelToolCalls: openaiv3.Bool(true), + } + } + for _, model := range reasoningModels { if strings.Contains(modelSlug, model) { return openaiv3.ChatCompletionNewParams{ diff --git a/pkg/gen/api/chat/v2/chat.pb.go b/pkg/gen/api/chat/v2/chat.pb.go index 0d312c55..2f66c2e6 100644 --- a/pkg/gen/api/chat/v2/chat.pb.go +++ b/pkg/gen/api/chat/v2/chat.pb.go @@ -7,13 +7,12 @@ package chatv2 import ( - reflect "reflect" - sync "sync" - unsafe "unsafe" - _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" ) const ( @@ -1035,6 +1034,7 @@ type SupportedModel struct { OutputPrice int64 `protobuf:"varint,6,opt,name=output_price,json=outputPrice,proto3" json:"output_price,omitempty"` // in cents per 1M tokens Disabled bool `protobuf:"varint,7,opt,name=disabled,proto3" json:"disabled,omitempty"` // If true, the model is disabled and cannot be used DisabledReason *string `protobuf:"bytes,8,opt,name=disabled_reason,json=disabledReason,proto3,oneof" json:"disabled_reason,omitempty"` // The reason why the model is disabled + IsCustom bool `protobuf:"varint,9,opt,name=is_custom,json=isCustom,proto3" json:"is_custom,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1125,6 +1125,13 @@ func (x *SupportedModel) GetDisabledReason() string { return "" } +func (x *SupportedModel) GetIsCustom() bool { + if x != nil { + return x.IsCustom + } + return false +} + type ListSupportedModelsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -2062,7 +2069,7 @@ const file_chat_v2_chat_proto_rawDesc = "" + "\fconversation\x18\x01 \x01(\v2\x15.chat.v2.ConversationR\fconversation\"D\n" + "\x19DeleteConversationRequest\x12'\n" + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\"\x1c\n" + - "\x1aDeleteConversationResponse\"\x9e\x02\n" + + "\x1aDeleteConversationResponse\"\xbb\x02\n" + "\x0eSupportedModel\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + "\x04slug\x18\x02 \x01(\tR\x04slug\x12#\n" + @@ -2073,7 +2080,8 @@ const file_chat_v2_chat_proto_rawDesc = "" + "inputPrice\x12!\n" + "\foutput_price\x18\x06 \x01(\x03R\voutputPrice\x12\x1a\n" + "\bdisabled\x18\a \x01(\bR\bdisabled\x12,\n" + - "\x0fdisabled_reason\x18\b \x01(\tH\x00R\x0edisabledReason\x88\x01\x01B\x12\n" + + "\x0fdisabled_reason\x18\b \x01(\tH\x00R\x0edisabledReason\x88\x01\x01\x12\x1b\n" + + "\tis_custom\x18\t \x01(\bR\bisCustomB\x12\n" + "\x10_disabled_reason\"\x1c\n" + "\x1aListSupportedModelsRequest\"N\n" + "\x1bListSupportedModelsResponse\x12/\n" + diff --git a/pkg/gen/api/user/v1/user.pb.go b/pkg/gen/api/user/v1/user.pb.go index 41752992..1f727599 100644 --- a/pkg/gen/api/user/v1/user.pb.go +++ b/pkg/gen/api/user/v1/user.pb.go @@ -615,6 +615,114 @@ func (*DeletePromptResponse) Descriptor() ([]byte, []int) { return file_user_v1_user_proto_rawDescGZIP(), []int{11} } +type CustomModel struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Slug string `protobuf:"bytes,2,opt,name=slug,proto3" json:"slug,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + BaseUrl string `protobuf:"bytes,4,opt,name=base_url,json=baseUrl,proto3" json:"base_url,omitempty"` + ApiKey string `protobuf:"bytes,5,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` + ContextWindow int32 `protobuf:"varint,6,opt,name=context_window,json=contextWindow,proto3" json:"context_window,omitempty"` + MaxOutput int32 `protobuf:"varint,7,opt,name=max_output,json=maxOutput,proto3" json:"max_output,omitempty"` + InputPrice int32 `protobuf:"varint,8,opt,name=input_price,json=inputPrice,proto3" json:"input_price,omitempty"` + OutputPrice int32 `protobuf:"varint,9,opt,name=output_price,json=outputPrice,proto3" json:"output_price,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CustomModel) Reset() { + *x = CustomModel{} + mi := &file_user_v1_user_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CustomModel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CustomModel) ProtoMessage() {} + +func (x *CustomModel) ProtoReflect() protoreflect.Message { + mi := &file_user_v1_user_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CustomModel.ProtoReflect.Descriptor instead. +func (*CustomModel) Descriptor() ([]byte, []int) { + return file_user_v1_user_proto_rawDescGZIP(), []int{12} +} + +func (x *CustomModel) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *CustomModel) GetSlug() string { + if x != nil { + return x.Slug + } + return "" +} + +func (x *CustomModel) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CustomModel) GetBaseUrl() string { + if x != nil { + return x.BaseUrl + } + return "" +} + +func (x *CustomModel) GetApiKey() string { + if x != nil { + return x.ApiKey + } + return "" +} + +func (x *CustomModel) GetContextWindow() int32 { + if x != nil { + return x.ContextWindow + } + return 0 +} + +func (x *CustomModel) GetMaxOutput() int32 { + if x != nil { + return x.MaxOutput + } + return 0 +} + +func (x *CustomModel) GetInputPrice() int32 { + if x != nil { + return x.InputPrice + } + return 0 +} + +func (x *CustomModel) GetOutputPrice() int32 { + if x != nil { + return x.OutputPrice + } + return 0 +} + type Settings struct { state protoimpl.MessageState `protogen:"open.v1"` ShowShortcutsAfterSelection bool `protobuf:"varint,1,opt,name=show_shortcuts_after_selection,json=showShortcutsAfterSelection,proto3" json:"show_shortcuts_after_selection,omitempty"` @@ -623,13 +731,14 @@ type Settings struct { FullDocumentRag bool `protobuf:"varint,4,opt,name=full_document_rag,json=fullDocumentRag,proto3" json:"full_document_rag,omitempty"` ShowedOnboarding bool `protobuf:"varint,5,opt,name=showed_onboarding,json=showedOnboarding,proto3" json:"showed_onboarding,omitempty"` OpenaiApiKey string `protobuf:"bytes,6,opt,name=openai_api_key,json=openaiApiKey,proto3" json:"openai_api_key,omitempty"` + CustomModels []*CustomModel `protobuf:"bytes,7,rep,name=custom_models,json=customModels,proto3" json:"custom_models,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *Settings) Reset() { *x = Settings{} - mi := &file_user_v1_user_proto_msgTypes[12] + mi := &file_user_v1_user_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -641,7 +750,7 @@ func (x *Settings) String() string { func (*Settings) ProtoMessage() {} func (x *Settings) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[12] + mi := &file_user_v1_user_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -654,7 +763,7 @@ func (x *Settings) ProtoReflect() protoreflect.Message { // Deprecated: Use Settings.ProtoReflect.Descriptor instead. func (*Settings) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{12} + return file_user_v1_user_proto_rawDescGZIP(), []int{13} } func (x *Settings) GetShowShortcutsAfterSelection() bool { @@ -699,6 +808,13 @@ func (x *Settings) GetOpenaiApiKey() string { return "" } +func (x *Settings) GetCustomModels() []*CustomModel { + if x != nil { + return x.CustomModels + } + return nil +} + type GetSettingsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -707,7 +823,7 @@ type GetSettingsRequest struct { func (x *GetSettingsRequest) Reset() { *x = GetSettingsRequest{} - mi := &file_user_v1_user_proto_msgTypes[13] + mi := &file_user_v1_user_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -719,7 +835,7 @@ func (x *GetSettingsRequest) String() string { func (*GetSettingsRequest) ProtoMessage() {} func (x *GetSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[13] + mi := &file_user_v1_user_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -732,7 +848,7 @@ func (x *GetSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSettingsRequest.ProtoReflect.Descriptor instead. func (*GetSettingsRequest) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{13} + return file_user_v1_user_proto_rawDescGZIP(), []int{14} } type GetSettingsResponse struct { @@ -744,7 +860,7 @@ type GetSettingsResponse struct { func (x *GetSettingsResponse) Reset() { *x = GetSettingsResponse{} - mi := &file_user_v1_user_proto_msgTypes[14] + mi := &file_user_v1_user_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -756,7 +872,7 @@ func (x *GetSettingsResponse) String() string { func (*GetSettingsResponse) ProtoMessage() {} func (x *GetSettingsResponse) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[14] + mi := &file_user_v1_user_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -769,7 +885,7 @@ func (x *GetSettingsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSettingsResponse.ProtoReflect.Descriptor instead. func (*GetSettingsResponse) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{14} + return file_user_v1_user_proto_rawDescGZIP(), []int{15} } func (x *GetSettingsResponse) GetSettings() *Settings { @@ -788,7 +904,7 @@ type UpdateSettingsRequest struct { func (x *UpdateSettingsRequest) Reset() { *x = UpdateSettingsRequest{} - mi := &file_user_v1_user_proto_msgTypes[15] + mi := &file_user_v1_user_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -800,7 +916,7 @@ func (x *UpdateSettingsRequest) String() string { func (*UpdateSettingsRequest) ProtoMessage() {} func (x *UpdateSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[15] + mi := &file_user_v1_user_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -813,7 +929,7 @@ func (x *UpdateSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateSettingsRequest.ProtoReflect.Descriptor instead. func (*UpdateSettingsRequest) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{15} + return file_user_v1_user_proto_rawDescGZIP(), []int{16} } func (x *UpdateSettingsRequest) GetSettings() *Settings { @@ -832,7 +948,7 @@ type UpdateSettingsResponse struct { func (x *UpdateSettingsResponse) Reset() { *x = UpdateSettingsResponse{} - mi := &file_user_v1_user_proto_msgTypes[16] + mi := &file_user_v1_user_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -844,7 +960,7 @@ func (x *UpdateSettingsResponse) String() string { func (*UpdateSettingsResponse) ProtoMessage() {} func (x *UpdateSettingsResponse) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[16] + mi := &file_user_v1_user_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -857,7 +973,7 @@ func (x *UpdateSettingsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateSettingsResponse.ProtoReflect.Descriptor instead. func (*UpdateSettingsResponse) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{16} + return file_user_v1_user_proto_rawDescGZIP(), []int{17} } func (x *UpdateSettingsResponse) GetSettings() *Settings { @@ -875,7 +991,7 @@ type ResetSettingsRequest struct { func (x *ResetSettingsRequest) Reset() { *x = ResetSettingsRequest{} - mi := &file_user_v1_user_proto_msgTypes[17] + mi := &file_user_v1_user_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -887,7 +1003,7 @@ func (x *ResetSettingsRequest) String() string { func (*ResetSettingsRequest) ProtoMessage() {} func (x *ResetSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[17] + mi := &file_user_v1_user_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -900,7 +1016,7 @@ func (x *ResetSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResetSettingsRequest.ProtoReflect.Descriptor instead. func (*ResetSettingsRequest) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{17} + return file_user_v1_user_proto_rawDescGZIP(), []int{18} } type ResetSettingsResponse struct { @@ -912,7 +1028,7 @@ type ResetSettingsResponse struct { func (x *ResetSettingsResponse) Reset() { *x = ResetSettingsResponse{} - mi := &file_user_v1_user_proto_msgTypes[18] + mi := &file_user_v1_user_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -924,7 +1040,7 @@ func (x *ResetSettingsResponse) String() string { func (*ResetSettingsResponse) ProtoMessage() {} func (x *ResetSettingsResponse) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[18] + mi := &file_user_v1_user_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -937,7 +1053,7 @@ func (x *ResetSettingsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResetSettingsResponse.ProtoReflect.Descriptor instead. func (*ResetSettingsResponse) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{18} + return file_user_v1_user_proto_rawDescGZIP(), []int{19} } func (x *ResetSettingsResponse) GetSettings() *Settings { @@ -955,7 +1071,7 @@ type GetUserInstructionsRequest struct { func (x *GetUserInstructionsRequest) Reset() { *x = GetUserInstructionsRequest{} - mi := &file_user_v1_user_proto_msgTypes[19] + mi := &file_user_v1_user_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -967,7 +1083,7 @@ func (x *GetUserInstructionsRequest) String() string { func (*GetUserInstructionsRequest) ProtoMessage() {} func (x *GetUserInstructionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[19] + mi := &file_user_v1_user_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -980,7 +1096,7 @@ func (x *GetUserInstructionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserInstructionsRequest.ProtoReflect.Descriptor instead. func (*GetUserInstructionsRequest) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{19} + return file_user_v1_user_proto_rawDescGZIP(), []int{20} } type GetUserInstructionsResponse struct { @@ -992,7 +1108,7 @@ type GetUserInstructionsResponse struct { func (x *GetUserInstructionsResponse) Reset() { *x = GetUserInstructionsResponse{} - mi := &file_user_v1_user_proto_msgTypes[20] + mi := &file_user_v1_user_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1004,7 +1120,7 @@ func (x *GetUserInstructionsResponse) String() string { func (*GetUserInstructionsResponse) ProtoMessage() {} func (x *GetUserInstructionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[20] + mi := &file_user_v1_user_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1017,7 +1133,7 @@ func (x *GetUserInstructionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUserInstructionsResponse.ProtoReflect.Descriptor instead. func (*GetUserInstructionsResponse) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{20} + return file_user_v1_user_proto_rawDescGZIP(), []int{21} } func (x *GetUserInstructionsResponse) GetInstructions() string { @@ -1036,7 +1152,7 @@ type UpsertUserInstructionsRequest struct { func (x *UpsertUserInstructionsRequest) Reset() { *x = UpsertUserInstructionsRequest{} - mi := &file_user_v1_user_proto_msgTypes[21] + mi := &file_user_v1_user_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1048,7 +1164,7 @@ func (x *UpsertUserInstructionsRequest) String() string { func (*UpsertUserInstructionsRequest) ProtoMessage() {} func (x *UpsertUserInstructionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[21] + mi := &file_user_v1_user_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1061,7 +1177,7 @@ func (x *UpsertUserInstructionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertUserInstructionsRequest.ProtoReflect.Descriptor instead. func (*UpsertUserInstructionsRequest) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{21} + return file_user_v1_user_proto_rawDescGZIP(), []int{22} } func (x *UpsertUserInstructionsRequest) GetInstructions() string { @@ -1080,7 +1196,7 @@ type UpsertUserInstructionsResponse struct { func (x *UpsertUserInstructionsResponse) Reset() { *x = UpsertUserInstructionsResponse{} - mi := &file_user_v1_user_proto_msgTypes[22] + mi := &file_user_v1_user_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1092,7 +1208,7 @@ func (x *UpsertUserInstructionsResponse) String() string { func (*UpsertUserInstructionsResponse) ProtoMessage() {} func (x *UpsertUserInstructionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_user_v1_user_proto_msgTypes[22] + mi := &file_user_v1_user_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1105,7 +1221,7 @@ func (x *UpsertUserInstructionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertUserInstructionsResponse.ProtoReflect.Descriptor instead. func (*UpsertUserInstructionsResponse) Descriptor() ([]byte, []int) { - return file_user_v1_user_proto_rawDescGZIP(), []int{22} + return file_user_v1_user_proto_rawDescGZIP(), []int{23} } func (x *UpsertUserInstructionsResponse) GetInstructions() string { @@ -1153,14 +1269,27 @@ const file_user_v1_user_proto_rawDesc = "" + "\x06prompt\x18\x01 \x01(\v2\x0f.user.v1.PromptR\x06prompt\"2\n" + "\x13DeletePromptRequest\x12\x1b\n" + "\tprompt_id\x18\x01 \x01(\tR\bpromptId\"\x16\n" + - "\x14DeletePromptResponse\"\xd4\x02\n" + + "\x14DeletePromptResponse\"\x83\x02\n" + + "\vCustomModel\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04slug\x18\x02 \x01(\tR\x04slug\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x19\n" + + "\bbase_url\x18\x04 \x01(\tR\abaseUrl\x12\x17\n" + + "\aapi_key\x18\x05 \x01(\tR\x06apiKey\x12%\n" + + "\x0econtext_window\x18\x06 \x01(\x05R\rcontextWindow\x12\x1d\n" + + "\n" + + "max_output\x18\a \x01(\x05R\tmaxOutput\x12\x1f\n" + + "\vinput_price\x18\b \x01(\x05R\n" + + "inputPrice\x12!\n" + + "\foutput_price\x18\t \x01(\x05R\voutputPrice\"\x8f\x03\n" + "\bSettings\x12C\n" + "\x1eshow_shortcuts_after_selection\x18\x01 \x01(\bR\x1bshowShortcutsAfterSelection\x12F\n" + " full_width_paper_debugger_button\x18\x02 \x01(\bR\x1cfullWidthPaperDebuggerButton\x12<\n" + "\x1aenable_citation_suggestion\x18\x03 \x01(\bR\x18enableCitationSuggestion\x12*\n" + "\x11full_document_rag\x18\x04 \x01(\bR\x0ffullDocumentRag\x12+\n" + "\x11showed_onboarding\x18\x05 \x01(\bR\x10showedOnboarding\x12$\n" + - "\x0eopenai_api_key\x18\x06 \x01(\tR\fopenaiApiKey\"\x14\n" + + "\x0eopenai_api_key\x18\x06 \x01(\tR\fopenaiApiKey\x129\n" + + "\rcustom_models\x18\a \x03(\v2\x14.user.v1.CustomModelR\fcustomModels\"\x14\n" + "\x12GetSettingsRequest\"D\n" + "\x13GetSettingsResponse\x12-\n" + "\bsettings\x18\x01 \x01(\v2\x11.user.v1.SettingsR\bsettings\"F\n" + @@ -1204,7 +1333,7 @@ func file_user_v1_user_proto_rawDescGZIP() []byte { return file_user_v1_user_proto_rawDescData } -var file_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 24) var file_user_v1_user_proto_goTypes = []any{ (*User)(nil), // 0: user.v1.User (*GetUserRequest)(nil), // 1: user.v1.GetUserRequest @@ -1218,55 +1347,57 @@ var file_user_v1_user_proto_goTypes = []any{ (*UpdatePromptResponse)(nil), // 9: user.v1.UpdatePromptResponse (*DeletePromptRequest)(nil), // 10: user.v1.DeletePromptRequest (*DeletePromptResponse)(nil), // 11: user.v1.DeletePromptResponse - (*Settings)(nil), // 12: user.v1.Settings - (*GetSettingsRequest)(nil), // 13: user.v1.GetSettingsRequest - (*GetSettingsResponse)(nil), // 14: user.v1.GetSettingsResponse - (*UpdateSettingsRequest)(nil), // 15: user.v1.UpdateSettingsRequest - (*UpdateSettingsResponse)(nil), // 16: user.v1.UpdateSettingsResponse - (*ResetSettingsRequest)(nil), // 17: user.v1.ResetSettingsRequest - (*ResetSettingsResponse)(nil), // 18: user.v1.ResetSettingsResponse - (*GetUserInstructionsRequest)(nil), // 19: user.v1.GetUserInstructionsRequest - (*GetUserInstructionsResponse)(nil), // 20: user.v1.GetUserInstructionsResponse - (*UpsertUserInstructionsRequest)(nil), // 21: user.v1.UpsertUserInstructionsRequest - (*UpsertUserInstructionsResponse)(nil), // 22: user.v1.UpsertUserInstructionsResponse - (*timestamppb.Timestamp)(nil), // 23: google.protobuf.Timestamp + (*CustomModel)(nil), // 12: user.v1.CustomModel + (*Settings)(nil), // 13: user.v1.Settings + (*GetSettingsRequest)(nil), // 14: user.v1.GetSettingsRequest + (*GetSettingsResponse)(nil), // 15: user.v1.GetSettingsResponse + (*UpdateSettingsRequest)(nil), // 16: user.v1.UpdateSettingsRequest + (*UpdateSettingsResponse)(nil), // 17: user.v1.UpdateSettingsResponse + (*ResetSettingsRequest)(nil), // 18: user.v1.ResetSettingsRequest + (*ResetSettingsResponse)(nil), // 19: user.v1.ResetSettingsResponse + (*GetUserInstructionsRequest)(nil), // 20: user.v1.GetUserInstructionsRequest + (*GetUserInstructionsResponse)(nil), // 21: user.v1.GetUserInstructionsResponse + (*UpsertUserInstructionsRequest)(nil), // 22: user.v1.UpsertUserInstructionsRequest + (*UpsertUserInstructionsResponse)(nil), // 23: user.v1.UpsertUserInstructionsResponse + (*timestamppb.Timestamp)(nil), // 24: google.protobuf.Timestamp } var file_user_v1_user_proto_depIdxs = []int32{ 0, // 0: user.v1.GetUserResponse.user:type_name -> user.v1.User - 23, // 1: user.v1.Prompt.created_at:type_name -> google.protobuf.Timestamp - 23, // 2: user.v1.Prompt.updated_at:type_name -> google.protobuf.Timestamp + 24, // 1: user.v1.Prompt.created_at:type_name -> google.protobuf.Timestamp + 24, // 2: user.v1.Prompt.updated_at:type_name -> google.protobuf.Timestamp 3, // 3: user.v1.ListPromptsResponse.prompts:type_name -> user.v1.Prompt 3, // 4: user.v1.CreatePromptResponse.prompt:type_name -> user.v1.Prompt 3, // 5: user.v1.UpdatePromptResponse.prompt:type_name -> user.v1.Prompt - 12, // 6: user.v1.GetSettingsResponse.settings:type_name -> user.v1.Settings - 12, // 7: user.v1.UpdateSettingsRequest.settings:type_name -> user.v1.Settings - 12, // 8: user.v1.UpdateSettingsResponse.settings:type_name -> user.v1.Settings - 12, // 9: user.v1.ResetSettingsResponse.settings:type_name -> user.v1.Settings - 1, // 10: user.v1.UserService.GetUser:input_type -> user.v1.GetUserRequest - 4, // 11: user.v1.UserService.ListPrompts:input_type -> user.v1.ListPromptsRequest - 6, // 12: user.v1.UserService.CreatePrompt:input_type -> user.v1.CreatePromptRequest - 8, // 13: user.v1.UserService.UpdatePrompt:input_type -> user.v1.UpdatePromptRequest - 19, // 14: user.v1.UserService.GetUserInstructions:input_type -> user.v1.GetUserInstructionsRequest - 21, // 15: user.v1.UserService.UpsertUserInstructions:input_type -> user.v1.UpsertUserInstructionsRequest - 10, // 16: user.v1.UserService.DeletePrompt:input_type -> user.v1.DeletePromptRequest - 13, // 17: user.v1.UserService.GetSettings:input_type -> user.v1.GetSettingsRequest - 15, // 18: user.v1.UserService.UpdateSettings:input_type -> user.v1.UpdateSettingsRequest - 17, // 19: user.v1.UserService.ResetSettings:input_type -> user.v1.ResetSettingsRequest - 2, // 20: user.v1.UserService.GetUser:output_type -> user.v1.GetUserResponse - 5, // 21: user.v1.UserService.ListPrompts:output_type -> user.v1.ListPromptsResponse - 7, // 22: user.v1.UserService.CreatePrompt:output_type -> user.v1.CreatePromptResponse - 9, // 23: user.v1.UserService.UpdatePrompt:output_type -> user.v1.UpdatePromptResponse - 20, // 24: user.v1.UserService.GetUserInstructions:output_type -> user.v1.GetUserInstructionsResponse - 22, // 25: user.v1.UserService.UpsertUserInstructions:output_type -> user.v1.UpsertUserInstructionsResponse - 11, // 26: user.v1.UserService.DeletePrompt:output_type -> user.v1.DeletePromptResponse - 14, // 27: user.v1.UserService.GetSettings:output_type -> user.v1.GetSettingsResponse - 16, // 28: user.v1.UserService.UpdateSettings:output_type -> user.v1.UpdateSettingsResponse - 18, // 29: user.v1.UserService.ResetSettings:output_type -> user.v1.ResetSettingsResponse - 20, // [20:30] is the sub-list for method output_type - 10, // [10:20] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 12, // 6: user.v1.Settings.custom_models:type_name -> user.v1.CustomModel + 13, // 7: user.v1.GetSettingsResponse.settings:type_name -> user.v1.Settings + 13, // 8: user.v1.UpdateSettingsRequest.settings:type_name -> user.v1.Settings + 13, // 9: user.v1.UpdateSettingsResponse.settings:type_name -> user.v1.Settings + 13, // 10: user.v1.ResetSettingsResponse.settings:type_name -> user.v1.Settings + 1, // 11: user.v1.UserService.GetUser:input_type -> user.v1.GetUserRequest + 4, // 12: user.v1.UserService.ListPrompts:input_type -> user.v1.ListPromptsRequest + 6, // 13: user.v1.UserService.CreatePrompt:input_type -> user.v1.CreatePromptRequest + 8, // 14: user.v1.UserService.UpdatePrompt:input_type -> user.v1.UpdatePromptRequest + 20, // 15: user.v1.UserService.GetUserInstructions:input_type -> user.v1.GetUserInstructionsRequest + 22, // 16: user.v1.UserService.UpsertUserInstructions:input_type -> user.v1.UpsertUserInstructionsRequest + 10, // 17: user.v1.UserService.DeletePrompt:input_type -> user.v1.DeletePromptRequest + 14, // 18: user.v1.UserService.GetSettings:input_type -> user.v1.GetSettingsRequest + 16, // 19: user.v1.UserService.UpdateSettings:input_type -> user.v1.UpdateSettingsRequest + 18, // 20: user.v1.UserService.ResetSettings:input_type -> user.v1.ResetSettingsRequest + 2, // 21: user.v1.UserService.GetUser:output_type -> user.v1.GetUserResponse + 5, // 22: user.v1.UserService.ListPrompts:output_type -> user.v1.ListPromptsResponse + 7, // 23: user.v1.UserService.CreatePrompt:output_type -> user.v1.CreatePromptResponse + 9, // 24: user.v1.UserService.UpdatePrompt:output_type -> user.v1.UpdatePromptResponse + 21, // 25: user.v1.UserService.GetUserInstructions:output_type -> user.v1.GetUserInstructionsResponse + 23, // 26: user.v1.UserService.UpsertUserInstructions:output_type -> user.v1.UpsertUserInstructionsResponse + 11, // 27: user.v1.UserService.DeletePrompt:output_type -> user.v1.DeletePromptResponse + 15, // 28: user.v1.UserService.GetSettings:output_type -> user.v1.GetSettingsResponse + 17, // 29: user.v1.UserService.UpdateSettings:output_type -> user.v1.UpdateSettingsResponse + 19, // 30: user.v1.UserService.ResetSettings:output_type -> user.v1.ResetSettingsResponse + 21, // [21:31] is the sub-list for method output_type + 11, // [11:21] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_user_v1_user_proto_init() } @@ -1280,7 +1411,7 @@ func file_user_v1_user_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_v1_user_proto_rawDesc), len(file_user_v1_user_proto_rawDesc)), NumEnums: 0, - NumMessages: 23, + NumMessages: 24, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/chat/v2/chat.proto b/proto/chat/v2/chat.proto index 779fe913..4ec44427 100644 --- a/proto/chat/v2/chat.proto +++ b/proto/chat/v2/chat.proto @@ -136,6 +136,7 @@ message SupportedModel { int64 output_price = 6; // in cents per 1M tokens bool disabled = 7; // If true, the model is disabled and cannot be used optional string disabled_reason = 8; // The reason why the model is disabled + bool is_custom = 9; } message ListSupportedModelsRequest { diff --git a/proto/user/v1/user.proto b/proto/user/v1/user.proto index fc7f02b5..9f45503a 100644 --- a/proto/user/v1/user.proto +++ b/proto/user/v1/user.proto @@ -114,6 +114,18 @@ message DeletePromptRequest { message DeletePromptResponse {} +message CustomModel { + string id = 1; + string slug = 2; + string name = 3; + string base_url = 4; + string api_key = 5; + int32 context_window = 6; + int32 max_output = 7; + int32 input_price = 8; + int32 output_price = 9; +} + message Settings { bool show_shortcuts_after_selection = 1; bool full_width_paper_debugger_button = 2; @@ -121,6 +133,7 @@ message Settings { bool full_document_rag = 4; bool showed_onboarding = 5; string openai_api_key = 6; + repeated CustomModel custom_models = 7; } message GetSettingsRequest {} diff --git a/webapp/_webapp/src/hooks/useLanguageModels.ts b/webapp/_webapp/src/hooks/useLanguageModels.ts index a45b3761..676104b3 100644 --- a/webapp/_webapp/src/hooks/useLanguageModels.ts +++ b/webapp/_webapp/src/hooks/useLanguageModels.ts @@ -14,6 +14,7 @@ export type Model = { outputPrice: number; disabled: boolean; disabledReason?: string; + isCustom: boolean; }; // Extract provider from model slug (e.g., "openai/gpt-4.1" -> "openai") @@ -33,6 +34,7 @@ const fallbackModels: Model[] = [ inputPrice: 200, outputPrice: 800, disabled: false, + isCustom: false, }, ]; @@ -46,6 +48,7 @@ const mapSupportedModelToModel = (supportedModel: SupportedModel): Model => ({ outputPrice: Number(supportedModel.outputPrice), disabled: supportedModel.disabled, disabledReason: supportedModel.disabledReason, + isCustom: supportedModel.isCustom, }); export const useLanguageModels = () => { @@ -55,7 +58,7 @@ export const useLanguageModels = () => { const models: Model[] = useMemo(() => { if (supportedModelsResponse?.models && supportedModelsResponse.models.length > 0) { - return supportedModelsResponse.models.map(mapSupportedModelToModel); + return supportedModelsResponse.models.map(mapSupportedModelToModel).filter((m) => !m.disabled || m.isCustom); } return fallbackModels; }, [supportedModelsResponse]); diff --git a/webapp/_webapp/src/pkg/gen/apiclient/chat/v2/chat_pb.ts b/webapp/_webapp/src/pkg/gen/apiclient/chat/v2/chat_pb.ts index 0cb75815..f270c782 100644 --- a/webapp/_webapp/src/pkg/gen/apiclient/chat/v2/chat_pb.ts +++ b/webapp/_webapp/src/pkg/gen/apiclient/chat/v2/chat_pb.ts @@ -11,7 +11,7 @@ import type { Message as Message$1 } from "@bufbuild/protobuf"; * Describes the file chat/v2/chat.proto. */ export const file_chat_v2_chat: GenFile = /*@__PURE__*/ - fileDesc("ChJjaGF0L3YyL2NoYXQucHJvdG8SB2NoYXQudjIiUAoTTWVzc2FnZVR5cGVUb29sQ2FsbBIMCgRuYW1lGAEgASgJEgwKBGFyZ3MYAiABKAkSDgoGcmVzdWx0GAMgASgJEg0KBWVycm9yGAQgASgJIkEKI01lc3NhZ2VUeXBlVG9vbENhbGxQcmVwYXJlQXJndW1lbnRzEgwKBG5hbWUYASABKAkSDAoEYXJncxgCIAEoCSIkChFNZXNzYWdlVHlwZVN5c3RlbRIPCgdjb250ZW50GAEgASgJImEKFE1lc3NhZ2VUeXBlQXNzaXN0YW50Eg8KB2NvbnRlbnQYASABKAkSEgoKbW9kZWxfc2x1ZxgCIAEoCRIWCglyZWFzb25pbmcYAyABKAlIAIgBAUIMCgpfcmVhc29uaW5nInoKD01lc3NhZ2VUeXBlVXNlchIPCgdjb250ZW50GAEgASgJEhoKDXNlbGVjdGVkX3RleHQYAiABKAlIAIgBARIYCgtzdXJyb3VuZGluZxgHIAEoCUgBiAEBQhAKDl9zZWxlY3RlZF90ZXh0Qg4KDF9zdXJyb3VuZGluZyIpChJNZXNzYWdlVHlwZVVua25vd24SEwoLZGVzY3JpcHRpb24YASABKAki5AIKDk1lc3NhZ2VQYXlsb2FkEiwKBnN5c3RlbRgBIAEoCzIaLmNoYXQudjIuTWVzc2FnZVR5cGVTeXN0ZW1IABIoCgR1c2VyGAIgASgLMhguY2hhdC52Mi5NZXNzYWdlVHlwZVVzZXJIABIyCglhc3Npc3RhbnQYAyABKAsyHS5jaGF0LnYyLk1lc3NhZ2VUeXBlQXNzaXN0YW50SAASUwobdG9vbF9jYWxsX3ByZXBhcmVfYXJndW1lbnRzGAQgASgLMiwuY2hhdC52Mi5NZXNzYWdlVHlwZVRvb2xDYWxsUHJlcGFyZUFyZ3VtZW50c0gAEjEKCXRvb2xfY2FsbBgFIAEoCzIcLmNoYXQudjIuTWVzc2FnZVR5cGVUb29sQ2FsbEgAEi4KB3Vua25vd24YBiABKAsyGy5jaGF0LnYyLk1lc3NhZ2VUeXBlVW5rbm93bkgAQg4KDG1lc3NhZ2VfdHlwZSJaCgdNZXNzYWdlEhIKCm1lc3NhZ2VfaWQYASABKAkSKAoHcGF5bG9hZBgCIAEoCzIXLmNoYXQudjIuTWVzc2FnZVBheWxvYWQSEQoJdGltZXN0YW1wGAMgASgDImEKDENvbnZlcnNhdGlvbhIKCgJpZBgBIAEoCRINCgV0aXRsZRgCIAEoCRISCgptb2RlbF9zbHVnGAMgASgJEiIKCG1lc3NhZ2VzGAQgAygLMhAuY2hhdC52Mi5NZXNzYWdlIkIKGExpc3RDb252ZXJzYXRpb25zUmVxdWVzdBIXCgpwcm9qZWN0X2lkGAEgASgJSACIAQFCDQoLX3Byb2plY3RfaWQiSQoZTGlzdENvbnZlcnNhdGlvbnNSZXNwb25zZRIsCg1jb252ZXJzYXRpb25zGAEgAygLMhUuY2hhdC52Mi5Db252ZXJzYXRpb24iMQoWR2V0Q29udmVyc2F0aW9uUmVxdWVzdBIXCg9jb252ZXJzYXRpb25faWQYASABKAkiRgoXR2V0Q29udmVyc2F0aW9uUmVzcG9uc2USKwoMY29udmVyc2F0aW9uGAEgASgLMhUuY2hhdC52Mi5Db252ZXJzYXRpb24iQwoZVXBkYXRlQ29udmVyc2F0aW9uUmVxdWVzdBIXCg9jb252ZXJzYXRpb25faWQYASABKAkSDQoFdGl0bGUYAiABKAkiSQoaVXBkYXRlQ29udmVyc2F0aW9uUmVzcG9uc2USKwoMY29udmVyc2F0aW9uGAEgASgLMhUuY2hhdC52Mi5Db252ZXJzYXRpb24iNAoZRGVsZXRlQ29udmVyc2F0aW9uUmVxdWVzdBIXCg9jb252ZXJzYXRpb25faWQYASABKAkiHAoaRGVsZXRlQ29udmVyc2F0aW9uUmVzcG9uc2UixgEKDlN1cHBvcnRlZE1vZGVsEgwKBG5hbWUYASABKAkSDAoEc2x1ZxgCIAEoCRIVCg10b3RhbF9jb250ZXh0GAMgASgDEhIKCm1heF9vdXRwdXQYBCABKAMSEwoLaW5wdXRfcHJpY2UYBSABKAMSFAoMb3V0cHV0X3ByaWNlGAYgASgDEhAKCGRpc2FibGVkGAcgASgIEhwKD2Rpc2FibGVkX3JlYXNvbhgIIAEoCUgAiAEBQhIKEF9kaXNhYmxlZF9yZWFzb24iHAoaTGlzdFN1cHBvcnRlZE1vZGVsc1JlcXVlc3QiRgobTGlzdFN1cHBvcnRlZE1vZGVsc1Jlc3BvbnNlEicKBm1vZGVscxgBIAMoCzIXLmNoYXQudjIuU3VwcG9ydGVkTW9kZWwiQwoUU3RyZWFtSW5pdGlhbGl6YXRpb24SFwoPY29udmVyc2F0aW9uX2lkGAEgASgJEhIKCm1vZGVsX3NsdWcYAiABKAkiTwoPU3RyZWFtUGFydEJlZ2luEhIKCm1lc3NhZ2VfaWQYASABKAkSKAoHcGF5bG9hZBgDIAEoCzIXLmNoYXQudjIuTWVzc2FnZVBheWxvYWQiMQoMTWVzc2FnZUNodW5rEhIKCm1lc3NhZ2VfaWQYASABKAkSDQoFZGVsdGEYAiABKAkiMwoOUmVhc29uaW5nQ2h1bmsSEgoKbWVzc2FnZV9pZBgBIAEoCRINCgVkZWx0YRgCIAEoCSI6ChNJbmNvbXBsZXRlSW5kaWNhdG9yEg4KBnJlYXNvbhgBIAEoCRITCgtyZXNwb25zZV9pZBgCIAEoCSJNCg1TdHJlYW1QYXJ0RW5kEhIKCm1lc3NhZ2VfaWQYASABKAkSKAoHcGF5bG9hZBgDIAEoCzIXLmNoYXQudjIuTWVzc2FnZVBheWxvYWQiLQoSU3RyZWFtRmluYWxpemF0aW9uEhcKD2NvbnZlcnNhdGlvbl9pZBgBIAEoCSIkCgtTdHJlYW1FcnJvchIVCg1lcnJvcl9tZXNzYWdlGAEgASgJIssCCiZDcmVhdGVDb252ZXJzYXRpb25NZXNzYWdlU3RyZWFtUmVxdWVzdBISCgpwcm9qZWN0X2lkGAEgASgJEhwKD2NvbnZlcnNhdGlvbl9pZBgCIAEoCUgAiAEBEhIKCm1vZGVsX3NsdWcYAyABKAkSFAoMdXNlcl9tZXNzYWdlGAQgASgJEh8KEnVzZXJfc2VsZWN0ZWRfdGV4dBgFIAEoCUgBiAEBEjkKEWNvbnZlcnNhdGlvbl90eXBlGAYgASgOMhkuY2hhdC52Mi5Db252ZXJzYXRpb25UeXBlSAKIAQESGAoLc3Vycm91bmRpbmcYCCABKAlIA4gBAUISChBfY29udmVyc2F0aW9uX2lkQhUKE191c2VyX3NlbGVjdGVkX3RleHRCFAoSX2NvbnZlcnNhdGlvbl90eXBlQg4KDF9zdXJyb3VuZGluZyLzAwonQ3JlYXRlQ29udmVyc2F0aW9uTWVzc2FnZVN0cmVhbVJlc3BvbnNlEj4KFXN0cmVhbV9pbml0aWFsaXphdGlvbhgBIAEoCzIdLmNoYXQudjIuU3RyZWFtSW5pdGlhbGl6YXRpb25IABI1ChFzdHJlYW1fcGFydF9iZWdpbhgCIAEoCzIYLmNoYXQudjIuU3RyZWFtUGFydEJlZ2luSAASLgoNbWVzc2FnZV9jaHVuaxgDIAEoCzIVLmNoYXQudjIuTWVzc2FnZUNodW5rSAASPAoUaW5jb21wbGV0ZV9pbmRpY2F0b3IYBCABKAsyHC5jaGF0LnYyLkluY29tcGxldGVJbmRpY2F0b3JIABIxCg9zdHJlYW1fcGFydF9lbmQYBSABKAsyFi5jaGF0LnYyLlN0cmVhbVBhcnRFbmRIABI6ChNzdHJlYW1fZmluYWxpemF0aW9uGAYgASgLMhsuY2hhdC52Mi5TdHJlYW1GaW5hbGl6YXRpb25IABIsCgxzdHJlYW1fZXJyb3IYByABKAsyFC5jaGF0LnYyLlN0cmVhbUVycm9ySAASMgoPcmVhc29uaW5nX2NodW5rGAggASgLMhcuY2hhdC52Mi5SZWFzb25pbmdDaHVua0gAQhIKEHJlc3BvbnNlX3BheWxvYWQiPgoWR2V0Q2l0YXRpb25LZXlzUmVxdWVzdBIQCghzZW50ZW5jZRgBIAEoCRISCgpwcm9qZWN0X2lkGAIgASgJIjAKF0dldENpdGF0aW9uS2V5c1Jlc3BvbnNlEhUKDWNpdGF0aW9uX2tleXMYASADKAkqUgoQQ29udmVyc2F0aW9uVHlwZRIhCh1DT05WRVJTQVRJT05fVFlQRV9VTlNQRUNJRklFRBAAEhsKF0NPTlZFUlNBVElPTl9UWVBFX0RFQlVHEAEypwgKC0NoYXRTZXJ2aWNlEoMBChFMaXN0Q29udmVyc2F0aW9ucxIhLmNoYXQudjIuTGlzdENvbnZlcnNhdGlvbnNSZXF1ZXN0GiIuY2hhdC52Mi5MaXN0Q29udmVyc2F0aW9uc1Jlc3BvbnNlIieC0+STAiESHy9fcGQvYXBpL3YyL2NoYXRzL2NvbnZlcnNhdGlvbnMSjwEKD0dldENvbnZlcnNhdGlvbhIfLmNoYXQudjIuR2V0Q29udmVyc2F0aW9uUmVxdWVzdBogLmNoYXQudjIuR2V0Q29udmVyc2F0aW9uUmVzcG9uc2UiOYLT5JMCMxIxL19wZC9hcGkvdjIvY2hhdHMvY29udmVyc2F0aW9ucy97Y29udmVyc2F0aW9uX2lkfRLCAQofQ3JlYXRlQ29udmVyc2F0aW9uTWVzc2FnZVN0cmVhbRIvLmNoYXQudjIuQ3JlYXRlQ29udmVyc2F0aW9uTWVzc2FnZVN0cmVhbVJlcXVlc3QaMC5jaGF0LnYyLkNyZWF0ZUNvbnZlcnNhdGlvbk1lc3NhZ2VTdHJlYW1SZXNwb25zZSI6gtPkkwI0OgEqIi8vX3BkL2FwaS92Mi9jaGF0cy9jb252ZXJzYXRpb25zL21lc3NhZ2VzL3N0cmVhbTABEpsBChJVcGRhdGVDb252ZXJzYXRpb24SIi5jaGF0LnYyLlVwZGF0ZUNvbnZlcnNhdGlvblJlcXVlc3QaIy5jaGF0LnYyLlVwZGF0ZUNvbnZlcnNhdGlvblJlc3BvbnNlIjyC0+STAjY6ASoyMS9fcGQvYXBpL3YyL2NoYXRzL2NvbnZlcnNhdGlvbnMve2NvbnZlcnNhdGlvbl9pZH0SmAEKEkRlbGV0ZUNvbnZlcnNhdGlvbhIiLmNoYXQudjIuRGVsZXRlQ29udmVyc2F0aW9uUmVxdWVzdBojLmNoYXQudjIuRGVsZXRlQ29udmVyc2F0aW9uUmVzcG9uc2UiOYLT5JMCMyoxL19wZC9hcGkvdjIvY2hhdHMvY29udmVyc2F0aW9ucy97Y29udmVyc2F0aW9uX2lkfRKCAQoTTGlzdFN1cHBvcnRlZE1vZGVscxIjLmNoYXQudjIuTGlzdFN1cHBvcnRlZE1vZGVsc1JlcXVlc3QaJC5jaGF0LnYyLkxpc3RTdXBwb3J0ZWRNb2RlbHNSZXNwb25zZSIggtPkkwIaEhgvX3BkL2FwaS92Mi9jaGF0cy9tb2RlbHMSfQoPR2V0Q2l0YXRpb25LZXlzEh8uY2hhdC52Mi5HZXRDaXRhdGlvbktleXNSZXF1ZXN0GiAuY2hhdC52Mi5HZXRDaXRhdGlvbktleXNSZXNwb25zZSIngtPkkwIhEh8vX3BkL2FwaS92Mi9jaGF0cy9jaXRhdGlvbi1rZXlzQn8KC2NvbS5jaGF0LnYyQglDaGF0UHJvdG9QAVoocGFwZXJkZWJ1Z2dlci9wa2cvZ2VuL2FwaS9jaGF0L3YyO2NoYXR2MqICA0NYWKoCB0NoYXQuVjLKAgdDaGF0XFYy4gITQ2hhdFxWMlxHUEJNZXRhZGF0YeoCCENoYXQ6OlYyYgZwcm90bzM", [file_google_api_annotations]); + fileDesc("ChJjaGF0L3YyL2NoYXQucHJvdG8SB2NoYXQudjIiUAoTTWVzc2FnZVR5cGVUb29sQ2FsbBIMCgRuYW1lGAEgASgJEgwKBGFyZ3MYAiABKAkSDgoGcmVzdWx0GAMgASgJEg0KBWVycm9yGAQgASgJIkEKI01lc3NhZ2VUeXBlVG9vbENhbGxQcmVwYXJlQXJndW1lbnRzEgwKBG5hbWUYASABKAkSDAoEYXJncxgCIAEoCSIkChFNZXNzYWdlVHlwZVN5c3RlbRIPCgdjb250ZW50GAEgASgJImEKFE1lc3NhZ2VUeXBlQXNzaXN0YW50Eg8KB2NvbnRlbnQYASABKAkSEgoKbW9kZWxfc2x1ZxgCIAEoCRIWCglyZWFzb25pbmcYAyABKAlIAIgBAUIMCgpfcmVhc29uaW5nInoKD01lc3NhZ2VUeXBlVXNlchIPCgdjb250ZW50GAEgASgJEhoKDXNlbGVjdGVkX3RleHQYAiABKAlIAIgBARIYCgtzdXJyb3VuZGluZxgHIAEoCUgBiAEBQhAKDl9zZWxlY3RlZF90ZXh0Qg4KDF9zdXJyb3VuZGluZyIpChJNZXNzYWdlVHlwZVVua25vd24SEwoLZGVzY3JpcHRpb24YASABKAki5AIKDk1lc3NhZ2VQYXlsb2FkEiwKBnN5c3RlbRgBIAEoCzIaLmNoYXQudjIuTWVzc2FnZVR5cGVTeXN0ZW1IABIoCgR1c2VyGAIgASgLMhguY2hhdC52Mi5NZXNzYWdlVHlwZVVzZXJIABIyCglhc3Npc3RhbnQYAyABKAsyHS5jaGF0LnYyLk1lc3NhZ2VUeXBlQXNzaXN0YW50SAASUwobdG9vbF9jYWxsX3ByZXBhcmVfYXJndW1lbnRzGAQgASgLMiwuY2hhdC52Mi5NZXNzYWdlVHlwZVRvb2xDYWxsUHJlcGFyZUFyZ3VtZW50c0gAEjEKCXRvb2xfY2FsbBgFIAEoCzIcLmNoYXQudjIuTWVzc2FnZVR5cGVUb29sQ2FsbEgAEi4KB3Vua25vd24YBiABKAsyGy5jaGF0LnYyLk1lc3NhZ2VUeXBlVW5rbm93bkgAQg4KDG1lc3NhZ2VfdHlwZSJaCgdNZXNzYWdlEhIKCm1lc3NhZ2VfaWQYASABKAkSKAoHcGF5bG9hZBgCIAEoCzIXLmNoYXQudjIuTWVzc2FnZVBheWxvYWQSEQoJdGltZXN0YW1wGAMgASgDImEKDENvbnZlcnNhdGlvbhIKCgJpZBgBIAEoCRINCgV0aXRsZRgCIAEoCRISCgptb2RlbF9zbHVnGAMgASgJEiIKCG1lc3NhZ2VzGAQgAygLMhAuY2hhdC52Mi5NZXNzYWdlIkIKGExpc3RDb252ZXJzYXRpb25zUmVxdWVzdBIXCgpwcm9qZWN0X2lkGAEgASgJSACIAQFCDQoLX3Byb2plY3RfaWQiSQoZTGlzdENvbnZlcnNhdGlvbnNSZXNwb25zZRIsCg1jb252ZXJzYXRpb25zGAEgAygLMhUuY2hhdC52Mi5Db252ZXJzYXRpb24iMQoWR2V0Q29udmVyc2F0aW9uUmVxdWVzdBIXCg9jb252ZXJzYXRpb25faWQYASABKAkiRgoXR2V0Q29udmVyc2F0aW9uUmVzcG9uc2USKwoMY29udmVyc2F0aW9uGAEgASgLMhUuY2hhdC52Mi5Db252ZXJzYXRpb24iQwoZVXBkYXRlQ29udmVyc2F0aW9uUmVxdWVzdBIXCg9jb252ZXJzYXRpb25faWQYASABKAkSDQoFdGl0bGUYAiABKAkiSQoaVXBkYXRlQ29udmVyc2F0aW9uUmVzcG9uc2USKwoMY29udmVyc2F0aW9uGAEgASgLMhUuY2hhdC52Mi5Db252ZXJzYXRpb24iNAoZRGVsZXRlQ29udmVyc2F0aW9uUmVxdWVzdBIXCg9jb252ZXJzYXRpb25faWQYASABKAkiHAoaRGVsZXRlQ29udmVyc2F0aW9uUmVzcG9uc2Ui2QEKDlN1cHBvcnRlZE1vZGVsEgwKBG5hbWUYASABKAkSDAoEc2x1ZxgCIAEoCRIVCg10b3RhbF9jb250ZXh0GAMgASgDEhIKCm1heF9vdXRwdXQYBCABKAMSEwoLaW5wdXRfcHJpY2UYBSABKAMSFAoMb3V0cHV0X3ByaWNlGAYgASgDEhAKCGRpc2FibGVkGAcgASgIEhwKD2Rpc2FibGVkX3JlYXNvbhgIIAEoCUgAiAEBEhEKCWlzX2N1c3RvbRgJIAEoCEISChBfZGlzYWJsZWRfcmVhc29uIhwKGkxpc3RTdXBwb3J0ZWRNb2RlbHNSZXF1ZXN0IkYKG0xpc3RTdXBwb3J0ZWRNb2RlbHNSZXNwb25zZRInCgZtb2RlbHMYASADKAsyFy5jaGF0LnYyLlN1cHBvcnRlZE1vZGVsIkMKFFN0cmVhbUluaXRpYWxpemF0aW9uEhcKD2NvbnZlcnNhdGlvbl9pZBgBIAEoCRISCgptb2RlbF9zbHVnGAIgASgJIk8KD1N0cmVhbVBhcnRCZWdpbhISCgptZXNzYWdlX2lkGAEgASgJEigKB3BheWxvYWQYAyABKAsyFy5jaGF0LnYyLk1lc3NhZ2VQYXlsb2FkIjEKDE1lc3NhZ2VDaHVuaxISCgptZXNzYWdlX2lkGAEgASgJEg0KBWRlbHRhGAIgASgJIjMKDlJlYXNvbmluZ0NodW5rEhIKCm1lc3NhZ2VfaWQYASABKAkSDQoFZGVsdGEYAiABKAkiOgoTSW5jb21wbGV0ZUluZGljYXRvchIOCgZyZWFzb24YASABKAkSEwoLcmVzcG9uc2VfaWQYAiABKAkiTQoNU3RyZWFtUGFydEVuZBISCgptZXNzYWdlX2lkGAEgASgJEigKB3BheWxvYWQYAyABKAsyFy5jaGF0LnYyLk1lc3NhZ2VQYXlsb2FkIi0KElN0cmVhbUZpbmFsaXphdGlvbhIXCg9jb252ZXJzYXRpb25faWQYASABKAkiJAoLU3RyZWFtRXJyb3ISFQoNZXJyb3JfbWVzc2FnZRgBIAEoCSLLAgomQ3JlYXRlQ29udmVyc2F0aW9uTWVzc2FnZVN0cmVhbVJlcXVlc3QSEgoKcHJvamVjdF9pZBgBIAEoCRIcCg9jb252ZXJzYXRpb25faWQYAiABKAlIAIgBARISCgptb2RlbF9zbHVnGAMgASgJEhQKDHVzZXJfbWVzc2FnZRgEIAEoCRIfChJ1c2VyX3NlbGVjdGVkX3RleHQYBSABKAlIAYgBARI5ChFjb252ZXJzYXRpb25fdHlwZRgGIAEoDjIZLmNoYXQudjIuQ29udmVyc2F0aW9uVHlwZUgCiAEBEhgKC3N1cnJvdW5kaW5nGAggASgJSAOIAQFCEgoQX2NvbnZlcnNhdGlvbl9pZEIVChNfdXNlcl9zZWxlY3RlZF90ZXh0QhQKEl9jb252ZXJzYXRpb25fdHlwZUIOCgxfc3Vycm91bmRpbmci8wMKJ0NyZWF0ZUNvbnZlcnNhdGlvbk1lc3NhZ2VTdHJlYW1SZXNwb25zZRI+ChVzdHJlYW1faW5pdGlhbGl6YXRpb24YASABKAsyHS5jaGF0LnYyLlN0cmVhbUluaXRpYWxpemF0aW9uSAASNQoRc3RyZWFtX3BhcnRfYmVnaW4YAiABKAsyGC5jaGF0LnYyLlN0cmVhbVBhcnRCZWdpbkgAEi4KDW1lc3NhZ2VfY2h1bmsYAyABKAsyFS5jaGF0LnYyLk1lc3NhZ2VDaHVua0gAEjwKFGluY29tcGxldGVfaW5kaWNhdG9yGAQgASgLMhwuY2hhdC52Mi5JbmNvbXBsZXRlSW5kaWNhdG9ySAASMQoPc3RyZWFtX3BhcnRfZW5kGAUgASgLMhYuY2hhdC52Mi5TdHJlYW1QYXJ0RW5kSAASOgoTc3RyZWFtX2ZpbmFsaXphdGlvbhgGIAEoCzIbLmNoYXQudjIuU3RyZWFtRmluYWxpemF0aW9uSAASLAoMc3RyZWFtX2Vycm9yGAcgASgLMhQuY2hhdC52Mi5TdHJlYW1FcnJvckgAEjIKD3JlYXNvbmluZ19jaHVuaxgIIAEoCzIXLmNoYXQudjIuUmVhc29uaW5nQ2h1bmtIAEISChByZXNwb25zZV9wYXlsb2FkIj4KFkdldENpdGF0aW9uS2V5c1JlcXVlc3QSEAoIc2VudGVuY2UYASABKAkSEgoKcHJvamVjdF9pZBgCIAEoCSIwChdHZXRDaXRhdGlvbktleXNSZXNwb25zZRIVCg1jaXRhdGlvbl9rZXlzGAEgAygJKlIKEENvbnZlcnNhdGlvblR5cGUSIQodQ09OVkVSU0FUSU9OX1RZUEVfVU5TUEVDSUZJRUQQABIbChdDT05WRVJTQVRJT05fVFlQRV9ERUJVRxABMqcICgtDaGF0U2VydmljZRKDAQoRTGlzdENvbnZlcnNhdGlvbnMSIS5jaGF0LnYyLkxpc3RDb252ZXJzYXRpb25zUmVxdWVzdBoiLmNoYXQudjIuTGlzdENvbnZlcnNhdGlvbnNSZXNwb25zZSIngtPkkwIhEh8vX3BkL2FwaS92Mi9jaGF0cy9jb252ZXJzYXRpb25zEo8BCg9HZXRDb252ZXJzYXRpb24SHy5jaGF0LnYyLkdldENvbnZlcnNhdGlvblJlcXVlc3QaIC5jaGF0LnYyLkdldENvbnZlcnNhdGlvblJlc3BvbnNlIjmC0+STAjMSMS9fcGQvYXBpL3YyL2NoYXRzL2NvbnZlcnNhdGlvbnMve2NvbnZlcnNhdGlvbl9pZH0SwgEKH0NyZWF0ZUNvbnZlcnNhdGlvbk1lc3NhZ2VTdHJlYW0SLy5jaGF0LnYyLkNyZWF0ZUNvbnZlcnNhdGlvbk1lc3NhZ2VTdHJlYW1SZXF1ZXN0GjAuY2hhdC52Mi5DcmVhdGVDb252ZXJzYXRpb25NZXNzYWdlU3RyZWFtUmVzcG9uc2UiOoLT5JMCNDoBKiIvL19wZC9hcGkvdjIvY2hhdHMvY29udmVyc2F0aW9ucy9tZXNzYWdlcy9zdHJlYW0wARKbAQoSVXBkYXRlQ29udmVyc2F0aW9uEiIuY2hhdC52Mi5VcGRhdGVDb252ZXJzYXRpb25SZXF1ZXN0GiMuY2hhdC52Mi5VcGRhdGVDb252ZXJzYXRpb25SZXNwb25zZSI8gtPkkwI2OgEqMjEvX3BkL2FwaS92Mi9jaGF0cy9jb252ZXJzYXRpb25zL3tjb252ZXJzYXRpb25faWR9EpgBChJEZWxldGVDb252ZXJzYXRpb24SIi5jaGF0LnYyLkRlbGV0ZUNvbnZlcnNhdGlvblJlcXVlc3QaIy5jaGF0LnYyLkRlbGV0ZUNvbnZlcnNhdGlvblJlc3BvbnNlIjmC0+STAjMqMS9fcGQvYXBpL3YyL2NoYXRzL2NvbnZlcnNhdGlvbnMve2NvbnZlcnNhdGlvbl9pZH0SggEKE0xpc3RTdXBwb3J0ZWRNb2RlbHMSIy5jaGF0LnYyLkxpc3RTdXBwb3J0ZWRNb2RlbHNSZXF1ZXN0GiQuY2hhdC52Mi5MaXN0U3VwcG9ydGVkTW9kZWxzUmVzcG9uc2UiIILT5JMCGhIYL19wZC9hcGkvdjIvY2hhdHMvbW9kZWxzEn0KD0dldENpdGF0aW9uS2V5cxIfLmNoYXQudjIuR2V0Q2l0YXRpb25LZXlzUmVxdWVzdBogLmNoYXQudjIuR2V0Q2l0YXRpb25LZXlzUmVzcG9uc2UiJ4LT5JMCIRIfL19wZC9hcGkvdjIvY2hhdHMvY2l0YXRpb24ta2V5c0J/Cgtjb20uY2hhdC52MkIJQ2hhdFByb3RvUAFaKHBhcGVyZGVidWdnZXIvcGtnL2dlbi9hcGkvY2hhdC92MjtjaGF0djKiAgNDWFiqAgdDaGF0LlYyygIHQ2hhdFxWMuICE0NoYXRcVjJcR1BCTWV0YWRhdGHqAghDaGF0OjpWMmIGcHJvdG8z", [file_google_api_annotations]); /** * @generated from message chat.v2.MessageTypeToolCall @@ -469,6 +469,11 @@ export type SupportedModel = Message$1<"chat.v2.SupportedModel"> & { * @generated from field: optional string disabled_reason = 8; */ disabledReason?: string; + + /** + * @generated from field: bool is_custom = 9; + */ + isCustom: boolean; }; /** diff --git a/webapp/_webapp/src/pkg/gen/apiclient/user/v1/user_pb.ts b/webapp/_webapp/src/pkg/gen/apiclient/user/v1/user_pb.ts index 38f267c6..5ff1e27a 100644 --- a/webapp/_webapp/src/pkg/gen/apiclient/user/v1/user_pb.ts +++ b/webapp/_webapp/src/pkg/gen/apiclient/user/v1/user_pb.ts @@ -13,7 +13,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file user/v1/user.proto. */ export const file_user_v1_user: GenFile = /*@__PURE__*/ - fileDesc("ChJ1c2VyL3YxL3VzZXIucHJvdG8SB3VzZXIudjEiQAoEVXNlchIKCgJpZBgBIAEoCRINCgVlbWFpbBgCIAEoCRIMCgRuYW1lGAMgASgJEg8KB3BpY3R1cmUYBCABKAkiEAoOR2V0VXNlclJlcXVlc3QiLgoPR2V0VXNlclJlc3BvbnNlEhsKBHVzZXIYASABKAsyDS51c2VyLnYxLlVzZXIirAEKBlByb21wdBIKCgJpZBgBIAEoCRIuCgpjcmVhdGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBINCgV0aXRsZRgEIAEoCRIPCgdjb250ZW50GAUgASgJEhYKDmlzX3VzZXJfcHJvbXB0GAYgASgIIhQKEkxpc3RQcm9tcHRzUmVxdWVzdCI3ChNMaXN0UHJvbXB0c1Jlc3BvbnNlEiAKB3Byb21wdHMYASADKAsyDy51c2VyLnYxLlByb21wdCI1ChNDcmVhdGVQcm9tcHRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEg8KB2NvbnRlbnQYAiABKAkiNwoUQ3JlYXRlUHJvbXB0UmVzcG9uc2USHwoGcHJvbXB0GAEgASgLMg8udXNlci52MS5Qcm9tcHQiSAoTVXBkYXRlUHJvbXB0UmVxdWVzdBIRCglwcm9tcHRfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDwoHY29udGVudBgDIAEoCSI3ChRVcGRhdGVQcm9tcHRSZXNwb25zZRIfCgZwcm9tcHQYASABKAsyDy51c2VyLnYxLlByb21wdCIoChNEZWxldGVQcm9tcHRSZXF1ZXN0EhEKCXByb21wdF9pZBgBIAEoCSIWChREZWxldGVQcm9tcHRSZXNwb25zZSLOAQoIU2V0dGluZ3MSJgoec2hvd19zaG9ydGN1dHNfYWZ0ZXJfc2VsZWN0aW9uGAEgASgIEigKIGZ1bGxfd2lkdGhfcGFwZXJfZGVidWdnZXJfYnV0dG9uGAIgASgIEiIKGmVuYWJsZV9jaXRhdGlvbl9zdWdnZXN0aW9uGAMgASgIEhkKEWZ1bGxfZG9jdW1lbnRfcmFnGAQgASgIEhkKEXNob3dlZF9vbmJvYXJkaW5nGAUgASgIEhYKDm9wZW5haV9hcGlfa2V5GAYgASgJIhQKEkdldFNldHRpbmdzUmVxdWVzdCI6ChNHZXRTZXR0aW5nc1Jlc3BvbnNlEiMKCHNldHRpbmdzGAEgASgLMhEudXNlci52MS5TZXR0aW5ncyI8ChVVcGRhdGVTZXR0aW5nc1JlcXVlc3QSIwoIc2V0dGluZ3MYASABKAsyES51c2VyLnYxLlNldHRpbmdzIj0KFlVwZGF0ZVNldHRpbmdzUmVzcG9uc2USIwoIc2V0dGluZ3MYASABKAsyES51c2VyLnYxLlNldHRpbmdzIhYKFFJlc2V0U2V0dGluZ3NSZXF1ZXN0IjwKFVJlc2V0U2V0dGluZ3NSZXNwb25zZRIjCghzZXR0aW5ncxgBIAEoCzIRLnVzZXIudjEuU2V0dGluZ3MiHAoaR2V0VXNlckluc3RydWN0aW9uc1JlcXVlc3QiMwobR2V0VXNlckluc3RydWN0aW9uc1Jlc3BvbnNlEhQKDGluc3RydWN0aW9ucxgBIAEoCSI1Ch1VcHNlcnRVc2VySW5zdHJ1Y3Rpb25zUmVxdWVzdBIUCgxpbnN0cnVjdGlvbnMYASABKAkiNgoeVXBzZXJ0VXNlckluc3RydWN0aW9uc1Jlc3BvbnNlEhQKDGluc3RydWN0aW9ucxgBIAEoCTKDCgoLVXNlclNlcnZpY2USXQoHR2V0VXNlchIXLnVzZXIudjEuR2V0VXNlclJlcXVlc3QaGC51c2VyLnYxLkdldFVzZXJSZXNwb25zZSIfgtPkkwIZEhcvX3BkL2FwaS92MS91c2Vycy9Ac2VsZhJxCgtMaXN0UHJvbXB0cxIbLnVzZXIudjEuTGlzdFByb21wdHNSZXF1ZXN0GhwudXNlci52MS5MaXN0UHJvbXB0c1Jlc3BvbnNlIieC0+STAiESHy9fcGQvYXBpL3YxL3VzZXJzL0BzZWxmL3Byb21wdHMSdwoMQ3JlYXRlUHJvbXB0EhwudXNlci52MS5DcmVhdGVQcm9tcHRSZXF1ZXN0Gh0udXNlci52MS5DcmVhdGVQcm9tcHRSZXNwb25zZSIqgtPkkwIkOgEqIh8vX3BkL2FwaS92MS91c2Vycy9Ac2VsZi9wcm9tcHRzEoMBCgxVcGRhdGVQcm9tcHQSHC51c2VyLnYxLlVwZGF0ZVByb21wdFJlcXVlc3QaHS51c2VyLnYxLlVwZGF0ZVByb21wdFJlc3BvbnNlIjaC0+STAjA6ASoaKy9fcGQvYXBpL3YxL3VzZXJzL0BzZWxmL3Byb21wdHMve3Byb21wdF9pZH0SjgEKE0dldFVzZXJJbnN0cnVjdGlvbnMSIy51c2VyLnYxLkdldFVzZXJJbnN0cnVjdGlvbnNSZXF1ZXN0GiQudXNlci52MS5HZXRVc2VySW5zdHJ1Y3Rpb25zUmVzcG9uc2UiLILT5JMCJhIkL19wZC9hcGkvdjEvdXNlcnMvQHNlbGYvaW5zdHJ1Y3Rpb25zEpoBChZVcHNlcnRVc2VySW5zdHJ1Y3Rpb25zEiYudXNlci52MS5VcHNlcnRVc2VySW5zdHJ1Y3Rpb25zUmVxdWVzdBonLnVzZXIudjEuVXBzZXJ0VXNlckluc3RydWN0aW9uc1Jlc3BvbnNlIi+C0+STAik6ASoiJC9fcGQvYXBpL3YxL3VzZXJzL0BzZWxmL2luc3RydWN0aW9ucxKAAQoMRGVsZXRlUHJvbXB0EhwudXNlci52MS5EZWxldGVQcm9tcHRSZXF1ZXN0Gh0udXNlci52MS5EZWxldGVQcm9tcHRSZXNwb25zZSIzgtPkkwItKisvX3BkL2FwaS92MS91c2Vycy9Ac2VsZi9wcm9tcHRzL3twcm9tcHRfaWR9EnIKC0dldFNldHRpbmdzEhsudXNlci52MS5HZXRTZXR0aW5nc1JlcXVlc3QaHC51c2VyLnYxLkdldFNldHRpbmdzUmVzcG9uc2UiKILT5JMCIhIgL19wZC9hcGkvdjEvdXNlcnMvQHNlbGYvc2V0dGluZ3MSfgoOVXBkYXRlU2V0dGluZ3MSHi51c2VyLnYxLlVwZGF0ZVNldHRpbmdzUmVxdWVzdBofLnVzZXIudjEuVXBkYXRlU2V0dGluZ3NSZXNwb25zZSIrgtPkkwIlOgEqGiAvX3BkL2FwaS92MS91c2Vycy9Ac2VsZi9zZXR0aW5ncxJ+Cg1SZXNldFNldHRpbmdzEh0udXNlci52MS5SZXNldFNldHRpbmdzUmVxdWVzdBoeLnVzZXIudjEuUmVzZXRTZXR0aW5nc1Jlc3BvbnNlIi6C0+STAigiJi9fcGQvYXBpL3YxL3VzZXJzL0BzZWxmL3NldHRpbmdzL3Jlc2V0Qn8KC2NvbS51c2VyLnYxQglVc2VyUHJvdG9QAVoocGFwZXJkZWJ1Z2dlci9wa2cvZ2VuL2FwaS91c2VyL3YxO3VzZXJ2MaICA1VYWKoCB1VzZXIuVjHKAgdVc2VyXFYx4gITVXNlclxWMVxHUEJNZXRhZGF0YeoCCFVzZXI6OlYxYgZwcm90bzM", [file_google_api_annotations, file_google_protobuf_timestamp]); + fileDesc("ChJ1c2VyL3YxL3VzZXIucHJvdG8SB3VzZXIudjEiQAoEVXNlchIKCgJpZBgBIAEoCRINCgVlbWFpbBgCIAEoCRIMCgRuYW1lGAMgASgJEg8KB3BpY3R1cmUYBCABKAkiEAoOR2V0VXNlclJlcXVlc3QiLgoPR2V0VXNlclJlc3BvbnNlEhsKBHVzZXIYASABKAsyDS51c2VyLnYxLlVzZXIirAEKBlByb21wdBIKCgJpZBgBIAEoCRIuCgpjcmVhdGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBINCgV0aXRsZRgEIAEoCRIPCgdjb250ZW50GAUgASgJEhYKDmlzX3VzZXJfcHJvbXB0GAYgASgIIhQKEkxpc3RQcm9tcHRzUmVxdWVzdCI3ChNMaXN0UHJvbXB0c1Jlc3BvbnNlEiAKB3Byb21wdHMYASADKAsyDy51c2VyLnYxLlByb21wdCI1ChNDcmVhdGVQcm9tcHRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEg8KB2NvbnRlbnQYAiABKAkiNwoUQ3JlYXRlUHJvbXB0UmVzcG9uc2USHwoGcHJvbXB0GAEgASgLMg8udXNlci52MS5Qcm9tcHQiSAoTVXBkYXRlUHJvbXB0UmVxdWVzdBIRCglwcm9tcHRfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDwoHY29udGVudBgDIAEoCSI3ChRVcGRhdGVQcm9tcHRSZXNwb25zZRIfCgZwcm9tcHQYASABKAsyDy51c2VyLnYxLlByb21wdCIoChNEZWxldGVQcm9tcHRSZXF1ZXN0EhEKCXByb21wdF9pZBgBIAEoCSIWChREZWxldGVQcm9tcHRSZXNwb25zZSKvAQoLQ3VzdG9tTW9kZWwSCgoCaWQYASABKAkSDAoEc2x1ZxgCIAEoCRIMCgRuYW1lGAMgASgJEhAKCGJhc2VfdXJsGAQgASgJEg8KB2FwaV9rZXkYBSABKAkSFgoOY29udGV4dF93aW5kb3cYBiABKAUSEgoKbWF4X291dHB1dBgHIAEoBRITCgtpbnB1dF9wcmljZRgIIAEoBRIUCgxvdXRwdXRfcHJpY2UYCSABKAUi+wEKCFNldHRpbmdzEiYKHnNob3dfc2hvcnRjdXRzX2FmdGVyX3NlbGVjdGlvbhgBIAEoCBIoCiBmdWxsX3dpZHRoX3BhcGVyX2RlYnVnZ2VyX2J1dHRvbhgCIAEoCBIiChplbmFibGVfY2l0YXRpb25fc3VnZ2VzdGlvbhgDIAEoCBIZChFmdWxsX2RvY3VtZW50X3JhZxgEIAEoCBIZChFzaG93ZWRfb25ib2FyZGluZxgFIAEoCBIWCg5vcGVuYWlfYXBpX2tleRgGIAEoCRIrCg1jdXN0b21fbW9kZWxzGAcgAygLMhQudXNlci52MS5DdXN0b21Nb2RlbCIUChJHZXRTZXR0aW5nc1JlcXVlc3QiOgoTR2V0U2V0dGluZ3NSZXNwb25zZRIjCghzZXR0aW5ncxgBIAEoCzIRLnVzZXIudjEuU2V0dGluZ3MiPAoVVXBkYXRlU2V0dGluZ3NSZXF1ZXN0EiMKCHNldHRpbmdzGAEgASgLMhEudXNlci52MS5TZXR0aW5ncyI9ChZVcGRhdGVTZXR0aW5nc1Jlc3BvbnNlEiMKCHNldHRpbmdzGAEgASgLMhEudXNlci52MS5TZXR0aW5ncyIWChRSZXNldFNldHRpbmdzUmVxdWVzdCI8ChVSZXNldFNldHRpbmdzUmVzcG9uc2USIwoIc2V0dGluZ3MYASABKAsyES51c2VyLnYxLlNldHRpbmdzIhwKGkdldFVzZXJJbnN0cnVjdGlvbnNSZXF1ZXN0IjMKG0dldFVzZXJJbnN0cnVjdGlvbnNSZXNwb25zZRIUCgxpbnN0cnVjdGlvbnMYASABKAkiNQodVXBzZXJ0VXNlckluc3RydWN0aW9uc1JlcXVlc3QSFAoMaW5zdHJ1Y3Rpb25zGAEgASgJIjYKHlVwc2VydFVzZXJJbnN0cnVjdGlvbnNSZXNwb25zZRIUCgxpbnN0cnVjdGlvbnMYASABKAkygwoKC1VzZXJTZXJ2aWNlEl0KB0dldFVzZXISFy51c2VyLnYxLkdldFVzZXJSZXF1ZXN0GhgudXNlci52MS5HZXRVc2VyUmVzcG9uc2UiH4LT5JMCGRIXL19wZC9hcGkvdjEvdXNlcnMvQHNlbGYScQoLTGlzdFByb21wdHMSGy51c2VyLnYxLkxpc3RQcm9tcHRzUmVxdWVzdBocLnVzZXIudjEuTGlzdFByb21wdHNSZXNwb25zZSIngtPkkwIhEh8vX3BkL2FwaS92MS91c2Vycy9Ac2VsZi9wcm9tcHRzEncKDENyZWF0ZVByb21wdBIcLnVzZXIudjEuQ3JlYXRlUHJvbXB0UmVxdWVzdBodLnVzZXIudjEuQ3JlYXRlUHJvbXB0UmVzcG9uc2UiKoLT5JMCJDoBKiIfL19wZC9hcGkvdjEvdXNlcnMvQHNlbGYvcHJvbXB0cxKDAQoMVXBkYXRlUHJvbXB0EhwudXNlci52MS5VcGRhdGVQcm9tcHRSZXF1ZXN0Gh0udXNlci52MS5VcGRhdGVQcm9tcHRSZXNwb25zZSI2gtPkkwIwOgEqGisvX3BkL2FwaS92MS91c2Vycy9Ac2VsZi9wcm9tcHRzL3twcm9tcHRfaWR9Eo4BChNHZXRVc2VySW5zdHJ1Y3Rpb25zEiMudXNlci52MS5HZXRVc2VySW5zdHJ1Y3Rpb25zUmVxdWVzdBokLnVzZXIudjEuR2V0VXNlckluc3RydWN0aW9uc1Jlc3BvbnNlIiyC0+STAiYSJC9fcGQvYXBpL3YxL3VzZXJzL0BzZWxmL2luc3RydWN0aW9ucxKaAQoWVXBzZXJ0VXNlckluc3RydWN0aW9ucxImLnVzZXIudjEuVXBzZXJ0VXNlckluc3RydWN0aW9uc1JlcXVlc3QaJy51c2VyLnYxLlVwc2VydFVzZXJJbnN0cnVjdGlvbnNSZXNwb25zZSIvgtPkkwIpOgEqIiQvX3BkL2FwaS92MS91c2Vycy9Ac2VsZi9pbnN0cnVjdGlvbnMSgAEKDERlbGV0ZVByb21wdBIcLnVzZXIudjEuRGVsZXRlUHJvbXB0UmVxdWVzdBodLnVzZXIudjEuRGVsZXRlUHJvbXB0UmVzcG9uc2UiM4LT5JMCLSorL19wZC9hcGkvdjEvdXNlcnMvQHNlbGYvcHJvbXB0cy97cHJvbXB0X2lkfRJyCgtHZXRTZXR0aW5ncxIbLnVzZXIudjEuR2V0U2V0dGluZ3NSZXF1ZXN0GhwudXNlci52MS5HZXRTZXR0aW5nc1Jlc3BvbnNlIiiC0+STAiISIC9fcGQvYXBpL3YxL3VzZXJzL0BzZWxmL3NldHRpbmdzEn4KDlVwZGF0ZVNldHRpbmdzEh4udXNlci52MS5VcGRhdGVTZXR0aW5nc1JlcXVlc3QaHy51c2VyLnYxLlVwZGF0ZVNldHRpbmdzUmVzcG9uc2UiK4LT5JMCJToBKhogL19wZC9hcGkvdjEvdXNlcnMvQHNlbGYvc2V0dGluZ3MSfgoNUmVzZXRTZXR0aW5ncxIdLnVzZXIudjEuUmVzZXRTZXR0aW5nc1JlcXVlc3QaHi51c2VyLnYxLlJlc2V0U2V0dGluZ3NSZXNwb25zZSIugtPkkwIoIiYvX3BkL2FwaS92MS91c2Vycy9Ac2VsZi9zZXR0aW5ncy9yZXNldEJ/Cgtjb20udXNlci52MUIJVXNlclByb3RvUAFaKHBhcGVyZGVidWdnZXIvcGtnL2dlbi9hcGkvdXNlci92MTt1c2VydjGiAgNVWFiqAgdVc2VyLlYxygIHVXNlclxWMeICE1VzZXJcVjFcR1BCTWV0YWRhdGHqAghVc2VyOjpWMWIGcHJvdG8z", [file_google_api_annotations, file_google_protobuf_timestamp]); /** * @generated from message user.v1.User @@ -262,6 +262,63 @@ export type DeletePromptResponse = Message<"user.v1.DeletePromptResponse"> & { export const DeletePromptResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_user_v1_user, 11); +/** + * @generated from message user.v1.CustomModel + */ +export type CustomModel = Message<"user.v1.CustomModel"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string slug = 2; + */ + slug: string; + + /** + * @generated from field: string name = 3; + */ + name: string; + + /** + * @generated from field: string base_url = 4; + */ + baseUrl: string; + + /** + * @generated from field: string api_key = 5; + */ + apiKey: string; + + /** + * @generated from field: int32 context_window = 6; + */ + contextWindow: number; + + /** + * @generated from field: int32 max_output = 7; + */ + maxOutput: number; + + /** + * @generated from field: int32 input_price = 8; + */ + inputPrice: number; + + /** + * @generated from field: int32 output_price = 9; + */ + outputPrice: number; +}; + +/** + * Describes the message user.v1.CustomModel. + * Use `create(CustomModelSchema)` to create a new message. + */ +export const CustomModelSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_user_v1_user, 12); + /** * @generated from message user.v1.Settings */ @@ -295,6 +352,11 @@ export type Settings = Message<"user.v1.Settings"> & { * @generated from field: string openai_api_key = 6; */ openaiApiKey: string; + + /** + * @generated from field: repeated user.v1.CustomModel custom_models = 7; + */ + customModels: CustomModel[]; }; /** @@ -302,7 +364,7 @@ export type Settings = Message<"user.v1.Settings"> & { * Use `create(SettingsSchema)` to create a new message. */ export const SettingsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 12); + messageDesc(file_user_v1_user, 13); /** * @generated from message user.v1.GetSettingsRequest @@ -315,7 +377,7 @@ export type GetSettingsRequest = Message<"user.v1.GetSettingsRequest"> & { * Use `create(GetSettingsRequestSchema)` to create a new message. */ export const GetSettingsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 13); + messageDesc(file_user_v1_user, 14); /** * @generated from message user.v1.GetSettingsResponse @@ -332,7 +394,7 @@ export type GetSettingsResponse = Message<"user.v1.GetSettingsResponse"> & { * Use `create(GetSettingsResponseSchema)` to create a new message. */ export const GetSettingsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 14); + messageDesc(file_user_v1_user, 15); /** * @generated from message user.v1.UpdateSettingsRequest @@ -349,7 +411,7 @@ export type UpdateSettingsRequest = Message<"user.v1.UpdateSettingsRequest"> & { * Use `create(UpdateSettingsRequestSchema)` to create a new message. */ export const UpdateSettingsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 15); + messageDesc(file_user_v1_user, 16); /** * @generated from message user.v1.UpdateSettingsResponse @@ -366,7 +428,7 @@ export type UpdateSettingsResponse = Message<"user.v1.UpdateSettingsResponse"> & * Use `create(UpdateSettingsResponseSchema)` to create a new message. */ export const UpdateSettingsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 16); + messageDesc(file_user_v1_user, 17); /** * @generated from message user.v1.ResetSettingsRequest @@ -379,7 +441,7 @@ export type ResetSettingsRequest = Message<"user.v1.ResetSettingsRequest"> & { * Use `create(ResetSettingsRequestSchema)` to create a new message. */ export const ResetSettingsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 17); + messageDesc(file_user_v1_user, 18); /** * @generated from message user.v1.ResetSettingsResponse @@ -396,7 +458,7 @@ export type ResetSettingsResponse = Message<"user.v1.ResetSettingsResponse"> & { * Use `create(ResetSettingsResponseSchema)` to create a new message. */ export const ResetSettingsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 18); + messageDesc(file_user_v1_user, 19); /** * @generated from message user.v1.GetUserInstructionsRequest @@ -409,7 +471,7 @@ export type GetUserInstructionsRequest = Message<"user.v1.GetUserInstructionsReq * Use `create(GetUserInstructionsRequestSchema)` to create a new message. */ export const GetUserInstructionsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 19); + messageDesc(file_user_v1_user, 20); /** * @generated from message user.v1.GetUserInstructionsResponse @@ -426,7 +488,7 @@ export type GetUserInstructionsResponse = Message<"user.v1.GetUserInstructionsRe * Use `create(GetUserInstructionsResponseSchema)` to create a new message. */ export const GetUserInstructionsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 20); + messageDesc(file_user_v1_user, 21); /** * @generated from message user.v1.UpsertUserInstructionsRequest @@ -443,7 +505,7 @@ export type UpsertUserInstructionsRequest = Message<"user.v1.UpsertUserInstructi * Use `create(UpsertUserInstructionsRequestSchema)` to create a new message. */ export const UpsertUserInstructionsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 21); + messageDesc(file_user_v1_user, 22); /** * @generated from message user.v1.UpsertUserInstructionsResponse @@ -460,7 +522,7 @@ export type UpsertUserInstructionsResponse = Message<"user.v1.UpsertUserInstruct * Use `create(UpsertUserInstructionsResponseSchema)` to create a new message. */ export const UpsertUserInstructionsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_user_v1_user, 22); + messageDesc(file_user_v1_user, 23); /** * @generated from service user.v1.UserService diff --git a/webapp/_webapp/src/views/chat/footer/toolbar/model-selection.tsx b/webapp/_webapp/src/views/chat/footer/toolbar/model-selection.tsx index 33d2d7df..ef89927e 100644 --- a/webapp/_webapp/src/views/chat/footer/toolbar/model-selection.tsx +++ b/webapp/_webapp/src/views/chat/footer/toolbar/model-selection.tsx @@ -14,7 +14,7 @@ export function ModelSelection({ onSelectModel }: ModelSelectionProps) { const items: SelectionItem[] = useMemo(() => { return models.map((model) => ({ title: model.name, - subtitle: model.slug, + subtitle: `${model.slug}${model.isCustom ? " (Custom)" : ""}`, value: model.slug, disabled: model.disabled, disabledReason: model.disabledReason, diff --git a/webapp/_webapp/src/views/settings/sections/api-key-settings.tsx b/webapp/_webapp/src/views/settings/sections/api-key-settings.tsx index 82f5cd90..4d5c854d 100644 --- a/webapp/_webapp/src/views/settings/sections/api-key-settings.tsx +++ b/webapp/_webapp/src/views/settings/sections/api-key-settings.tsx @@ -1,21 +1,330 @@ +import { Fragment, useState } from "react"; +import { Icon } from "@iconify/react"; +import { Modal } from "../../../components/modal"; import { SettingsSectionContainer, SettingsSectionTitle } from "./components"; -import { createSettingsTextInput } from "../setting-text-input"; - -const ApiKeyInput = createSettingsTextInput("openaiApiKey"); +import { Accordion, AccordionItem, Button, Tooltip } from "@heroui/react"; +import { useSettingStore } from "../../../stores/setting-store"; export const ApiKeySettings = () => { + const { updateSettings, settings } = useSettingStore(); + + const [isShowModal, setIsShowModal] = useState(false); + + const handleCustomModelChange = async (newModel: CustomModel, isDelete: boolean) => { + const otherCustomModels = Array.from(settings?.customModels || []).filter((model) => model.id != newModel.id); + + if (isDelete) { + await updateSettings({ + customModels: otherCustomModels, + }); + } else { + await updateSettings({ + customModels: [ + ...otherCustomModels, + { + id: newModel.id, + name: newModel.name, + baseUrl: newModel.baseUrl, + slug: newModel.slug, + apiKey: newModel.apiKey, + contextWindow: newModel.contextWindow, + maxOutput: newModel.maxOutput, + inputPrice: newModel.inputPrice, + outputPrice: newModel.outputPrice, + }, + ], + }); + } + }; + return ( Bring Your Own Key (BYOK) -
- setIsShowModal((i) => !i)} className="shrink-0"> + Edit + + setIsShowModal(isOpen)} + content={ +
+ + {Array.from(settings?.customModels || []).map((m) => ( + +
+ +
+ ))} +
+ } + /> + + ); +}; + +type CustomModel = { + id: string; + name: string; + baseUrl: string; + slug: string; + apiKey: string; + contextWindow: number; + maxOutput: number; + inputPrice: number; + outputPrice: number; +}; + +type NewCustomModelSectionProps = { + isNew: true; + onChange: (model: CustomModel, isDelete: boolean) => void; + model?: never; +}; + +type ExistingCustomModelSectionProps = { + isNew: false; + onChange: (model: CustomModel, isDelete: boolean) => void; + model: CustomModel; +}; + +type CustomModelSectionProps = NewCustomModelSectionProps | ExistingCustomModelSectionProps; + +const CustomModelSection = ({ isNew, onChange, model: customModel }: CustomModelSectionProps) => { + const id = customModel?.id || ""; + const [isEditing, setIsEditing] = useState(isNew); + const [baseUrl, setBaseUrl] = useState(customModel?.baseUrl || ""); + const [slug, setSlug] = useState(customModel?.slug ?? ""); + const [apiKey, setApiKey] = useState(customModel?.apiKey || ""); + const [contextWindow, setContextWindow] = useState(customModel?.contextWindow || 0); + const [maxOutput, setMaxOutput] = useState(customModel?.maxOutput || 0); + const [inputPrice, setInputPrice] = useState(customModel?.inputPrice || 0); + const [outputPrice, setOutputPrice] = useState(customModel?.outputPrice || 0); + const [modelName, setModelName] = useState(customModel?.name || ""); + const [isModelNameValid, setIsModelNameValid] = useState(true); + const [isSlugValid, setIsSlugValid] = useState(true); + const [isBaseUrlValid, setIsBaseUrlValid] = useState(true); + const [isApiKeyValid, setIsApiKeyValid] = useState(true); + + const borderedInputClassName = "rnd-cancel px-2 py-1 border !border-gray-200 dark:!border-default-200 rounded-md"; + const baseClassName = "bg-transparent p-1 focus:outline-none disabled:opacity-70"; + const modelNameInputClassName = `${baseClassName} ${isEditing || isNew ? borderedInputClassName : ""} text-sm text-default-900 font-medium flex-1 truncate mr-1`; + const labelClassName = `${baseClassName} text-xs text-default-900 w-auto`; + const detailInputClassName = `${baseClassName} ${isEditing || isNew ? borderedInputClassName : ""} flex-1 noselect focus:outline-none text-xs text-default-700 placeholder:text-default-400`; + const errorInputClassName = "!border-red-500 focus:!border-red-500"; + + const handleOnChange = async (isDelete: boolean) => { + if ( + modelName.trim().length < 1 || + slug.trim().length < 1 || + baseUrl.trim().length < 1 || + apiKey.trim().length < 1 + ) { + setIsModelNameValid(modelName.trim().length > 0); + setIsSlugValid(slug.trim().length > 0); + setIsBaseUrlValid(baseUrl.trim().length > 0); + setIsApiKeyValid(apiKey.trim().length > 0); + return; + } + + await onChange( + { + id: id, + name: modelName.trim(), + baseUrl: baseUrl.trim(), + slug: slug.trim(), + apiKey: apiKey.trim(), + contextWindow: contextWindow, + maxOutput: maxOutput, + inputPrice: inputPrice, + outputPrice: outputPrice, + }, + isDelete, + ); + + if (isNew) { + setModelName(""); + setBaseUrl(""); + setSlug(""); + setApiKey(""); + setContextWindow(0); + setMaxOutput(0); + setInputPrice(0); + setOutputPrice(0); + } else { + setIsEditing(false); + } + }; + + return ( +
+
+ { + setIsModelNameValid(true); + setModelName(e.target.value); + }} + > + + {isNew ? ( + + + + ) : ( +
+ + + + + + +
+ )} +
+ +
+ + { + setIsSlugValid(true); + setSlug(e.target.value); + }} />
- + +
+ + { + setIsBaseUrlValid(true); + setBaseUrl(e.target.value); + }} + /> +
+ +
+ + { + setIsApiKeyValid(true); + setApiKey(e.target.value); + }} + /> +
+ + + {isNew ? "Optional Fields" : "More"}} + classNames={{ + trigger: "px-1 py-0 min-h-0", + content: "pt-1 pb-1", + }} + > +
+
+ + setContextWindow(e.target.value === "" ? 0 : Math.trunc(Number(e.target.value)))} + /> +
+ +
+ + setMaxOutput(e.target.value === "" ? 0 : Math.trunc(Number(e.target.value)))} + /> +
+ +
+ + setInputPrice(e.target.value === "" ? 0 : Math.trunc(Number(e.target.value)))} + /> +
+ +
+ + setOutputPrice(e.target.value === "" ? 0 : Math.trunc(Number(e.target.value)))} + /> +
+
+
+
+
); };