Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
345129c7bf | ||
|
|
87eeb41364 |
No files matched your search
@@ -1,105 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/auth/password"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/database"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/file"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/llm"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/security"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/tools/weather"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/chat"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
var db database.Database
|
|
||||||
var store storage.StorageProvider
|
|
||||||
var userRepository domain.UserRepository
|
|
||||||
var providerRepository domain.ProviderRepository
|
|
||||||
var agentRepository domain.AgentRepository
|
|
||||||
var chatRepository domain.ChatRepository
|
|
||||||
var settingsRepository domain.SettingsRepository
|
|
||||||
var authenticators = make(map[string]auth.Authenticator)
|
|
||||||
var tokenManager auth.TokenManager
|
|
||||||
var llmengine domain.LLMEngine
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
db, err := database.GetDatabase()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.Migrate()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// repositories
|
|
||||||
userRepository = db.UserRepository()
|
|
||||||
providerRepository = db.ProviderRepository()
|
|
||||||
agentRepository = db.AgentRepository()
|
|
||||||
chatRepository = db.ChatRepository()
|
|
||||||
settingsRepository = db.SettingsRepository()
|
|
||||||
|
|
||||||
// storage
|
|
||||||
store, err = file.GetStorageProvider(db.FileRepository())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// authentication
|
|
||||||
tokenManager = security.NewJwtTokenManager(12*time.Hour, 7*24*time.Hour, "lassistanoque")
|
|
||||||
pwdAuth := password.NewPasswordAuthenticator(tokenManager, GetUserRepository())
|
|
||||||
authenticators["password"] = pwdAuth
|
|
||||||
|
|
||||||
// llm
|
|
||||||
llmengine = llm.NewAnyLLMEngine()
|
|
||||||
|
|
||||||
// llm tools
|
|
||||||
chat.RegisterTool(&weather.WeatherTool{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetStorageProvider() storage.StorageProvider {
|
|
||||||
return store
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserRepository() domain.UserRepository {
|
|
||||||
return userRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetProviderRepository() domain.ProviderRepository {
|
|
||||||
return providerRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAgentRepository() domain.AgentRepository {
|
|
||||||
return agentRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetChatRepository() domain.ChatRepository {
|
|
||||||
return chatRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSettingsRepository() domain.SettingsRepository {
|
|
||||||
return settingsRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAuthenticators() map[string]auth.Authenticator {
|
|
||||||
return authenticators
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTokenManager() auth.TokenManager {
|
|
||||||
return tokenManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLLMEngine() domain.LLMEngine {
|
|
||||||
return llmengine
|
|
||||||
}
|
|
||||||
|
|
||||||
func Close() {
|
|
||||||
db.Close()
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/auth/password"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/database"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/file"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/llm"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/security"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/tools/searxng"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/tools/weather"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/agent"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/chat"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/provider"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/storage"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/tool"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
var db database.Database
|
||||||
|
|
||||||
|
// adapters
|
||||||
|
var storeAdapter storage.StorageProvider
|
||||||
|
var llmengine domain.LLMEngine
|
||||||
|
|
||||||
|
// services
|
||||||
|
var authService *auth.Service
|
||||||
|
var userService *user.Service
|
||||||
|
var storageService *storage.Service
|
||||||
|
var providerService *provider.Service
|
||||||
|
var agentService *agent.Service
|
||||||
|
var toolService *tool.Service
|
||||||
|
var chatService *chat.Service
|
||||||
|
var tokenManager auth.TokenManager
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
db, err = database.GetDatabase()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Migrate()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapters
|
||||||
|
func getStoreAdapter() storage.StorageProvider {
|
||||||
|
if storeAdapter == nil {
|
||||||
|
var err error
|
||||||
|
storeAdapter, err = file.GetStorageProvider(db.FileRepository())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return storeAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthenticators() map[string]auth.Authenticator {
|
||||||
|
authenticators := make(map[string]auth.Authenticator)
|
||||||
|
pwdAuth := password.NewPasswordAuthenticator(GetTokenManager(), db.UserRepository())
|
||||||
|
authenticators["password"] = pwdAuth
|
||||||
|
return authenticators
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTokenManager() auth.TokenManager {
|
||||||
|
if tokenManager == nil {
|
||||||
|
tokenManager = security.NewJwtTokenManager(12*time.Hour, 7*24*time.Hour, "lassistanoque")
|
||||||
|
}
|
||||||
|
return tokenManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLLMAdapter() domain.LLMEngine {
|
||||||
|
if llmengine == nil {
|
||||||
|
llmengine = llm.NewAnyLLMEngine()
|
||||||
|
}
|
||||||
|
return llmengine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Services
|
||||||
|
func GetStorageService() *storage.Service {
|
||||||
|
if storageService == nil {
|
||||||
|
storageService = storage.NewService(getStoreAdapter())
|
||||||
|
}
|
||||||
|
return storageService
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAuthService() *auth.Service {
|
||||||
|
if authService == nil {
|
||||||
|
authService = auth.NewService(db.SettingsRepository(), db.UserRepository(), getAuthenticators())
|
||||||
|
}
|
||||||
|
return authService
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserService() *user.Service {
|
||||||
|
if userService == nil {
|
||||||
|
userService = user.NewService(db.UserRepository())
|
||||||
|
}
|
||||||
|
return userService
|
||||||
|
}
|
||||||
|
func GetProviderService() *provider.Service {
|
||||||
|
if providerService == nil {
|
||||||
|
providerService = provider.NewService(db.ProviderRepository(), db.UserRepository(), GetLLMAdapter())
|
||||||
|
}
|
||||||
|
return providerService
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAgentService() *agent.Service {
|
||||||
|
if agentService == nil {
|
||||||
|
agentService = agent.NewService(db.AgentRepository(), db.UserRepository())
|
||||||
|
}
|
||||||
|
return agentService
|
||||||
|
}
|
||||||
|
func GetChatService() *chat.Service {
|
||||||
|
if chatService == nil {
|
||||||
|
chatService = chat.NewService(
|
||||||
|
db.UserRepository(),
|
||||||
|
db.AgentRepository(),
|
||||||
|
db.ProviderRepository(),
|
||||||
|
db.ChatRepository(),
|
||||||
|
GetLLMAdapter(),
|
||||||
|
GetToolService(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return chatService
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetToolService() *tool.Service {
|
||||||
|
if toolService == nil {
|
||||||
|
toolService = tool.NewService(db.ToolRepository(), db.UserRepository())
|
||||||
|
toolService.RegisterToolImpl(&weather.WeatherTool{})
|
||||||
|
toolService.RegisterToolImpl(&searxng.SearxngTool{})
|
||||||
|
}
|
||||||
|
return toolService
|
||||||
|
}
|
||||||
+11
-26
@@ -11,14 +11,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/brain"
|
"trankilou.fr/lassistanoque/backend/internal/gateway"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/http"
|
"trankilou.fr/lassistanoque/backend/internal/http"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/agent"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/chat"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/provider"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/storage"
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -44,35 +38,26 @@ func runServe() {
|
|||||||
|
|
||||||
defer Close()
|
defer Close()
|
||||||
|
|
||||||
// services
|
|
||||||
authService := auth.NewService(GetSettingsRepository(), GetUserRepository(), GetAuthenticators())
|
|
||||||
userService := user.NewService(GetUserRepository())
|
|
||||||
storageService := storage.NewService(GetStorageProvider())
|
|
||||||
providerService := provider.NewService(GetProviderRepository(), GetUserRepository(), GetLLMEngine())
|
|
||||||
agentService := agent.NewService(GetAgentRepository(), GetUserRepository())
|
|
||||||
chatService := chat.NewService(GetUserRepository(), GetAgentRepository(), GetProviderRepository(), GetChatRepository(), GetLLMEngine())
|
|
||||||
|
|
||||||
// http server
|
// http server
|
||||||
httpRouter := http.NewRouter(http.Dependencies{
|
httpRouter := http.NewRouter(http.Dependencies{
|
||||||
StorageService: storageService,
|
StorageService: GetStorageService(),
|
||||||
AuthService: authService,
|
AuthService: GetAuthService(),
|
||||||
UserService: userService,
|
UserService: GetUserService(),
|
||||||
ProviderService: providerService,
|
ProviderService: GetProviderService(),
|
||||||
AgentService: agentService,
|
AgentService: GetAgentService(),
|
||||||
ChatService: chatService,
|
ChatService: GetChatService(),
|
||||||
|
ToolService: GetToolService(),
|
||||||
TokenManager: GetTokenManager(),
|
TokenManager: GetTokenManager(),
|
||||||
})
|
})
|
||||||
|
|
||||||
brainRouter := brain.NewRouter(brain.Dependencies{
|
gatewayRouter := gateway.NewRouter(gateway.Dependencies{})
|
||||||
ChatRepository: GetChatRepository(),
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
// start Brain router
|
// start Brain router
|
||||||
go func() {
|
go func() {
|
||||||
if err := brainRouter.Start(); err != nil && !errors.Is(err, nethttp.ErrServerClosed) {
|
if err := gatewayRouter.Start(); err != nil && !errors.Is(err, nethttp.ErrServerClosed) {
|
||||||
slog.Error("failed to start llm server", "error", err)
|
slog.Error("failed to start llm server", "error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -97,7 +82,7 @@ func runServe() {
|
|||||||
// stop LLM service
|
// stop LLM service
|
||||||
ctx, cancel2 := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel2 := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel2()
|
defer cancel2()
|
||||||
if err := brainRouter.Stop(ctx); err != nil {
|
if err := gatewayRouter.Stop(ctx); err != nil {
|
||||||
slog.Error("failed to stop llm server", "error", err)
|
slog.Error("failed to stop llm server", "error", err)
|
||||||
}
|
}
|
||||||
slog.Info("LLM server stopped gracefully")
|
slog.Info("LLM server stopped gracefully")
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Database interface {
|
|||||||
ProviderRepository() domain.ProviderRepository
|
ProviderRepository() domain.ProviderRepository
|
||||||
AgentRepository() domain.AgentRepository
|
AgentRepository() domain.AgentRepository
|
||||||
ChatRepository() domain.ChatRepository
|
ChatRepository() domain.ChatRepository
|
||||||
|
ToolRepository() domain.ToolRepository
|
||||||
Migrate() error
|
Migrate() error
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,3 +79,7 @@ func (db *TursoDB) AgentRepository() domain.AgentRepository {
|
|||||||
func (db *TursoDB) ChatRepository() domain.ChatRepository {
|
func (db *TursoDB) ChatRepository() domain.ChatRepository {
|
||||||
return NewTursoChatRepository(db.DB)
|
return NewTursoChatRepository(db.DB)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *TursoDB) ToolRepository() domain.ToolRepository {
|
||||||
|
return NewTursoToolRepository(db.DB)
|
||||||
|
}
|
||||||
@@ -95,9 +95,8 @@ create table tools (
|
|||||||
id text not null primary key,
|
id text not null primary key,
|
||||||
team_id text not null,
|
team_id text not null,
|
||||||
name text not null,
|
name text not null,
|
||||||
type text not null,
|
|
||||||
enabled numeric not null default 1,
|
enabled numeric not null default 1,
|
||||||
configuration text not null default '{}',
|
configuration text not null default '',
|
||||||
_date_created numeric not null default current_timestamp,
|
_date_created numeric not null default current_timestamp,
|
||||||
_date_updated numeric,
|
_date_updated numeric,
|
||||||
_version text not null
|
_version text not null
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package turso
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/database/orm"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TursoToolRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
ToolTable orm.Table[domain.Tool]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTursoToolRepository(db *sql.DB) *TursoToolRepository {
|
||||||
|
dialect := orm.NewSqliteDialect()
|
||||||
|
ToolTable, err := orm.NewTable[domain.Tool](db, dialect, "tools")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error creating lasebuche Tool table")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TursoToolRepository{
|
||||||
|
db: db,
|
||||||
|
ToolTable: ToolTable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TursoToolRepository) ListTools(userID string, teamID string) ([]*domain.Tool, error) {
|
||||||
|
return r.ToolTable.Select(
|
||||||
|
orm.WithWhere(
|
||||||
|
"team_id=$1 and team_id in (select team_id from user_teams where user_id=$2)",
|
||||||
|
teamID,
|
||||||
|
userID,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TursoToolRepository) GetToolByName(userID string, teamID string, name string) (*domain.Tool, error) {
|
||||||
|
return r.ToolTable.SelectOne(
|
||||||
|
"name=$1 and team_id=$2 and team_id in (select team_id from user_teams where user_id=$3)",
|
||||||
|
name,
|
||||||
|
teamID,
|
||||||
|
userID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TursoToolRepository) GetToolByID(userID string, teamID string, id string) (*domain.Tool, error) {
|
||||||
|
return r.ToolTable.SelectOne(
|
||||||
|
"id=$1 and team_id=$2 and team_id in (select team_id from user_teams where user_id=$3)",
|
||||||
|
id,
|
||||||
|
teamID,
|
||||||
|
userID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TursoToolRepository) CreateTool(userID string, Tool *domain.Tool) (*domain.Tool, error) {
|
||||||
|
return r.ToolTable.Insert(Tool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TursoToolRepository) UpdateTool(userID string, Tool *domain.Tool) (*domain.Tool, error) {
|
||||||
|
return r.ToolTable.Update(Tool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TursoToolRepository) DeleteTool(userID string, teamID string, name string) error {
|
||||||
|
return r.ToolTable.DeleteWhere(
|
||||||
|
"name=$1 and team_id=$2 and team_id in (select team_id from user_teams where user_id=$3)",
|
||||||
|
name,
|
||||||
|
teamID,
|
||||||
|
userID,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package llm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package searxng
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearxngConfig struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearxngTool struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SearxngTool) Name() string {
|
||||||
|
return "searxng"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SearxngTool) Description() string {
|
||||||
|
return "Search the web using a SearXNG instance. Returns a list of relevant results with title, URL, and snippet."
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SearxngTool) Params() []domain.ToolParam {
|
||||||
|
return []domain.ToolParam{
|
||||||
|
{JsonName: "url", Type: "string", Mandatory: true, Description: "Searxng URL"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SearxngTool) Definition(ctx context.Context) *domain.ToolDefinition {
|
||||||
|
return &domain.ToolDefinition{
|
||||||
|
Type: "function",
|
||||||
|
Function: &domain.ToolFunction{
|
||||||
|
Name: t.Name(),
|
||||||
|
Description: t.Description(),
|
||||||
|
Parameters: map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]any{
|
||||||
|
"query": map[string]any{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The search query",
|
||||||
|
},
|
||||||
|
"categories": map[string]any{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Comma-separated list of categories: general, news, images, science, it, social_media. Defaults to 'general'.",
|
||||||
|
},
|
||||||
|
"language": map[string]any{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Language code for results, e.g. 'fr', 'en'. Defaults to 'all'.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": []string{"query"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type searxngResponse struct {
|
||||||
|
Results []searxngResult `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type searxngResult struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Engine string `json:"engine"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SearxngTool) Execute(ctx context.Context, input []byte, configuration string) ([]byte, error) {
|
||||||
|
|
||||||
|
slog.Debug("[searxng]", "input", string(input))
|
||||||
|
|
||||||
|
var params struct {
|
||||||
|
Query string `json:"query"`
|
||||||
|
Categories string `json:"categories"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(input, ¶ms); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid parameters: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var toolCfg SearxngConfig
|
||||||
|
err := json.Unmarshal([]byte(configuration), &toolCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("searxng tool not configured: %w", err)
|
||||||
|
}
|
||||||
|
if toolCfg.URL == "" {
|
||||||
|
return nil, fmt.Errorf("searxng tool config missing 'url' field")
|
||||||
|
}
|
||||||
|
baseURL := toolCfg.URL
|
||||||
|
|
||||||
|
categories := "general"
|
||||||
|
if params.Categories != "" {
|
||||||
|
categories = params.Categories
|
||||||
|
}
|
||||||
|
language := "all"
|
||||||
|
if params.Language != "" {
|
||||||
|
language = params.Language
|
||||||
|
}
|
||||||
|
|
||||||
|
reqURL := fmt.Sprintf("%s/search?q=%s&format=json&categories=%s&language=%s",
|
||||||
|
strings.TrimRight(baseURL, "/"),
|
||||||
|
url.QueryEscape(params.Query),
|
||||||
|
url.QueryEscape(categories),
|
||||||
|
url.QueryEscape(language),
|
||||||
|
)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("searxng request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("searxng returned status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchResp searxngResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&searchResp); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(searchResp.Results) == 0 {
|
||||||
|
return []byte("No results found."), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
fmt.Fprintf(&sb, "Search results for \"%s\":\n\n", params.Query)
|
||||||
|
for i, r := range searchResp.Results {
|
||||||
|
fmt.Fprintf(&sb, "%d. %s\n", i+1, r.Title)
|
||||||
|
fmt.Fprintf(&sb, " URL: %s\n", r.URL)
|
||||||
|
if r.Content != "" {
|
||||||
|
fmt.Fprintf(&sb, " %s\n", r.Content)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&sb, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(sb.String()), nil
|
||||||
|
}
|
||||||
@@ -25,6 +25,10 @@ func (t *WeatherTool) Description() string {
|
|||||||
return "Get the current weather for a given location"
|
return "Get the current weather for a given location"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *WeatherTool) Params() []domain.ToolParam {
|
||||||
|
return []domain.ToolParam{}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *WeatherTool) Definition(ctx context.Context) *domain.ToolDefinition {
|
func (t *WeatherTool) Definition(ctx context.Context) *domain.ToolDefinition {
|
||||||
return &domain.ToolDefinition{
|
return &domain.ToolDefinition{
|
||||||
Type: "function",
|
Type: "function",
|
||||||
@@ -45,7 +49,7 @@ func (t *WeatherTool) Definition(ctx context.Context) *domain.ToolDefinition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *WeatherTool) Execute(ctx context.Context, input []byte) ([]byte, error) {
|
func (t *WeatherTool) Execute(ctx context.Context, input []byte, configuration string) ([]byte, error) {
|
||||||
|
|
||||||
var params struct {
|
var params struct {
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type Config struct {
|
|||||||
StorageS3AccessKeySecret string // S3
|
StorageS3AccessKeySecret string // S3
|
||||||
StorageS3UseSSL string // S3
|
StorageS3UseSSL string // S3
|
||||||
ServerID string
|
ServerID string
|
||||||
|
SearXngUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
var config *Config
|
var config *Config
|
||||||
|
|||||||
@@ -66,11 +66,19 @@ type ChatParams struct {
|
|||||||
OnChunk StreamCallback
|
OnChunk StreamCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tool interface {
|
type ToolParam struct {
|
||||||
|
JsonName string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Mandatory bool `json:"mandatory"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ITool interface {
|
||||||
Name() string
|
Name() string
|
||||||
Description() string
|
Description() string
|
||||||
|
Params() []ToolParam
|
||||||
Definition(ctx context.Context) *ToolDefinition
|
Definition(ctx context.Context) *ToolDefinition
|
||||||
Execute(ctx context.Context, input []byte) ([]byte, error)
|
Execute(ctx context.Context, input []byte, configuration string) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolDefinition struct {
|
type ToolDefinition struct {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Tool struct {
|
||||||
|
ID string `db:"id" json:"id"`
|
||||||
|
TeamID string `db:"team_id" json:"teamId"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Enabled bool `db:"enabled" json:"enabled"`
|
||||||
|
Configuration string `db:"configuration" json:"configuration"`
|
||||||
|
DateCreated time.Time `db:"_date_created" json:"_date_created"`
|
||||||
|
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
|
||||||
|
VersionId string `db:"_version" json:"_version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolRepository interface {
|
||||||
|
ListTools(userID string, teamID string) ([]*Tool, error)
|
||||||
|
GetToolByName(userID string, teamID string, name string) (*Tool, error)
|
||||||
|
GetToolByID(userID string, teamID string, id string) (*Tool, error)
|
||||||
|
CreateTool(userID string, agent *Tool) (*Tool, error)
|
||||||
|
UpdateTool(userID string, agent *Tool) (*Tool, error)
|
||||||
|
DeleteTool(userID string, teamID string, name string) error
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package brain
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package brain
|
package gateway
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
serverID string
|
serverID string
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/tool"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewToolGroup(
|
||||||
|
prefix string,
|
||||||
|
e *echo.Group,
|
||||||
|
ToolService *tool.Service,
|
||||||
|
userService *user.Service,
|
||||||
|
middlewares ...echo.MiddlewareFunc) *echo.Group {
|
||||||
|
|
||||||
|
ToolHandler := &ToolHandler{
|
||||||
|
ToolService: ToolService,
|
||||||
|
userService: userService,
|
||||||
|
}
|
||||||
|
Tool := e.Group(prefix, middlewares...)
|
||||||
|
|
||||||
|
Tool.GET("/:space", ToolHandler.ListTools)
|
||||||
|
Tool.GET("/:space/:tool", ToolHandler.GetTool)
|
||||||
|
Tool.GET("/:space/:tool/params", ToolHandler.GetToolParams)
|
||||||
|
Tool.PUT("/:space", ToolHandler.UpdateTool)
|
||||||
|
Tool.POST("/:space", ToolHandler.CreateTool)
|
||||||
|
//Tool.DELETE("/:space/:tool", ToolHandler.DeleteTool)
|
||||||
|
|
||||||
|
return Tool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolHandler struct {
|
||||||
|
ToolService *tool.Service
|
||||||
|
userService *user.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ToolHandler) ListTools(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
teamID := c.Param("space")
|
||||||
|
Tools, err := h.ToolService.ListTools(userID, teamID)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error("error listing Tools", "error", err)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "error listing Tools")
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, Tools)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ToolHandler) GetTool(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
teamID := c.Param("space")
|
||||||
|
ToolID := c.Param("tool")
|
||||||
|
Tool, err := h.ToolService.GetTool(userID, teamID, ToolID)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error("error getting Tool", "error", err)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "error getting Tool")
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, Tool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ToolHandler) GetToolParams(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
teamID := c.Param("space")
|
||||||
|
toolID := c.Param("tool")
|
||||||
|
tool, err := h.ToolService.GetTool(userID, teamID, toolID)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error("error getting Tool", "error", err, "teamid", teamID, "toolID", toolID)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "error getting Tool")
|
||||||
|
}
|
||||||
|
toolImpl := h.ToolService.GetToolImpl(tool.Name)
|
||||||
|
return c.JSON(http.StatusOK, toolImpl.Params())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ToolHandler) CreateTool(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
teamID := c.Param("space")
|
||||||
|
var Tool domain.Tool
|
||||||
|
err := c.Bind(&Tool)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error("error binding Tool", "error", err)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "error binding Tool")
|
||||||
|
}
|
||||||
|
Tool.TeamID = teamID
|
||||||
|
updTool, err := h.ToolService.CreateTool(userID, &Tool)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error("error creating Tool", "error", err)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "error creating Tool")
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, updTool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ToolHandler) UpdateTool(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
teamID := c.Param("space")
|
||||||
|
var Tool domain.Tool
|
||||||
|
err := c.Bind(&Tool)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error("error binding Tool", "error", err)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "error binding Tool")
|
||||||
|
}
|
||||||
|
Tool.TeamID = teamID
|
||||||
|
updTool, err := h.ToolService.UpdateTool(userID, &Tool)
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error("error updating Tool", "error", err)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "error updating Tool")
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, updTool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (h *ToolHandler) DeleteTool(c *echo.Context) error {
|
||||||
|
// userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
// teamID := c.Param("space")
|
||||||
|
// ToolID := c.Param("tool")
|
||||||
|
// err := h.ToolService.DeleteTool(userID, teamID, ToolID)
|
||||||
|
// if err != nil {
|
||||||
|
// c.Logger().Error("error creating Tool", "error", err)
|
||||||
|
// return echo.NewHTTPError(http.StatusBadRequest, "error creating Tool")
|
||||||
|
// }
|
||||||
|
// return c.JSON(http.StatusOK, ToolID)
|
||||||
|
// }
|
||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"trankilou.fr/lassistanoque/backend/internal/service/chat"
|
"trankilou.fr/lassistanoque/backend/internal/service/chat"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/provider"
|
"trankilou.fr/lassistanoque/backend/internal/service/provider"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/storage"
|
"trankilou.fr/lassistanoque/backend/internal/service/storage"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/tool"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ type Dependencies struct {
|
|||||||
ProviderService *provider.Service
|
ProviderService *provider.Service
|
||||||
AgentService *agent.Service
|
AgentService *agent.Service
|
||||||
ChatService *chat.Service
|
ChatService *chat.Service
|
||||||
|
ToolService *tool.Service
|
||||||
TokenManager auth.TokenManager
|
TokenManager auth.TokenManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +84,7 @@ func NewRouter(deps Dependencies) *Router {
|
|||||||
_ = handlers.NewMiscGroup("/misc", api, deps.TokenManager.TokenMiddleware)
|
_ = handlers.NewMiscGroup("/misc", api, deps.TokenManager.TokenMiddleware)
|
||||||
_ = handlers.NewModelGroup("/provider", api, deps.ProviderService, deps.TokenManager.TokenMiddleware)
|
_ = handlers.NewModelGroup("/provider", api, deps.ProviderService, deps.TokenManager.TokenMiddleware)
|
||||||
_ = handlers.NewAgentGroup("/agent", api, deps.AgentService, deps.UserService, deps.TokenManager.TokenMiddleware)
|
_ = handlers.NewAgentGroup("/agent", api, deps.AgentService, deps.UserService, deps.TokenManager.TokenMiddleware)
|
||||||
|
_ = handlers.NewToolGroup("/tool", api, deps.ToolService, deps.UserService, deps.TokenManager.TokenMiddleware)
|
||||||
_ = handlers.NewTeamGroup("/team", api, deps.UserService, deps.TokenManager.TokenMiddleware)
|
_ = handlers.NewTeamGroup("/team", api, deps.UserService, deps.TokenManager.TokenMiddleware)
|
||||||
_ = handlers.NewChatGroup("/chat", api, deps.ChatService, deps.TokenManager.TokenMiddleware)
|
_ = handlers.NewChatGroup("/chat", api, deps.ChatService, deps.TokenManager.TokenMiddleware)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/tool"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
@@ -17,6 +18,7 @@ type Service struct {
|
|||||||
repoChat domain.ChatRepository
|
repoChat domain.ChatRepository
|
||||||
llmEngine domain.LLMEngine
|
llmEngine domain.LLMEngine
|
||||||
runningSessions map[string]*ChatSession
|
runningSessions map[string]*ChatSession
|
||||||
|
toolService *tool.Service
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ func NewService(
|
|||||||
repoProvider domain.ProviderRepository,
|
repoProvider domain.ProviderRepository,
|
||||||
repoChat domain.ChatRepository,
|
repoChat domain.ChatRepository,
|
||||||
llmEngine domain.LLMEngine,
|
llmEngine domain.LLMEngine,
|
||||||
|
toolService *tool.Service,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
repoUser: repoUser,
|
repoUser: repoUser,
|
||||||
@@ -33,6 +36,7 @@ func NewService(
|
|||||||
repoProvider: repoProvider,
|
repoProvider: repoProvider,
|
||||||
repoChat: repoChat,
|
repoChat: repoChat,
|
||||||
llmEngine: llmEngine,
|
llmEngine: llmEngine,
|
||||||
|
toolService: toolService,
|
||||||
runningSessions: make(map[string]*ChatSession),
|
runningSessions: make(map[string]*ChatSession),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,6 +198,7 @@ func (s *Service) runQuery(
|
|||||||
modelID: modelID,
|
modelID: modelID,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
llmEngine: s.llmEngine,
|
llmEngine: s.llmEngine,
|
||||||
|
toolService: s.toolService,
|
||||||
subscribers: make([]domain.StreamCallback, 0),
|
subscribers: make([]domain.StreamCallback, 0),
|
||||||
repoChat: s.repoChat,
|
repoChat: s.repoChat,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,17 @@ package chat
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"log/slog"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/service/tool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed system_prompt.md
|
||||||
|
var systemSystemPrompt string
|
||||||
|
|
||||||
type ChatSession struct {
|
type ChatSession struct {
|
||||||
userID string
|
userID string
|
||||||
teamID string
|
teamID string
|
||||||
@@ -16,6 +22,7 @@ type ChatSession struct {
|
|||||||
modelID string
|
modelID string
|
||||||
messages []*domain.Message
|
messages []*domain.Message
|
||||||
llmEngine domain.LLMEngine
|
llmEngine domain.LLMEngine
|
||||||
|
toolService *tool.Service
|
||||||
repoChat domain.ChatRepository
|
repoChat domain.ChatRepository
|
||||||
subscribers []domain.StreamCallback
|
subscribers []domain.StreamCallback
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@@ -26,7 +33,7 @@ func (s *ChatSession) run(ctx context.Context) error {
|
|||||||
params := &domain.LLMParams{}
|
params := &domain.LLMParams{}
|
||||||
|
|
||||||
tools := make([]*domain.ToolDefinition, 0)
|
tools := make([]*domain.ToolDefinition, 0)
|
||||||
for _, t := range allTools {
|
for _, t := range s.toolService.GetAllToolImpl() {
|
||||||
tools = append(tools, t.Definition(ctx))
|
tools = append(tools, t.Definition(ctx))
|
||||||
}
|
}
|
||||||
params.Tools = tools
|
params.Tools = tools
|
||||||
@@ -43,7 +50,7 @@ func (s *ChatSession) run(ctx context.Context) error {
|
|||||||
|
|
||||||
systemPrompt := &domain.Message{
|
systemPrompt := &domain.Message{
|
||||||
Role: string(domain.RoleSystem),
|
Role: string(domain.RoleSystem),
|
||||||
Content: s.agent.SystemPrompt,
|
Content: systemSystemPrompt + "\n\n## Customization\n\n" + s.agent.SystemPrompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
continue_loop := true
|
continue_loop := true
|
||||||
@@ -72,8 +79,21 @@ func (s *ChatSession) run(ctx context.Context) error {
|
|||||||
continue_loop = true
|
continue_loop = true
|
||||||
for _, tc := range msg.ToolCalls {
|
for _, tc := range msg.ToolCalls {
|
||||||
|
|
||||||
tool := GetTool(tc.Function.Name)
|
toolImpl := s.toolService.GetToolImpl(tc.Function.Name)
|
||||||
output, err := tool.Execute(ctx, []byte(tc.Function.Arguments))
|
slog.Info("Execute tool", "name", tc.Function.Name, "impl", toolImpl)
|
||||||
|
toolRecord, err := s.toolService.GetToolByName(s.userID, s.teamID, toolImpl.Name())
|
||||||
|
if err != nil {
|
||||||
|
slog.Info("Execute tool", "error", err)
|
||||||
|
toolResponse := &domain.Message{
|
||||||
|
ChatID: s.chatID,
|
||||||
|
TeamID: s.teamID,
|
||||||
|
ToolCallID: tc.ID,
|
||||||
|
Role: string(domain.RoleTool),
|
||||||
|
Content: "ERROR: " + err.Error(),
|
||||||
|
}
|
||||||
|
s.repoChat.CreateChatMessage(s.userID, toolResponse)
|
||||||
|
}
|
||||||
|
output, err := toolImpl.Execute(ctx, []byte(tc.Function.Arguments), toolRecord.Configuration)
|
||||||
var toolResponse *domain.Message
|
var toolResponse *domain.Message
|
||||||
if err != nil {
|
if err != nil {
|
||||||
toolResponse = &domain.Message{
|
toolResponse = &domain.Message{
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
You are an assistant that helps users by relying on the tools and skills made available to you. Reply in the user's language.
|
||||||
|
|
||||||
|
## Style: be direct
|
||||||
|
|
||||||
|
Answer precisely and concisely. Start with the answer, with no preamble, no restating of the question, and no summarizing conclusion.
|
||||||
|
|
||||||
|
Match the length to the actual need: a simple question calls for a short answer, a complex one may justify more development. Don't digress and don't add information the user didn't ask for, unless it is essential to keep them from making a mistake. If the answer fits in one sentence, one sentence is enough.
|
||||||
|
|
||||||
|
Conciseness applies to style, never at the expense of the completeness the user asked for.
|
||||||
|
|
||||||
|
## Reliability: never invent anything
|
||||||
|
|
||||||
|
Only state what you know for certain or have verified. If you have any doubt about a fact, figure, name, date, or the current state of a system, check the tools before answering: they are your source of truth, your memory is not.
|
||||||
|
|
||||||
|
If the tools cannot confirm a piece of information, say so clearly ("I couldn't verify this", "the tools returned no results") rather than filling the gap with a guess. Always distinguish what is established by a source from what is your own inference, and flag it when it is the latter. Saying "I don't know" is always better than giving an invented answer.
|
||||||
|
|
||||||
|
## Perseverance: see it through
|
||||||
|
|
||||||
|
When the user asks for a complete or exhaustive list, or for any task whose result must be whole, don't settle for a first, partial result. Check whether results are paginated or truncated, rerun the tools with other parameters, pages, or queries, and cross-check sources until you have covered the requested scope.
|
||||||
|
|
||||||
|
Never present a partial result as complete. If, despite your efforts, you cannot guarantee completeness (tool limitation, inaccessible source), state precisely what is missing or could not be verified. If a tool fails or returns an unusable result, try another approach before concluding.
|
||||||
|
|
||||||
|
## Tools and skills
|
||||||
|
|
||||||
|
Use the available tools and skills whenever they can help you answer more accurately, verify a fact, or complete the task. Don't answer from memory when a tool can provide the answer. If several tools are relevant, combine them. When a task matches an available skill, consult it before starting.
|
||||||
|
|
||||||
|
You may skip them for purely conversational exchanges or questions whose answer depends on no information that needs verifying.
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package chat
|
|
||||||
|
|
||||||
import "trankilou.fr/lassistanoque/backend/internal/domain"
|
|
||||||
|
|
||||||
var allTools = make(map[string]domain.Tool)
|
|
||||||
|
|
||||||
func RegisterTool(tool domain.Tool) error {
|
|
||||||
allTools[tool.Name()] = tool
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTool(name string) domain.Tool {
|
|
||||||
return allTools[name]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package tool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repo domain.ToolRepository
|
||||||
|
repoUser domain.UserRepository
|
||||||
|
allTools map[string]domain.ITool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(
|
||||||
|
repo domain.ToolRepository,
|
||||||
|
repoUser domain.UserRepository,
|
||||||
|
) *Service {
|
||||||
|
return &Service{
|
||||||
|
repo: repo,
|
||||||
|
repoUser: repoUser,
|
||||||
|
allTools: make(map[string]domain.ITool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListTools(userID string, teamID string) ([]*domain.Tool, error) {
|
||||||
|
|
||||||
|
for _, tool := range s.allTools {
|
||||||
|
_, err := s.repo.GetToolByName(userID, teamID, tool.Name())
|
||||||
|
slog.Info("ListTools", "err1", err)
|
||||||
|
if err != nil {
|
||||||
|
_, err = s.repo.CreateTool(userID, &domain.Tool{
|
||||||
|
TeamID: teamID,
|
||||||
|
Name: tool.Name(),
|
||||||
|
Enabled: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Info("ListTools", "err2", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.ListTools(userID, teamID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetTool(userID string, teamID string, id string) (*domain.Tool, error) {
|
||||||
|
return s.repo.GetToolByID(userID, teamID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetToolByName(userID string, teamID string, name string) (*domain.Tool, error) {
|
||||||
|
return s.repo.GetToolByName(userID, teamID, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateTool(userID string, Tool *domain.Tool) (*domain.Tool, error) {
|
||||||
|
|
||||||
|
if _, err := s.repoUser.FindUserTeam(userID, Tool.TeamID); err != nil {
|
||||||
|
return nil, fmt.Errorf("error finding user in team: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.CreateTool(userID, Tool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateTool(userID string, Tool *domain.Tool) (*domain.Tool, error) {
|
||||||
|
if _, err := s.repoUser.FindUserTeam(userID, Tool.TeamID); err != nil {
|
||||||
|
return nil, fmt.Errorf("error finding user in team: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.repo.GetToolByID(userID, Tool.TeamID, Tool.ID); err != nil {
|
||||||
|
return nil, fmt.Errorf("error finding Tool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.UpdateTool(userID, Tool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteTool(userID string, teamID string, id string) error {
|
||||||
|
if _, err := s.repoUser.FindUserTeam(userID, teamID); err != nil {
|
||||||
|
return fmt.Errorf("error finding user in team: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.repo.GetToolByID(userID, teamID, id); err != nil {
|
||||||
|
return fmt.Errorf("error finding Tool: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.DeleteTool(userID, teamID, id)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) RegisterToolImpl(tool domain.ITool) error {
|
||||||
|
s.allTools[tool.Name()] = tool
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllToolImpl() map[string]domain.ITool {
|
||||||
|
return s.allTools
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetToolImpl(name string) domain.ITool {
|
||||||
|
return s.allTools[name]
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
|||||||
|
import { api } from '../client';
|
||||||
|
import type { Tool, ToolParam } from "$lib/types/api"
|
||||||
|
|
||||||
|
export const toolApi = {
|
||||||
|
getTools: (space: string) => api.get<Tool[]>(`/tool/${space}`),
|
||||||
|
getTool: (space: string, id: string) => api.get<Tool>(`/tool/${space}/${id}`),
|
||||||
|
getToolParams: (space: string, id: string) => api.get<ToolParam[]>(`/tool/${space}/${id}/params`),
|
||||||
|
createTool: (space: string,data: Partial<Tool>) => api.post<Tool>(`/tool/${space}`, data),
|
||||||
|
updateTool: (space: string,data: Partial<Tool>) => api.put<Tool>(`/tool/${space}`, data),
|
||||||
|
};
|
||||||
@@ -104,6 +104,24 @@ export interface Message {
|
|||||||
_version: string
|
_version: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Tool {
|
||||||
|
id: string
|
||||||
|
teamId: string
|
||||||
|
name: string
|
||||||
|
configuration: string
|
||||||
|
enabled: boolean
|
||||||
|
_date_created: string
|
||||||
|
_date_updated: string
|
||||||
|
_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolParam {
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
description: string
|
||||||
|
mandatory: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface ToolCall {
|
export interface ToolCall {
|
||||||
ID: string
|
ID: string
|
||||||
Type: string
|
Type: string
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {autofocus} from "$lib/ui/autofocus"
|
import {autofocus} from "$lib/ui/autofocus"
|
||||||
import { onMount } from "svelte"
|
import {onMount, tick } from "svelte"
|
||||||
import {page} from "$app/state"
|
import {page} from "$app/state"
|
||||||
import {chatApi} from "$lib/api/endpoints/chat"
|
import {chatApi} from "$lib/api/endpoints/chat"
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
@@ -20,9 +20,12 @@
|
|||||||
let chatWithMessage : ChatWithMessages = await chatApi.getChat(data.spaceId, data.chatId)
|
let chatWithMessage : ChatWithMessages = await chatApi.getChat(data.spaceId, data.chatId)
|
||||||
messages = chatWithMessage.messages
|
messages = chatWithMessage.messages
|
||||||
}
|
}
|
||||||
|
await tick()
|
||||||
|
historyDiv.scrollTop = historyDiv.scrollHeight;
|
||||||
})
|
})
|
||||||
|
|
||||||
let historyDiv: HTMLDivElement;
|
let historyDiv: HTMLDivElement;
|
||||||
|
let inputPrompt: HTMLDivElement;
|
||||||
|
|
||||||
const sendRequest = async(theprompt:string) => {
|
const sendRequest = async(theprompt:string) => {
|
||||||
prompt = ""
|
prompt = ""
|
||||||
@@ -34,6 +37,12 @@
|
|||||||
if (waiting && chunk.role==="assistant") {
|
if (waiting && chunk.role==="assistant") {
|
||||||
waiting = false
|
waiting = false
|
||||||
}
|
}
|
||||||
|
if (chunk.done) {
|
||||||
|
document
|
||||||
|
.querySelectorAll<HTMLDivElement>('div.reasoning:not(.hidden)')
|
||||||
|
.forEach((el) => el.classList.add('hidden'));
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (chunk.role!=lastrole || chunk.toolName!=lastToolName) {
|
if (chunk.role!=lastrole || chunk.toolName!=lastToolName) {
|
||||||
const currentDate = new Date()
|
const currentDate = new Date()
|
||||||
@@ -80,7 +89,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleOnSubmit = async ()=>{
|
const handleOnSubmit = async ()=>{
|
||||||
|
historyDiv.scrollTop = historyDiv.scrollHeight;
|
||||||
await sendRequest(prompt)
|
await sendRequest(prompt)
|
||||||
|
inputPrompt.focus()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -94,7 +105,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if message.toolcalls != null && message.toolcalls.length>0}
|
{#if message.toolcalls != null && message.toolcalls.length>0}
|
||||||
<div class="text-green-600">⚒️ Appel de l'outil {message.toolcalls[0].Function.Name}</div>
|
<div class="tool text-sm mt-2 py-1 px-2 rounded">⚒️ Appel de l'outil {message.toolcalls[0].Function.Name}</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
<SvelteMarkdown source={message.content} streaming={true}/>
|
<SvelteMarkdown source={message.content} streaming={true}/>
|
||||||
@@ -114,6 +125,7 @@
|
|||||||
placeholder="..."
|
placeholder="..."
|
||||||
bind:value={prompt}
|
bind:value={prompt}
|
||||||
use:autofocus
|
use:autofocus
|
||||||
|
bind:this={inputPrompt}
|
||||||
/>
|
/>
|
||||||
<button class="chatbutton">Go</button>
|
<button class="chatbutton">Go</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -17,9 +17,10 @@
|
|||||||
href="settings/tools"
|
href="settings/tools"
|
||||||
>
|
>
|
||||||
Outils
|
Outils
|
||||||
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">12</span>
|
<!-- <span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">12</span>-->
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<!--
|
||||||
<a class="bt p-5 text-center"
|
<a class="bt p-5 text-center"
|
||||||
title="Skills"
|
title="Skills"
|
||||||
href="settings/skills">
|
href="settings/skills">
|
||||||
@@ -58,4 +59,6 @@
|
|||||||
Utilisateurs
|
Utilisateurs
|
||||||
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">1</span>
|
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">1</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
@@ -108,7 +108,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleKeydown = (event: KeyboardEvent) => {
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
event.preventDefault()
|
|
||||||
if (event.key==="Escape") {
|
if (event.key==="Escape") {
|
||||||
handleCancelEditAgent()
|
handleCancelEditAgent()
|
||||||
}
|
}
|
||||||
@@ -149,7 +148,7 @@
|
|||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="rounded border p-2 my-2 border-stone-500 flex">
|
<div class="rounded border p-2 my-2 border-stone-500 flex formgroup">
|
||||||
<div class="flex-1 p-2">
|
<div class="flex-1 p-2">
|
||||||
<span>{ agent.name }</span>
|
<span>{ agent.name }</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,7 +102,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleKeydown = (event: KeyboardEvent) => {
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
event.preventDefault()
|
|
||||||
if (event.key==="Escape") {
|
if (event.key==="Escape") {
|
||||||
cancelEditProviderHandler()
|
cancelEditProviderHandler()
|
||||||
}
|
}
|
||||||
@@ -146,7 +145,7 @@
|
|||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="rounded border p-2 my-2 border-stone-500 flex">
|
<div class="rounded border p-2 my-2 border-stone-500 flex formgroup">
|
||||||
<div class="flex-1 p-2">
|
<div class="flex-1 p-2">
|
||||||
<span>{ p.name }</span>
|
<span>{ p.name }</span>
|
||||||
<span class="text-stone-500">({ p.type })</span>
|
<span class="text-stone-500">({ p.type })</span>
|
||||||
|
|||||||
@@ -1 +1,96 @@
|
|||||||
<div>Modèles</div>
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
|
import { untrack } from 'svelte';
|
||||||
|
|
||||||
|
import type { Tool } from '$lib/types/api'
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import { toolApi } from "$lib/api/endpoints/tool"
|
||||||
|
|
||||||
|
//import { validateUrl } from '$lib/utilities/validators'
|
||||||
|
import ToolForm from './ToolForm.svelte'
|
||||||
|
|
||||||
|
let { data }: PageProps = $props();
|
||||||
|
|
||||||
|
let tools = $state(untrack(()=>data.tools))
|
||||||
|
let adding = $state(false)
|
||||||
|
let editing = $state(null as string|null)
|
||||||
|
let errors = new SvelteMap<string,string>()
|
||||||
|
|
||||||
|
const cancelEditHandler = () => {
|
||||||
|
editing = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitEdit = (tool: Tool) => {
|
||||||
|
|
||||||
|
if (errors.size === 0) {
|
||||||
|
toolApi.updateTool(data.space,tool)
|
||||||
|
.then((t)=>{
|
||||||
|
tools = tools.map((it)=>{return t.id===it.id ? t : it})
|
||||||
|
editing = null
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = async (id: string) => {
|
||||||
|
errors.clear()
|
||||||
|
if (adding) return
|
||||||
|
toolApi.getTool(data.space,id)
|
||||||
|
.then((data)=>{
|
||||||
|
tools = tools.map((it)=>it.id===id ? data : it)
|
||||||
|
editing = id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// const handleDelete = (id: string) => {
|
||||||
|
// toolApi.deleteTool(data.space,id)
|
||||||
|
// .then(() => {
|
||||||
|
// tools = tools.filter((it)=>it.id!==id)
|
||||||
|
// })
|
||||||
|
// .catch((err) => {console.log(err)})
|
||||||
|
// }
|
||||||
|
|
||||||
|
const handleKeydown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key==="Escape") {
|
||||||
|
cancelEditHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window onkeydown={handleKeydown} />
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
{#each tools as t (t.id) }
|
||||||
|
{#if editing===t.id}
|
||||||
|
{#key t.id}
|
||||||
|
<ToolForm
|
||||||
|
item={t}
|
||||||
|
errors={errors}
|
||||||
|
onSave={submitEdit}
|
||||||
|
onCancel={cancelEditHandler}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
{:else}
|
||||||
|
<div class="rounded border p-2 my-2 border-stone-500 flex formgroup">
|
||||||
|
<div class="flex-1 p-2">
|
||||||
|
<span>{ t.name }</span>
|
||||||
|
<span class="text-stone-500">({ t.enabled })</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="bt small mx-1"
|
||||||
|
onclick={()=>handleEdit(t.id)}
|
||||||
|
aria-label="Modifier"
|
||||||
|
>
|
||||||
|
<i class="icon-edit"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
export function load() {
|
import { toolApi } from "$lib/api/endpoints/tool"
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ params }) => {
|
||||||
return {
|
return {
|
||||||
title: 'Paramétrage : Outils',
|
title: 'Paramétrage : Outils',
|
||||||
back: "/settings",
|
back: "../settings",
|
||||||
|
space: params.space,
|
||||||
|
tools: await toolApi.getTools(params.space),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import FormError from '$lib/components/FormError.svelte'
|
||||||
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
|
import { toolApi } from "$lib/api/endpoints/tool"
|
||||||
|
import type { Tool, ToolParam } from "$lib/types/api"
|
||||||
|
import { onMount, untrack } from 'svelte';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: Tool
|
||||||
|
errors: Map<string,string>
|
||||||
|
onSave: (item: Tool) => void
|
||||||
|
onCancel: () => void
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
item,
|
||||||
|
errors,
|
||||||
|
onSave,
|
||||||
|
onCancel,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let tool = $state(untrack(()=>item))
|
||||||
|
let toolParams = $state([] as ToolParam[])
|
||||||
|
let paramValues = new SvelteMap<string, string>();
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
toolParams = await toolApi.getToolParams(item.teamId, item.id)
|
||||||
|
paramValues = new SvelteMap<string, string>(Object.entries(JSON.parse(item.configuration)));
|
||||||
|
})
|
||||||
|
|
||||||
|
const autofocus = (node: HTMLElement) => {
|
||||||
|
node.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
item.configuration = JSON.stringify(Object.fromEntries(paramValues));
|
||||||
|
onSave(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="formgroup my-2">
|
||||||
|
<form onsubmit={handleSave} onchange={()=>{errors.clear()}}>
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="name">Nom</label>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
bind:value={tool.name}
|
||||||
|
class="textinput"
|
||||||
|
placeholder="nom affiché"
|
||||||
|
disabled
|
||||||
|
use:autofocus
|
||||||
|
/>
|
||||||
|
<FormError key="name" errors={errors} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="enabled">Actif</label>
|
||||||
|
<input
|
||||||
|
id="enabled"
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={tool.enabled}
|
||||||
|
class="checkboxinput"
|
||||||
|
/>
|
||||||
|
<FormError key="baseUrl" errors={errors} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{#each toolParams as param (param.name)}
|
||||||
|
|
||||||
|
<div class="formelement">
|
||||||
|
<label for={param.name}>{param.name}</label>
|
||||||
|
<input
|
||||||
|
id={param.name}
|
||||||
|
bind:value={() => paramValues.get(param.name) ?? '', (v) => paramValues.set(param.name, v)}
|
||||||
|
class="textinput"
|
||||||
|
placeholder={param.description}
|
||||||
|
/>
|
||||||
|
<FormError key="baseUrl" errors={errors} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="text-right">
|
||||||
|
<button type="button" class="bt" onclick={handleCancel}>Annuler</button>
|
||||||
|
<button type="submit" class="bt mx-4">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -137,6 +137,10 @@ h2 {
|
|||||||
@apply h-8 w-full rounded border-stone-300 bg-stone-50 px-2 py-1 text-sm text-stone-900 dark:border-stone-700 dark:bg-stone-950 dark:text-stone-100;
|
@apply h-8 w-full rounded border-stone-300 bg-stone-50 px-2 py-1 text-sm text-stone-900 dark:border-stone-700 dark:bg-stone-950 dark:text-stone-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkboxinput {
|
||||||
|
@apply rounded border border-stone-500 bg-stone-100 text-sm text-stone-900 dark:bg-stone-900 dark:text-stone-100;
|
||||||
|
}
|
||||||
|
|
||||||
.areainput {
|
.areainput {
|
||||||
@apply w-full rounded border-stone-300 bg-stone-50 px-2 py-1 text-sm text-stone-900 dark:bg-stone-950 dark:text-stone-100;
|
@apply w-full rounded border-stone-300 bg-stone-50 px-2 py-1 text-sm text-stone-900 dark:bg-stone-950 dark:text-stone-100;
|
||||||
}
|
}
|
||||||
@@ -275,25 +279,60 @@ h2 {
|
|||||||
/* chat */
|
/* chat */
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
@apply rounded p-2;
|
|
||||||
|
|
||||||
&.user {
|
&.user {
|
||||||
@apply bg-sky-700 dark:text-white;
|
@apply bg-sky-500/20 px-2 py-1 text-sky-700 dark:text-sky-300;
|
||||||
|
}
|
||||||
|
.tool {
|
||||||
|
@apply bg-green-500/20 px-2 py-1 text-green-700 dark:text-green-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
@apply my-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
@apply my-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@apply text-2xl font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@apply text-xl font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
@apply text-lg font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
@apply text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
@apply text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
@apply ml-5 list-disc pl-4 marker:text-xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
@apply my-2 rounded px-2 py-1 text-xs dark:bg-stone-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
@apply my-2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
@apply text-sm;
|
@apply text-sm;
|
||||||
|
@apply whitespace-pre-line;
|
||||||
|
|
||||||
&.reasoning {
|
&.reasoning {
|
||||||
@apply text-white/50;
|
@apply text-white/50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-content p {
|
|
||||||
@apply my-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content pre {
|
|
||||||
@apply my-2 whitespace-pre dark:bg-stone-700;
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user