119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
|
|
package usecase
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
|
||
|
|
app "haixun-backend/internal/library/errors"
|
||
|
|
"haixun-backend/internal/library/errors/code"
|
||
|
|
"haixun-backend/internal/model/ai/domain/enum"
|
||
|
|
domai "haixun-backend/internal/model/ai/domain/usecase"
|
||
|
|
"haixun-backend/internal/model/ai/provider"
|
||
|
|
)
|
||
|
|
|
||
|
|
type UseCase = domai.UseCase
|
||
|
|
|
||
|
|
type providerMeta struct {
|
||
|
|
ID string
|
||
|
|
Label string
|
||
|
|
Streams bool
|
||
|
|
}
|
||
|
|
|
||
|
|
type aiUseCase struct {
|
||
|
|
providers map[enum.ProviderID]domai.Provider
|
||
|
|
catalog []providerMeta
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewUseCase() UseCase {
|
||
|
|
providers := map[enum.ProviderID]domai.Provider{
|
||
|
|
enum.ProviderOpenCode: provider.NewOpenAICompatible(enum.ProviderOpenCode, "https://opencode.ai/zen/go/v1"),
|
||
|
|
enum.ProviderXAI: provider.NewOpenAICompatible(enum.ProviderXAI, "https://api.x.ai/v1"),
|
||
|
|
}
|
||
|
|
return &aiUseCase{
|
||
|
|
providers: providers,
|
||
|
|
catalog: []providerMeta{
|
||
|
|
{ID: string(enum.ProviderOpenCode), Label: "OpenCode Go", Streams: true},
|
||
|
|
{ID: string(enum.ProviderXAI), Label: "Grok (xAI)", Streams: true},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *aiUseCase) ListProviders(ctx context.Context) []domai.ProviderOption {
|
||
|
|
options := make([]domai.ProviderOption, 0, len(u.catalog))
|
||
|
|
for _, meta := range u.catalog {
|
||
|
|
options = append(options, domai.ProviderOption{
|
||
|
|
ID: meta.ID,
|
||
|
|
Label: meta.Label,
|
||
|
|
Streams: meta.Streams,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return options
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *aiUseCase) ListProviderModels(ctx context.Context, providerID enum.ProviderID, credential domai.Credential) domai.ProviderModels {
|
||
|
|
meta, ok := u.meta(providerID)
|
||
|
|
if !ok {
|
||
|
|
return domai.ProviderModels{
|
||
|
|
Error: app.For(code.AI).InputInvalidFormat("unsupported AI provider").Error(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
result := domai.ProviderModels{
|
||
|
|
ID: meta.ID,
|
||
|
|
Label: meta.Label,
|
||
|
|
Models: []string{},
|
||
|
|
Streams: meta.Streams,
|
||
|
|
}
|
||
|
|
|
||
|
|
p, ok := u.providers[providerID]
|
||
|
|
if !ok {
|
||
|
|
result.Error = app.For(code.AI).InputInvalidFormat("unsupported AI provider").Error()
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
models, err := p.ListModels(ctx, credential)
|
||
|
|
if err != nil {
|
||
|
|
if appErr := app.FromError(err); appErr != nil {
|
||
|
|
result.Error = appErr.Error()
|
||
|
|
} else {
|
||
|
|
result.Error = err.Error()
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
result.Models = models
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *aiUseCase) GenerateText(ctx context.Context, req domai.GenerateRequest) (*domai.GenerateResult, error) {
|
||
|
|
p, err := u.resolve(req.Provider)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return p.GenerateText(ctx, req)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *aiUseCase) StreamText(ctx context.Context, req domai.GenerateRequest) (<-chan domai.StreamEvent, error) {
|
||
|
|
p, err := u.resolve(req.Provider)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return p.StreamText(ctx, req)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *aiUseCase) meta(id enum.ProviderID) (providerMeta, bool) {
|
||
|
|
for _, meta := range u.catalog {
|
||
|
|
if meta.ID == string(id) {
|
||
|
|
return meta, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return providerMeta{}, false
|
||
|
|
}
|
||
|
|
|
||
|
|
func (u *aiUseCase) resolve(id enum.ProviderID) (domai.Provider, error) {
|
||
|
|
p, ok := u.providers[id]
|
||
|
|
if !ok {
|
||
|
|
return nil, app.For(code.AI).InputInvalidFormat("unsupported AI provider")
|
||
|
|
}
|
||
|
|
return p, nil
|
||
|
|
}
|