Files
yawan f7fc992f57
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
2026-09-22 16:19:06 +08:00

342 lines
13 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package types
import (
"database/sql/driver"
"encoding/json"
"strings"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// FallbackStrategy represents the fallback strategy type
type FallbackStrategy string
const (
FallbackStrategyFixed FallbackStrategy = "fixed" // Fixed response
FallbackStrategyModel FallbackStrategy = "model" // Model fallback response
)
// SummaryConfig represents the summary configuration for a session
type SummaryConfig struct {
// Max tokens
MaxTokens int `json:"max_tokens"`
// Repeat penalty
RepeatPenalty float64 `json:"repeat_penalty"`
// TopK
TopK int `json:"top_k"`
// TopP
TopP float64 `json:"top_p"`
// Frequency penalty
FrequencyPenalty float64 `json:"frequency_penalty"`
// Presence penalty
PresencePenalty float64 `json:"presence_penalty"`
// Prompt
Prompt string `json:"prompt"`
// Context template
ContextTemplate string `json:"context_template"`
// No match prefix
NoMatchPrefix string `json:"no_match_prefix"`
// Temperature
Temperature float64 `json:"temperature"`
// Seed
Seed int `json:"seed"`
// Max completion tokens
MaxCompletionTokens int `json:"max_completion_tokens"`
// Thinking - whether to enable thinking mode
Thinking *bool `json:"thinking"`
}
// ContextCompressionStrategy represents the strategy for context compression
type ContextCompressionStrategy string
const (
// ContextCompressionSlidingWindow keeps the most recent N messages
ContextCompressionSlidingWindow ContextCompressionStrategy = "sliding_window"
// ContextCompressionSmart uses LLM to summarize old messages
ContextCompressionSmart ContextCompressionStrategy = "smart"
)
// ContextConfig configures LLM context management
// This is separate from message storage and manages token limits
type ContextConfig struct {
// Maximum tokens allowed in LLM context
MaxTokens int `json:"max_tokens"`
// Compression strategy: "sliding_window" or "smart"
CompressionStrategy ContextCompressionStrategy `json:"compression_strategy"`
// For sliding_window: number of messages to keep
// For smart: number of recent messages to keep uncompressed
RecentMessageCount int `json:"recent_message_count"`
// Summarize threshold: number of messages before summarization
SummarizeThreshold int `json:"summarize_threshold"`
}
// Session represents the session
type Session struct {
// ID
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
// Title
Title string `json:"title"`
// Description
Description string `json:"description"`
// Workspace ID
TenantID uint64 `json:"tenant_id" gorm:"index"`
// UserID is the owner scope for this session. WeKnora user UUIDs, API
// external-user principals, and embed visitor principals all use this column.
UserID string `json:"user_id,omitempty" gorm:"type:varchar(512);index"`
// IsPinned indicates whether the session is pinned in the list.
IsPinned bool `json:"is_pinned" gorm:"default:false"`
// PinnedAt records when the session was pinned; nil when not pinned.
PinnedAt *time.Time `json:"pinned_at,omitempty"`
// LastRequestState records the input-bar state used the last time this
// session sent a question (agent, model, KB scope, web search, MCPs).
// Persisted on every successful POST to /knowledge-chat or /agent-chat so
// that reopening the session can restore the original request context to
// the chat UI. Stored in the legacy sessions.agent_config JSONB column to
// avoid a new migration; the shape used today is `SessionLastRequestState`.
LastRequestState *SessionLastRequestState `json:"last_request_state,omitempty" gorm:"column:agent_config;type:jsonb"`
// SandboxConfigID pins which sandbox config this session's CURRENT live
// sandbox was created on. Empty means no live sandbox;
// SandboxConfigIDGlobalDefault means the deployment-wide default config.
//
// This is an ephemeral pin that dies with the sandbox, not a permanent
// owner: sessions outlive sandboxes by months, so treating it as
// permanent would make "no session references this config" never true.
SandboxConfigID string `json:"sandbox_config_id,omitempty" gorm:"type:varchar(36)"`
// // Strategy configuration
// KnowledgeBaseID string `json:"knowledge_base_id"` // 关联的知识库ID
// MaxRounds int `json:"max_rounds"` // 多轮保持轮数
// EnableRewrite bool `json:"enable_rewrite"` // 多轮改写开关
// FallbackStrategy FallbackStrategy `json:"fallback_strategy"` // 兜底策略
// FallbackResponse string `json:"fallback_response"` // 固定回复内容
// EmbeddingTopK int `json:"embedding_top_k"` // 向量召回TopK
// KeywordThreshold float64 `json:"keyword_threshold"` // 关键词召回阈值
// VectorThreshold float64 `json:"vector_threshold"` // 向量召回阈值
// RerankModelID string `json:"rerank_model_id"` // 排序模型ID
// RerankTopK int `json:"rerank_top_k"` // 排序TopK
// RerankThreshold float64 `json:"rerank_threshold"` // 排序阈值
// SummaryModelID string `json:"summary_model_id"` // 总结模型ID
// SummaryParameters *SummaryConfig `json:"summary_parameters" gorm:"type:json"` // 总结模型参数
// AgentConfig *SessionAgentConfig `json:"agent_config" gorm:"type:jsonb"` // Agent 配置(会话级别,仅存储enabled和knowledge_bases)
// ContextConfig *ContextConfig `json:"context_config" gorm:"type:jsonb"` // 上下文管理配置(可选)
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
// IMPlatform is the originating IM platform (e.g. "feishu", "wecom") when
// this session is bound to an IM channel. It is not stored on the sessions
// table (it lives in im_channel_sessions) and is populated on read so the
// Web console can classify a session's origin folder without a list query.
IMPlatform string `json:"im_platform,omitempty" gorm:"-"`
// Association relationship, not stored in the database
Messages []Message `json:"-" gorm:"foreignKey:SessionID"`
}
func (s *Session) BeforeCreate(tx *gorm.DB) (err error) {
s.ID = uuid.New().String()
return nil
}
// SessionSourceAPI is the source filter that lists every session created via a
// tenant API key across the whole tenant. It is an admin-only view: the service
// layer requires Admin+ and drops the per-user owner scope so a tenant
// Owner/admin can observe API-key traffic that is otherwise isolated per key.
const SessionSourceAPI = "api"
// SessionSourceWeb is the source filter for a user's own Web-console chats.
const SessionSourceWeb = "web"
// SessionListSourceRequiresAdmin reports whether a session-list source filter
// exposes tenant-wide channel traffic (API / IM / embed) in the Web console.
func SessionListSourceRequiresAdmin(source string) bool {
src := strings.TrimSpace(source)
if src == "" || strings.EqualFold(src, SessionSourceWeb) {
return false
}
return true
}
// SessionRequiresAdminConsoleRead reports whether a session row is channel-
// managed traffic that non-admin web users must not open from the console.
func SessionRequiresAdminConsoleRead(s *Session, imPlatform string) bool {
if s == nil {
return false
}
if IsAPISessionOwnerID(s.UserID) {
return true
}
if strings.HasPrefix(s.Description, EmbedSessionMarkerPrefix) ||
strings.HasPrefix(s.UserID, PrincipalEmbedSession+":") {
return true
}
// Defence in depth for skill maintenance transcripts: the listing hides
// them, and this keeps a leaked session id from being opened by a
// non-admin who happens to own the row.
if IsSkillMaintenanceDescription(s.Description) {
return true
}
return strings.TrimSpace(imPlatform) != ""
}
// IsSkillMaintenanceDescription reports whether description is the reserved
// prefix that hides skill-install sessions from the console list.
func IsSkillMaintenanceDescription(description string) bool {
return strings.HasPrefix(description, SkillMaintenanceSessionMarker)
}
// SanitizeClientSessionDescription keeps the skill-maintenance marker off
// client-writable descriptions. A row that is already a maintenance session
// keeps its stored description so a PUT cannot un-hide it; any other row
// drops a planted marker rather than accepting it.
func SanitizeClientSessionDescription(incoming, existing string) string {
if IsSkillMaintenanceDescription(existing) {
return existing
}
if IsSkillMaintenanceDescription(incoming) {
return ""
}
return incoming
}
// SessionListQuery bundles the parameters for listing sessions.
// UserID empty means "tenant-wide" (used by API-key callers / legacy rows).
// Keyword matches title ILIKE '%keyword%'.
// Source values: "web" (user chats, no IM/embed), "embed" / "embed:{channelID}",
// "api" (all API-key sessions, Admin+ only), or an IM platform name
// (e.g. "feishu", "wechat"). IM and embed sources are also Admin+ only.
// AgentID currently only filters sessions that have an IM channel mapping.
type SessionListQuery struct {
TenantID uint64
UserID string
Keyword string
Source string
AgentID string
Page int
PageSize int
}
// SessionListItem is a session row enriched with its IM origin (when any).
// IM-related fields are populated from the im_channel_sessions table via LEFT JOIN
// and are empty for Web-created sessions.
type SessionListItem struct {
Session
IMPlatform string `json:"im_platform,omitempty" gorm:"column:im_platform"`
IMChatID string `json:"im_chat_id,omitempty" gorm:"column:im_chat_id"`
IMThreadID string `json:"im_thread_id,omitempty" gorm:"column:im_thread_id"`
IMUserID string `json:"im_user_id,omitempty" gorm:"column:im_user_id"`
IMAgentID string `json:"im_agent_id,omitempty" gorm:"column:im_agent_id"`
IMChannelID string `json:"im_channel_id,omitempty" gorm:"column:im_channel_id"`
}
// StringArray represents a list of strings
type StringArray []string
// Value implements the driver.Valuer interface, used to convert StringArray to database value
func (c StringArray) Value() (driver.Value, error) {
return json.Marshal(c)
}
// Scan implements the sql.Scanner interface, used to convert database value to StringArray
func (c *StringArray) Scan(value interface{}) error {
if value == nil {
return nil
}
b, ok := value.([]byte)
if !ok {
return nil
}
return json.Unmarshal(b, c)
}
// Value implements the driver.Valuer interface, used to convert SummaryConfig to database value
func (c *SummaryConfig) Value() (driver.Value, error) {
return json.Marshal(c)
}
// Scan implements the sql.Scanner interface, used to convert database value to SummaryConfig
func (c *SummaryConfig) Scan(value interface{}) error {
if value == nil {
return nil
}
b, ok := value.([]byte)
if !ok {
return nil
}
return json.Unmarshal(b, c)
}
// SessionLastRequestState captures the user-facing input-bar state at the
// time of the most recent QA request on a session. It is purely a UI memory
// aid — none of the fields here drive backend behaviour. They are echoed back
// to the frontend by GetSession so the chat input can restore the same agent,
// model, KB scope, etc. the user had selected last time.
type SessionLastRequestState struct {
AgentID string `json:"agent_id,omitempty"`
AgentEnabled bool `json:"agent_enabled"`
ModelID string `json:"model_id,omitempty"`
KnowledgeBaseIDs []string `json:"knowledge_base_ids,omitempty"`
KnowledgeIDs []string `json:"knowledge_ids,omitempty"`
TagIDs []string `json:"tag_ids,omitempty"`
MCPServiceIDs []string `json:"mcp_service_ids,omitempty"`
SkillNames []string `json:"skill_names,omitempty"`
MentionedItems MentionedItems `json:"mentioned_items,omitempty"`
WebSearchEnabled bool `json:"web_search_enabled"`
}
// Value implements driver.Valuer for SessionLastRequestState (JSONB).
func (s *SessionLastRequestState) Value() (driver.Value, error) {
if s == nil {
return nil, nil
}
return json.Marshal(s)
}
// Scan implements sql.Scanner for SessionLastRequestState (JSONB).
// Tolerates legacy values that may not match the current schema by silently
// ignoring unmarshal errors — the stored row predates this struct.
func (s *SessionLastRequestState) Scan(value interface{}) error {
if value == nil {
return nil
}
var b []byte
switch v := value.(type) {
case []byte:
b = v
case string:
b = []byte(v)
default:
return nil
}
if len(b) == 0 {
return nil
}
if err := json.Unmarshal(b, s); err != nil {
// Tolerate legacy shapes from before this column was repurposed.
return nil
}
return nil
}
// Value implements the driver.Valuer interface, used to convert ContextConfig to database value
func (c *ContextConfig) Value() (driver.Value, error) {
return json.Marshal(c)
}
// Scan implements the sql.Scanner interface, used to convert database value to ContextConfig
func (c *ContextConfig) Scan(value interface{}) error {
if value == nil {
return nil
}
b, ok := value.([]byte)
if !ok {
return nil
}
return json.Unmarshal(b, c)
}