mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
feat: improve user experience
This commit is contained in:
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"nofx/experience"
|
||||
"nofx/mcp"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -79,6 +80,16 @@ func Init() {
|
||||
|
||||
// Initialize experience improvement (installation ID will be set after database init)
|
||||
experience.Init(cfg.ExperienceImprovement, "")
|
||||
|
||||
// Set up AI token usage tracking callback
|
||||
mcp.TokenUsageCallback = func(usage mcp.TokenUsage) {
|
||||
experience.TrackAIUsage(experience.AIUsageEvent{
|
||||
ModelProvider: usage.Provider,
|
||||
ModelName: usage.Model,
|
||||
InputTokens: usage.PromptTokens,
|
||||
OutputTokens: usage.CompletionTokens,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the global configuration
|
||||
|
||||
@@ -71,6 +71,10 @@ To help us improve the product experience, the "Software" sends **anonymous usag
|
||||
- Trade type (open/close position)
|
||||
- Trade amount (USD value)
|
||||
- Trading pair (e.g., BTCUSDT)
|
||||
- AI model usage statistics:
|
||||
- AI provider name (e.g., OpenAI, DeepSeek, Anthropic)
|
||||
- AI model name (e.g., gpt-4o, deepseek-chat)
|
||||
- Token consumption (input/output tokens per request)
|
||||
- Anonymous identifiers (used for counting active numbers, not linked to personal identity):
|
||||
- Installation ID: Identifies each independently deployed software instance
|
||||
- User ID: Identifies user accounts within the software (only for counting active users)
|
||||
@@ -80,6 +84,7 @@ To help us improve the product experience, the "Software" sends **anonymous usag
|
||||
- Your API keys, private keys, or any credentials
|
||||
- Your account addresses, usernames, or identity information
|
||||
- Specific trade prices, times, or order details
|
||||
- AI conversation content (prompts, responses, or trading decisions)
|
||||
- Any information that could reverse-identify personal identity through the above anonymous IDs
|
||||
|
||||
**How to Disable:**
|
||||
|
||||
@@ -73,6 +73,10 @@ D. 体验改进计划(可选)
|
||||
- 交易类型(开仓/平仓)
|
||||
- 交易金额(USD 数值)
|
||||
- 交易币种(如 BTCUSDT)
|
||||
- AI 模型使用统计:
|
||||
- AI 服务商名称(如 OpenAI、DeepSeek、Anthropic)
|
||||
- AI 模型名称(如 gpt-4o、deepseek-chat)
|
||||
- Token 消耗量(每次请求的输入/输出 token 数)
|
||||
- 匿名标识符(用于统计活跃数量,不关联个人身份):
|
||||
- 安装实例 ID:标识每个独立部署的软件实例
|
||||
- 用户 ID:标识软件内的用户账号(仅用于统计活跃用户数)
|
||||
@@ -82,6 +86,7 @@ D. 体验改进计划(可选)
|
||||
- 您的 API 密钥、私钥或任何凭证
|
||||
- 您的账户地址、用户名或身份信息
|
||||
- 具体的交易价格、时间或订单详情
|
||||
- AI 对话内容(提示词、回复或交易决策)
|
||||
- 任何可通过上述匿名 ID 反向识别个人身份的信息
|
||||
|
||||
**如何关闭:**
|
||||
|
||||
@@ -37,6 +37,15 @@ type TradeEvent struct {
|
||||
TraderID string
|
||||
}
|
||||
|
||||
type AIUsageEvent struct {
|
||||
UserID string
|
||||
TraderID string
|
||||
ModelProvider string // openai, deepseek, anthropic, etc.
|
||||
ModelName string // gpt-4o, deepseek-chat, claude-3, etc.
|
||||
InputTokens int
|
||||
OutputTokens int
|
||||
}
|
||||
|
||||
type telemetryPayload struct {
|
||||
ClientID string `json:"client_id"`
|
||||
Events []telemetryEvent `json:"events"`
|
||||
@@ -186,3 +195,46 @@ func TrackStartup(version string) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func TrackAIUsage(event AIUsageEvent) {
|
||||
if client == nil || !IsEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
client.mu.RLock()
|
||||
installationID := client.installationID
|
||||
client.mu.RUnlock()
|
||||
|
||||
payload := telemetryPayload{
|
||||
ClientID: installationID,
|
||||
Events: []telemetryEvent{
|
||||
{
|
||||
Name: "ai_usage",
|
||||
Params: map[string]interface{}{
|
||||
"model_provider": event.ModelProvider,
|
||||
"model_name": event.ModelName,
|
||||
"input_tokens": event.InputTokens,
|
||||
"output_tokens": event.OutputTokens,
|
||||
"total_tokens": event.InputTokens + event.OutputTokens,
|
||||
"installation_id": installationID,
|
||||
"user_id": event.UserID,
|
||||
"trader_id": event.TraderID,
|
||||
"engagement_time_msec": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, _ := json.Marshal(payload)
|
||||
url := telemetryEndpoint + "?measurement_id=" + tid + "&api_secret=" + tk
|
||||
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if req != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := httpClient.Do(req)
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -99,6 +99,10 @@ func (c *ClaudeClient) parseMCPResponse(body []byte) (string, error) {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
} `json:"content"`
|
||||
Usage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
} `json:"usage"`
|
||||
Error *struct {
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
@@ -117,6 +121,18 @@ func (c *ClaudeClient) parseMCPResponse(body []byte) (string, error) {
|
||||
return "", fmt.Errorf("Claude returned empty content, body: %s", string(body))
|
||||
}
|
||||
|
||||
// Report token usage if callback is set
|
||||
totalTokens := response.Usage.InputTokens + response.Usage.OutputTokens
|
||||
if TokenUsageCallback != nil && totalTokens > 0 {
|
||||
TokenUsageCallback(TokenUsage{
|
||||
Provider: c.Provider,
|
||||
Model: c.Model,
|
||||
PromptTokens: response.Usage.InputTokens,
|
||||
CompletionTokens: response.Usage.OutputTokens,
|
||||
TotalTokens: totalTokens,
|
||||
})
|
||||
}
|
||||
|
||||
// Find text content
|
||||
for _, content := range response.Content {
|
||||
if content.Type == "text" {
|
||||
|
||||
@@ -31,8 +31,20 @@ var (
|
||||
"stream error", // HTTP/2 stream error
|
||||
"INTERNAL_ERROR", // Server internal error
|
||||
}
|
||||
|
||||
// TokenUsageCallback is called after each AI request with token usage info
|
||||
TokenUsageCallback func(usage TokenUsage)
|
||||
)
|
||||
|
||||
// TokenUsage represents token usage from AI API response
|
||||
type TokenUsage struct {
|
||||
Provider string
|
||||
Model string
|
||||
PromptTokens int
|
||||
CompletionTokens int
|
||||
TotalTokens int
|
||||
}
|
||||
|
||||
// Client AI API configuration
|
||||
type Client struct {
|
||||
Provider string
|
||||
@@ -226,6 +238,11 @@ func (client *Client) parseMCPResponse(body []byte) (string, error) {
|
||||
Content string `json:"content"`
|
||||
} `json:"message"`
|
||||
} `json:"choices"`
|
||||
Usage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
} `json:"usage"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
@@ -236,6 +253,17 @@ func (client *Client) parseMCPResponse(body []byte) (string, error) {
|
||||
return "", fmt.Errorf("API returned empty response")
|
||||
}
|
||||
|
||||
// Report token usage if callback is set
|
||||
if TokenUsageCallback != nil && result.Usage.TotalTokens > 0 {
|
||||
TokenUsageCallback(TokenUsage{
|
||||
Provider: client.Provider,
|
||||
Model: client.Model,
|
||||
PromptTokens: result.Usage.PromptTokens,
|
||||
CompletionTokens: result.Usage.CompletionTokens,
|
||||
TotalTokens: result.Usage.TotalTokens,
|
||||
})
|
||||
}
|
||||
|
||||
return result.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user