Skip to content
Open
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
24 changes: 24 additions & 0 deletions i18n/en_US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,13 @@ ui:
show_more: Show more
new: New chat
ai_generate: AI-generated from posts and may not be accurate.
ai_disclaimer: Generated by AI, for reference only. Please verify critical information.
ai_name: AI Assistant
request_failed: Request failed, please try again later.
suggestion_1: What is this website for?
suggestion_2: How do I ask a new question?
suggestion_3: How do I search for content I'm interested in?
suggestion_4: How do I follow or bookmark a question?
copy: Copy
ask_a_follow_up: Ask a follow-up
ask_placeholder: Ask a question
Expand Down Expand Up @@ -2347,6 +2354,23 @@ ui:
label: AI enabled
check: Enable AI features
text: The AI model must be configured correctly before it can be used.
home_chat:
label: Show AI assistant on homepage
tip: Shows a floating AI assistant entry at the bottom-right of the homepage.
home_chat_guest:
label: Allow guests to use the homepage AI assistant
tip: Logged-out visitors can chat with the homepage AI assistant; guest conversations stay in the browser and are never saved.
welcome_text:
label: AI assistant welcome text
tip: The description shown at the start of the AI assistant. Leave empty to use the default.
initial_messages:
label: Suggested questions
tip: One question per line, shown as tappable suggestion chips below the welcome text. Leave empty to use the built-in suggestions.
prompt_config:
label: System prompt
zh_placeholder: Chinese system prompt (leave empty for default)
en_placeholder: English system prompt (leave empty for default)
tip: The system prompt sent to the AI model. Use %s as the user question placeholder; leave empty to use the built-in default.
provider:
label: Provider
api_host:
Expand Down
26 changes: 26 additions & 0 deletions i18n/zh_CN.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -853,9 +853,18 @@ ui:
show_more: 显示更多
new: 新聊天
ai_generate: 来自帖子的 AI,可能不准确。
ai_disclaimer: 内容由 AI 生成,仅供参考,请自行甄别
ai_name: AI 助手
request_failed: 请求失败,请稍后重试
suggestion_1: 这个网站是做什么的?
suggestion_2: 如何提出一个新问题?
suggestion_3: 如何搜索我感兴趣的内容?
suggestion_4: 如何关注或收藏问题?
copy: 复制
ask_a_follow_up: 提出后续问题
ask_placeholder: 提问
thinking: 思考中…
thoughts: 思考过程
notifications:
title: 通知
inbox: 收件箱
Expand Down Expand Up @@ -2305,6 +2314,23 @@ ui:
label: AI 已启用
check: 启用AI功能
text: AI 模型必须正确配置才能使用。
home_chat:
label: 首页展示 AI 助手
tip: 开启后将在网站首页右下角展示 AI 助手悬浮入口。
home_chat_guest:
label: 允许游客使用首页 AI 助手
tip: 开启后未登录访客也可使用首页 AI 助手;游客会话仅保留在浏览器中,不会被保存。
welcome_text:
label: AI 助手欢迎语
tip: 显示在 AI 助手开场的提示语,留空使用默认文案。
initial_messages:
label: 建议问题列表
tip: 每行一条,展示为欢迎语下方的建议问题,点击即可发送;留空使用默认建议问题。
prompt_config:
label: 系统提示词
zh_placeholder: 中文系统提示词(留空使用默认)
en_placeholder: English system prompt (leave empty for default)
tip: 发送给 AI 模型的系统提示词,可用 %s 代表用户问题;留空使用内置默认提示词。
provider:
label: 提供商
api_host:
Expand Down
36 changes: 36 additions & 0 deletions internal/controller/ai_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ func (c *AIController) ChatCompletions(ctx *gin.Context) {
}
req.UserID = middleware.GetLoginUserIDFromContext(ctx)

// The route is registered as unauthenticated so logged-out visitors can
// use the homepage AI chat; guests are only served when the admin enables
// guest access, and their conversations are never persisted.
if req.UserID == "" && !aiConfig.HomeChatGuestEnabled {
handler.HandleResponse(ctx, errors.Unauthorized("guest access to AI chat is not enabled"), nil)
return
}

