Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion devserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func Run(cfg Config) error {
// routePrefixes 从路由声明中提取不重复的路径前缀(如 /v1/)
func routePrefixes(routes []sdk.RouteDefinition) []string {
seen := make(map[string]bool)
var prefixes []string
prefixes := make([]string, 0, len(routes))
for _, r := range routes {
// 取第二个 / 之前的部分作为前缀
parts := strings.SplitN(strings.TrimPrefix(r.Path, "/"), "/", 2)
Expand Down
27 changes: 27 additions & 0 deletions devserver/server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package devserver

import (
"testing"

sdk "github.com/DouDOU-start/airgate-sdk"
)

func TestRoutePrefixesDeduplicates(t *testing.T) {
t.Parallel()

got := routePrefixes([]sdk.RouteDefinition{
{Path: "/v1/chat/completions"},
{Path: "/v1/models"},
{Path: "/oauth/callback"},
{Path: "/"},
})
want := []string{"/v1/", "/oauth/", "//"}
if len(got) != len(want) {
t.Fatalf("routePrefixes length = %d, want %d (%v)", len(got), len(want), got)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("routePrefixes[%d] = %q, want %q", i, got[i], want[i])
}
}
}
14 changes: 12 additions & 2 deletions frontend/src/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,23 @@ export function setTheme(theme: ThemeName, options: ThemeSetOptions = {}): void
const themeAttribute = options.themeAttribute || 'data-theme';
const target = options.target || document.documentElement;
target.setAttribute(themeAttribute, theme);
localStorage.setItem(options.storageKey || 'ag-theme', theme);
try {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(options.storageKey || 'ag-theme', theme);
}
} catch {
// Theme switching should keep working when storage is unavailable.
}
}

/** 读取已保存的主题偏好,默认 dark */
export function getStoredTheme(options: ThemeStorageOptions = {}): ThemeName {
if (typeof localStorage === 'undefined') return 'dark';
return (localStorage.getItem(options.storageKey || 'ag-theme') as ThemeName) || 'dark';
try {
return (localStorage.getItem(options.storageKey || 'ag-theme') as ThemeName) || 'dark';
} catch {
return 'dark';
}
}

/** 生成 Tailwind 可消费的 theme bridge */
Expand Down
15 changes: 15 additions & 0 deletions grpc/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func (b *pluginBase) Info() sdk.PluginInfo {
Dependencies: resp.Dependencies,
}

if len(resp.ConfigSchema) > 0 {
info.ConfigSchema = make([]sdk.ConfigField, 0, len(resp.ConfigSchema))
}
for _, cf := range resp.ConfigSchema {
info.ConfigSchema = append(info.ConfigSchema, sdk.ConfigField{
Key: cf.Key,
Expand All @@ -96,12 +99,18 @@ func (b *pluginBase) Info() sdk.PluginInfo {
})
}

if len(resp.AccountTypes) > 0 {
info.AccountTypes = make([]sdk.AccountType, 0, len(resp.AccountTypes))
}
for _, at := range resp.AccountTypes {
accountType := sdk.AccountType{
Key: at.Key,
Label: at.Label,
Description: at.Description,
}
if len(at.Fields) > 0 {
accountType.Fields = make([]sdk.CredentialField, 0, len(at.Fields))
}
for _, f := range at.Fields {
accountType.Fields = append(accountType.Fields, sdk.CredentialField{
Key: f.Key,
Expand All @@ -114,6 +123,9 @@ func (b *pluginBase) Info() sdk.PluginInfo {
}
info.AccountTypes = append(info.AccountTypes, accountType)
}
if len(resp.FrontendPages) > 0 {
info.FrontendPages = make([]sdk.FrontendPage, 0, len(resp.FrontendPages))
}
for _, p := range resp.FrontendPages {
info.FrontendPages = append(info.FrontendPages, sdk.FrontendPage{
Path: p.Path,
Expand All @@ -123,6 +135,9 @@ func (b *pluginBase) Info() sdk.PluginInfo {
Audience: p.Audience,
})
}
if len(resp.FrontendWidgets) > 0 {
info.FrontendWidgets = make([]sdk.FrontendWidget, 0, len(resp.FrontendWidgets))
}
for _, w := range resp.FrontendWidgets {
info.FrontendWidgets = append(info.FrontendWidgets, sdk.FrontendWidget{
Slot: w.Slot,
Expand Down
3 changes: 3 additions & 0 deletions grpc/extension_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ func (s *ExtensionGRPCServer) Migrate(_ context.Context, _ *pb.Empty) (*pb.Empty
func (s *ExtensionGRPCServer) GetBackgroundTasks(_ context.Context, _ *pb.Empty) (*pb.BackgroundTasksResponse, error) {
tasks := s.Impl.BackgroundTasks()
resp := &pb.BackgroundTasksResponse{}
if len(tasks) > 0 {
resp.Tasks = make([]*pb.BackgroundTaskProto, 0, len(tasks))
}
taskMap := make(map[string]func(context.Context) error, len(tasks))
for _, t := range tasks {
resp.Tasks = append(resp.Tasks, &pb.BackgroundTaskProto{
Expand Down
6 changes: 6 additions & 0 deletions grpc/gateway_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ func (s *GatewayGRPCServer) GetPlatform(_ context.Context, _ *pb.Empty) (*pb.Str
func (s *GatewayGRPCServer) GetModels(_ context.Context, _ *pb.Empty) (*pb.ModelsResponse, error) {
models := s.Impl.Models()
resp := &pb.ModelsResponse{}
if len(models) > 0 {
resp.Models = make([]*pb.ModelInfoProto, 0, len(models))
}
for _, m := range models {
resp.Models = append(resp.Models, &pb.ModelInfoProto{
Id: m.ID,
Expand All @@ -45,6 +48,9 @@ func (s *GatewayGRPCServer) GetModels(_ context.Context, _ *pb.Empty) (*pb.Model
func (s *GatewayGRPCServer) GetRoutes(_ context.Context, _ *pb.Empty) (*pb.RoutesResponse, error) {
routes := s.Impl.Routes()
resp := &pb.RoutesResponse{}
if len(routes) > 0 {
resp.Routes = make([]*pb.RouteDefinitionProto, 0, len(routes))
}
for _, r := range routes {
resp.Routes = append(resp.Routes, &pb.RouteDefinitionProto{
Method: r.Method,
Expand Down
17 changes: 16 additions & 1 deletion grpc/plugin_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func (s *PluginGRPCServer) GetInfo(_ context.Context, _ *pb.Empty) (*pb.PluginIn
Dependencies: info.Dependencies,
}

if len(info.ConfigSchema) > 0 {
resp.ConfigSchema = make([]*pb.ConfigFieldProto, 0, len(info.ConfigSchema))
}
for _, cf := range info.ConfigSchema {
resp.ConfigSchema = append(resp.ConfigSchema, &pb.ConfigFieldProto{
Key: cf.Key,
Expand All @@ -47,12 +50,18 @@ func (s *PluginGRPCServer) GetInfo(_ context.Context, _ *pb.Empty) (*pb.PluginIn
})
}

if len(info.AccountTypes) > 0 {
resp.AccountTypes = make([]*pb.AccountTypeProto, 0, len(info.AccountTypes))
}
for _, at := range info.AccountTypes {
atProto := &pb.AccountTypeProto{
Key: at.Key,
Label: at.Label,
Description: at.Description,
}
if len(at.Fields) > 0 {
atProto.Fields = make([]*pb.CredentialFieldProto, 0, len(at.Fields))
}
for _, f := range at.Fields {
atProto.Fields = append(atProto.Fields, &pb.CredentialFieldProto{
Key: f.Key,
Expand All @@ -65,6 +74,9 @@ func (s *PluginGRPCServer) GetInfo(_ context.Context, _ *pb.Empty) (*pb.PluginIn
}
resp.AccountTypes = append(resp.AccountTypes, atProto)
}
if len(info.FrontendPages) > 0 {
resp.FrontendPages = make([]*pb.FrontendPageProto, 0, len(info.FrontendPages))
}
for _, p := range info.FrontendPages {
resp.FrontendPages = append(resp.FrontendPages, &pb.FrontendPageProto{
Path: p.Path,
Expand All @@ -74,6 +86,9 @@ func (s *PluginGRPCServer) GetInfo(_ context.Context, _ *pb.Empty) (*pb.PluginIn
Audience: p.Audience,
})
}
if len(info.FrontendWidgets) > 0 {
resp.FrontendWidgets = make([]*pb.FrontendWidgetProto, 0, len(info.FrontendWidgets))
}
for _, w := range info.FrontendWidgets {
resp.FrontendWidgets = append(resp.FrontendWidgets, &pb.FrontendWidgetProto{
Slot: w.Slot,
Expand Down Expand Up @@ -166,7 +181,7 @@ func (s *PluginGRPCServer) GetWebAssets(_ context.Context, _ *pb.Empty) (*pb.Web
if len(assets) == 0 {
return &pb.WebAssetsResponse{HasAssets: false}, nil
}
resp := &pb.WebAssetsResponse{HasAssets: true}
resp := &pb.WebAssetsResponse{HasAssets: true, Files: make([]*pb.WebAssetFile, 0, len(assets))}
for path, content := range assets {
resp.Files = append(resp.Files, &pb.WebAssetFile{
Path: path,
Expand Down