data, _ := json.Marshal(req)
log.Infof("ai chat request data: %s", string(data))

Expand Down Expand Up @@ -351,6 +359,34 @@ func (c *AIController) initializeConversationContext(ctx *gin.Context, model str
Model: model,
}

// Guest session: storage is never touched. The client sends the whole
// page-side history in the request, and a system prompt is prepended for
// the assistant to work with MCP tools.
if req.UserID == "" {
conversationCtx.IsNewConversation = true
question := ""
for i := len(req.Messages) - 1; i >= 0; i-- {
if req.Messages[i].Role == openai.ChatMessageRoleUser {
question = req.Messages[i].Content
break
}
}
conversationCtx.UserQuestion = question
currentLang := handler.GetLangByCtx(ctx)
prompt := c.getPromptByLanguage(currentLang, question)
conversationCtx.Messages = append(conversationCtx.Messages, &ai_conversation.ConversationMessage{
Role: openai.ChatMessageRoleSystem,
Content: prompt,
})
for _, m := range req.Messages {
conversationCtx.Messages = append(conversationCtx.Messages, &ai_conversation.ConversationMessage{
Role: m.Role,
Content: m.Content,
})
}
return conversationCtx
}

conversationDetail, exist, err := c.aiConversationService.GetConversationDetail(ctx, &schema.AIConversationDetailReq{
ConversationID: req.ConversationID,
UserID: req.UserID,
Expand Down
4 changes: 4 additions & 0 deletions internal/controller/siteinfo_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ func (sc *SiteInfoController) GetSiteInfo(ctx *gin.Context) {
}
if aiConf, err := sc.siteInfoService.GetSiteAI(ctx); err == nil {
resp.AIEnabled = aiConf.Enabled
resp.AIHomeChatEnabled = aiConf.HomeChatEnabled
resp.AIHomeChatGuestEnabled = aiConf.HomeChatGuestEnabled
resp.AIWelcomeText = aiConf.WelcomeText
resp.AIInitialMessages = aiConf.InitialMessages
}

if mcpConf, err := sc.siteInfoService.GetSiteMCP(ctx); err == nil {
Expand Down
10 changes: 7 additions & 3 deletions internal/router/answer_api_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
r.GET("/user/ranking", a.userController.UserRanking)
r.GET("/user/staff", a.userController.UserStaff)

// AI chat (guest-capable: the handler enforces the admin switches; logged
// in users always work, guests only when home chat guest access is on)
r.POST("/chat/completions", a.aiController.ChatCompletions)

// answer
r.GET("/answer/info", a.answerController.GetAnswerInfo)
r.GET("/answer/page", a.answerController.AnswerList)
Expand Down Expand Up @@ -324,10 +328,10 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
// meta
r.PUT("/meta/reaction", a.metaController.AddOrUpdateReaction)

// AI chat
r.POST("/chat/completions", a.aiController.ChatCompletions)

// AI conversation
// NOTE: /chat/completions is registered in RegisterUnAuthAnswerAPIRouter
// so logged-out visitors can use the homepage AI chat when the admin
// enables guest access; the handler enforces that switch itself.
r.GET("/ai/conversation/page", a.aiConversationController.GetConversationList)
r.GET("/ai/conversation", a.aiConversationController.GetConversationDetail)
r.POST("/ai/conversation/vote", a.aiConversationController.VoteRecord)
Expand Down
27 changes: 22 additions & 5 deletions internal/schema/siteinfo_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,23 @@ type AIPromptConfig struct {

// SiteAIReq AI configuration request
type SiteAIReq struct {
Enabled bool `validate:"omitempty" form:"enabled" json:"enabled"`
ChosenProvider string `validate:"omitempty,lte=50" form:"chosen_provider" json:"chosen_provider"`
Enabled bool `validate:"omitempty" form:"enabled" json:"enabled"`
ChosenProvider string `validate:"omitempty,lte=50" form:"chosen_provider" json:"chosen_provider"`
SiteAIProviders []*SiteAIProvider `validate:"omitempty,dive" form:"ai_providers" json:"ai_providers"`
PromptConfig *AIPromptConfig `validate:"omitempty" form:"prompt_config" json:"prompt_config,omitempty"`
PromptConfig *AIPromptConfig `validate:"omitempty" form:"prompt_config" json:"prompt_config,omitempty"`
// HomeChatEnabled shows the AI chat entry on the homepage.
HomeChatEnabled bool `validate:"omitempty" form:"home_chat_enabled" json:"home_chat_enabled"`
// HomeChatGuestEnabled lets logged-out visitors use the homepage AI chat.
// Guest conversations are kept in memory client-side only and are never
// persisted, and voting is disabled for them.
HomeChatGuestEnabled bool `validate:"omitempty" form:"home_chat_guest_enabled" json:"home_chat_guest_enabled"`
// WelcomeText overrides the assistant welcome description shown in a
// fresh conversation. Empty keeps the built-in i18n text.
WelcomeText string `validate:"omitempty,lte=500" form:"ai_welcome_text" json:"ai_welcome_text"`
// InitialMessages holds admin-configured assistant messages (one per
// line, markdown allowed) rendered in a fresh conversation. When set,
// the built-in suggestion chips are hidden.
InitialMessages string `validate:"omitempty,lte=5000" form:"ai_initial_messages" json:"ai_initial_messages"`
}

func (s *SiteAIResp) GetProvider() *SiteAIProvider {
Expand Down Expand Up @@ -385,8 +398,12 @@ type SiteInfoResp struct {
Security *SiteSecurityResp `json:"site_security"`
Version string `json:"version"`
Revision string `json:"revision"`
AIEnabled bool `json:"ai_enabled"`
MCPEnabled bool `json:"mcp_enabled"`
AIEnabled bool `json:"ai_enabled"`
AIHomeChatEnabled bool `json:"ai_home_chat_enabled"`
AIHomeChatGuestEnabled bool `json:"ai_home_chat_guest_enabled"`
AIWelcomeText string `json:"ai_welcome_text"`
AIInitialMessages string `json:"ai_initial_messages"`
MCPEnabled bool `json:"mcp_enabled"`
}

type TemplateSiteInfoResp struct {
Expand Down
3 changes: 1 addition & 2 deletions ui/.npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
strict-peer-dependencies = true
auto-install-peers = true
registry=https://registry.npmmirror.com
3 changes: 3 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@codemirror/language-data": "^6.5.0",
"@codemirror/state": "^6.5.0",
"@codemirror/view": "^6.26.1",
"@tdesign-react/chat": "^1.0.2",
"axios": "^1.7.7",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.10.5",
Expand All @@ -46,6 +47,8 @@
"react-router-dom": "^7.0.2",
"semver": "^7.3.8",
"swr": "^1.3.0",
"tdesign-icons-react": "^0.6.10",
"tdesign-react": "^1.18.2",
"uuid": "13.0.0",
"zustand": "^5.0.2"
},
Expand Down
12 changes: 12 additions & 0 deletions ui/src/common/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,10 @@ export interface SiteSettings {
revision: string;
site_security: AdminSettingsSecurity;
ai_enabled: boolean;
ai_home_chat_enabled?: boolean;
ai_home_chat_guest_enabled?: boolean;
ai_welcome_text?: string;
ai_initial_messages?: string;
}

export interface AdminSettingBranding {
Expand Down Expand Up @@ -828,7 +832,15 @@ export interface AddOrEditApiKeyParams {

export interface AiConfig {
enabled: boolean;
home_chat_enabled?: boolean;
home_chat_guest_enabled?: boolean;
chosen_provider: string;
ai_welcome_text?: string;
ai_initial_messages?: string;
prompt_config?: {
zh_cn?: string;
en_us?: string;
};
ai_providers: Array<{
provider: string;
api_host: string;
Expand Down
Loading