Skip to content

Commit 6d8befb

Browse files
committed
telemetry panel for waveai
1 parent e069b81 commit 6d8befb

9 files changed

Lines changed: 196 additions & 39 deletions

File tree

cmd/testai/main-testai.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import (
2323
//go:embed testschema.json
2424
var testSchemaJSON string
2525

26+
const (
27+
DefaultAnthropicModel = "claude-sonnet-4-5"
28+
DefaultOpenAIModel = "gpt-5"
29+
)
30+
2631
// TestResponseWriter implements http.ResponseWriter and additional interfaces for testing
2732
type TestResponseWriter struct {
2833
header http.Header
@@ -203,13 +208,13 @@ func testAnthropic(ctx context.Context, model, message string, tools []uctypes.T
203208
func testT1(ctx context.Context) {
204209
tool := aiusechat.GetAdderToolDefinition()
205210
tools := []uctypes.ToolDefinition{tool}
206-
testAnthropic(ctx, "claude-sonnet-4-20250514", "what is 2+2, use the provider adder tool", tools)
211+
testAnthropic(ctx, DefaultAnthropicModel, "what is 2+2, use the provider adder tool", tools)
207212
}
208213

209214
func testT2(ctx context.Context) {
210215
tool := aiusechat.GetAdderToolDefinition()
211216
tools := []uctypes.ToolDefinition{tool}
212-
testOpenAI(ctx, "gpt-5", "what is 2+2+8, use the provider adder tool", tools)
217+
testOpenAI(ctx, DefaultOpenAIModel, "what is 2+2+8, use the provider adder tool", tools)
213218
}
214219

215220
func printUsage() {
@@ -222,8 +227,8 @@ func printUsage() {
222227
fmt.Println(" go run main-testai.go --tools 'Help me configure GitHub Actions monitoring'")
223228
fmt.Println("")
224229
fmt.Println("Default models:")
225-
fmt.Println(" OpenAI: gpt-5")
226-
fmt.Println(" Anthropic: claude-sonnet-4-20250514")
230+
fmt.Printf(" OpenAI: %s\n", DefaultOpenAIModel)
231+
fmt.Printf(" Anthropic: %s\n", DefaultAnthropicModel)
227232
fmt.Println("")
228233
fmt.Println("Environment variables:")
229234
fmt.Println(" OPENAI_APIKEY (for OpenAI models)")
@@ -235,10 +240,10 @@ func main() {
235240
var model string
236241
flag.BoolVar(&anthropic, "anthropic", false, "Use Anthropic API instead of OpenAI")
237242
flag.BoolVar(&tools, "tools", false, "Enable GitHub Actions Monitor tools for testing")
238-
flag.StringVar(&model, "model", "", "AI model to use (defaults: gpt-5 for OpenAI, claude-sonnet-4-20250514 for Anthropic)")
243+
flag.StringVar(&model, "model", "", fmt.Sprintf("AI model to use (defaults: %s for OpenAI, %s for Anthropic)", DefaultOpenAIModel, DefaultAnthropicModel))
239244
flag.BoolVar(&help, "help", false, "Show usage information")
240-
flag.BoolVar(&t1, "t1", false, "Run preset T1 test (claude-sonnet-4-20250514 with 'what is 2+2')")
241-
flag.BoolVar(&t2, "t2", false, "Run preset T2 test (gpt-5 with 'what is 2+2')")
245+
flag.BoolVar(&t1, "t1", false, fmt.Sprintf("Run preset T1 test (%s with 'what is 2+2')", DefaultAnthropicModel))
246+
flag.BoolVar(&t2, "t2", false, fmt.Sprintf("Run preset T2 test (%s with 'what is 2+2')", DefaultOpenAIModel))
242247
flag.Parse()
243248

244249
if help {
@@ -261,9 +266,9 @@ func main() {
261266
// Set default model based on API type if not provided
262267
if model == "" {
263268
if anthropic {
264-
model = "claude-sonnet-4-20250514"
269+
model = DefaultAnthropicModel
265270
} else {
266-
model = "gpt-5"
271+
model = DefaultOpenAIModel
267272
}
268273
}
269274

frontend/app/aipanel/aipanel.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { AIDroppedFiles } from "./aidroppedfiles";
1616
import { AIPanelHeader } from "./aipanelheader";
1717
import { AIPanelInput, type AIPanelInputRef } from "./aipanelinput";
1818
import { AIPanelMessages } from "./aipanelmessages";
19+
import { TelemetryRequiredMessage } from "./telemetryrequired";
1920
import { WaveAIModel, type DroppedFile } from "./waveai-model";
2021

2122
interface AIPanelProps {
@@ -33,6 +34,7 @@ const AIPanelComponent = memo(({ className, onClose }: AIPanelProps) => {
3334
const isLayoutMode = jotai.useAtomValue(atoms.controlShiftDelayAtom);
3435
const showOverlayBlockNums = jotai.useAtomValue(getSettingsKeyAtom("app:showoverlayblocknums")) ?? true;
3536
const isInputFocused = jotai.useAtomValue(atoms.waveAIFocusedAtom);
37+
const telemetryEnabled = jotai.useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false;
3638

3739
const { messages, sendMessage, status, setMessages, error } = useChat({
3840
transport: new DefaultChatTransport({
@@ -283,20 +285,26 @@ const AIPanelComponent = memo(({ className, onClose }: AIPanelProps) => {
283285
<AIPanelHeader onClose={onClose} model={model} />
284286

285287
<div key="main-content" className="flex-1 flex flex-col min-h-0">
286-
<AIPanelMessages messages={messages} status={status} />
287-
{errorMessage && (
288-
<div className="px-4 py-2 text-red-400 bg-red-900/20 border-l-4 border-red-500 mx-2 mb-2">
289-
<div className="text-sm">{errorMessage}</div>
290-
</div>
288+
{!telemetryEnabled ? (
289+
<TelemetryRequiredMessage />
290+
) : (
291+
<>
292+
<AIPanelMessages messages={messages} status={status} />
293+
{errorMessage && (
294+
<div className="px-4 py-2 text-red-400 bg-red-900/20 border-l-4 border-red-500 mx-2 mb-2">
295+
<div className="text-sm">{errorMessage}</div>
296+
</div>
297+
)}
298+
<AIDroppedFiles model={model} />
299+
<AIPanelInput
300+
ref={inputRef}
301+
input={input}
302+
setInput={setInput}
303+
onSubmit={handleSubmit}
304+
status={status}
305+
/>
306+
</>
291307
)}
292-
<AIDroppedFiles model={model} />
293-
<AIPanelInput
294-
ref={inputRef}
295-
input={input}
296-
setInput={setInput}
297-
onSubmit={handleSubmit}
298-
status={status}
299-
/>
300308
</div>
301309
</div>
302310
);
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2025, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { RpcApi } from "@/app/store/wshclientapi";
5+
import { TabRpcClient } from "@/app/store/wshrpcutil";
6+
import { cn } from "@/util/util";
7+
import { useState } from "react";
8+
9+
interface TelemetryRequiredMessageProps {
10+
className?: string;
11+
}
12+
13+
const TelemetryRequiredMessage = ({ className }: TelemetryRequiredMessageProps) => {
14+
const [isEnabling, setIsEnabling] = useState(false);
15+
16+
const handleEnableTelemetry = async () => {
17+
setIsEnabling(true);
18+
try {
19+
await RpcApi.WaveAIEnableTelemetryCommand(TabRpcClient);
20+
} catch (error) {
21+
console.error("Failed to enable telemetry:", error);
22+
setIsEnabling(false);
23+
}
24+
};
25+
26+
return (
27+
<div className={cn("flex flex-col h-full", className)}>
28+
<div className="flex-grow"></div>
29+
<div className="flex items-center justify-center p-8 text-center">
30+
<div className="max-w-md space-y-6">
31+
<div className="space-y-4">
32+
<i className="fa fa-sparkles text-accent text-5xl"></i>
33+
<h2 className="text-2xl font-semibold text-foreground">Wave AI</h2>
34+
<p className="text-secondary leading-relaxed">
35+
Wave AI is free to use and provides integrated AI chat that can interact with your widgets,
36+
help you with code, analyze files, and assist with your terminal workflows.
37+
</p>
38+
</div>
39+
40+
<div className="bg-blue-900/20 border border-blue-500 rounded-lg p-4">
41+
<div className="flex items-start gap-3">
42+
<i className="fa fa-info-circle text-blue-400 text-lg mt-0.5"></i>
43+
<div className="text-left">
44+
<div className="text-blue-400 font-medium mb-1">Telemetry keeps Wave AI free</div>
45+
<div className="text-secondary text-sm mb-3">
46+
<p className="mb-2">
47+
To keep Wave AI free for everyone, we require a small amount of <i>anonymous</i>{" "}
48+
usage data (app version, feature usage, system info).
49+
</p>
50+
<p className="mb-2">
51+
This helps us block abuse by automated systems and ensure it's used by real
52+
people like you.
53+
</p>
54+
<p>
55+
We never collect your files, prompts, keystrokes, hostnames, or personally
56+
identifying information.
57+
</p>
58+
</div>
59+
<button
60+
onClick={handleEnableTelemetry}
61+
disabled={isEnabling}
62+
className="bg-accent/80 hover:bg-accent disabled:bg-accent/50 text-background px-4 py-2 rounded-lg font-medium cursor-pointer disabled:cursor-not-allowed"
63+
>
64+
{isEnabling ? "Enabling..." : "Enable Telemetry and Continue"}
65+
</button>
66+
</div>
67+
</div>
68+
</div>
69+
70+
<div className="text-xs text-secondary">
71+
<a
72+
href="https://waveterm.dev/privacy"
73+
target="_blank"
74+
rel="noopener noreferrer"
75+
className="!text-secondary hover:!text-accent/80 cursor-pointer"
76+
>
77+
Privacy Policy
78+
</a>
79+
</div>
80+
</div>
81+
</div>
82+
<div className="flex-grow-[2]"></div>
83+
</div>
84+
);
85+
};
86+
87+
TelemetryRequiredMessage.displayName = "TelemetryRequiredMessage";
88+
89+
export { TelemetryRequiredMessage };

frontend/app/store/wshclientapi.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,11 @@ class RpcApiType {
462462
return client.wshRpcCall("waitforroute", data, opts);
463463
}
464464

465+
// command "waveaienabletelemetry" [call]
466+
WaveAIEnableTelemetryCommand(client: WshClient, opts?: RpcOpts): Promise<void> {
467+
return client.wshRpcCall("waveaienabletelemetry", null, opts);
468+
}
469+
465470
// command "waveinfo" [call]
466471
WaveInfoCommand(client: WshClient, opts?: RpcOpts): Promise<WaveInfoData> {
467472
return client.wshRpcCall("waveinfo", null, opts);

pkg/aiusechat/usechat.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const (
2828
)
2929

3030
const DefaultAPI = APIType_OpenAI
31-
const DefaultAnthropicModel = "claude-sonnet-4-20250514"
31+
const DefaultAnthropicModel = "claude-sonnet-4-5"
3232
const DefaultAIEndpoint = "https://cfapi.waveterm.dev/api/waveai"
3333
const DefaultMaxTokens = 4 * 1024
3434
const DefaultOpenAIModel = "gpt-5-mini"

pkg/telemetry/telemetrydata/telemetrydata.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,21 @@ import (
1414
)
1515

1616
var ValidEventNames = map[string]bool{
17-
"app:startup": true,
18-
"app:shutdown": true,
19-
"app:activity": true,
20-
"app:display": true,
21-
"app:counts": true,
22-
"action:magnify": true,
23-
"action:settabtheme": true,
24-
"action:runaicmd": true,
25-
"action:createtab": true,
26-
"action:createblock": true,
27-
"wsh:run": true,
28-
"debug:panic": true,
29-
"conn:connect": true,
30-
"conn:connecterror": true,
17+
"app:startup": true,
18+
"app:shutdown": true,
19+
"app:activity": true,
20+
"app:display": true,
21+
"app:counts": true,
22+
"action:magnify": true,
23+
"action:settabtheme": true,
24+
"action:runaicmd": true,
25+
"action:createtab": true,
26+
"action:createblock": true,
27+
"wsh:run": true,
28+
"debug:panic": true,
29+
"conn:connect": true,
30+
"conn:connecterror": true,
31+
"waveai:enabletelemetry": true,
3132
}
3233

3334
type TEvent struct {

pkg/wshrpc/wshclient/wshclient.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,12 @@ func WaitForRouteCommand(w *wshutil.WshRpc, data wshrpc.CommandWaitForRouteData,
550550
return resp, err
551551
}
552552

553+
// command "waveaienabletelemetry", wshserver.WaveAIEnableTelemetryCommand
554+
func WaveAIEnableTelemetryCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) error {
555+
_, err := sendRpcRequestCallHelper[any](w, "waveaienabletelemetry", nil, opts)
556+
return err
557+
}
558+
553559
// command "waveinfo", wshserver.WaveInfoCommand
554560
func WaveInfoCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) (*wshrpc.WaveInfoData, error) {
555561
resp, err := sendRpcRequestCallHelper[*wshrpc.WaveInfoData](w, "waveinfo", nil, opts)

pkg/wshrpc/wshrpctypes.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ const (
136136
Command_VDomRender = "vdomrender"
137137
Command_VDomUrlRequest = "vdomurlrequest"
138138

139-
Command_AiSendMessage = "aisendmessage"
139+
Command_AiSendMessage = "aisendmessage"
140+
Command_WaveAIEnableTelemetry = "waveaienabletelemetry"
140141

141142
Command_GetRTInfo = "getrtinfo"
142143
Command_SetRTInfo = "setrtinfo"
@@ -258,6 +259,7 @@ type WshRpcInterface interface {
258259

259260
// ai
260261
AiSendMessageCommand(ctx context.Context, data AiMessageData) error
262+
WaveAIEnableTelemetryCommand(ctx context.Context) error
261263

262264
// rtinfo
263265
GetRTInfoCommand(ctx context.Context, data CommandGetRTInfoData) (*waveobj.ObjRTInfo, error)

pkg/wshrpc/wshserver/wshserver.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ func (ws *WshServer) SetRTInfoCommand(ctx context.Context, data wshrpc.CommandSe
168168
return nil
169169
}
170170

171-
172171
func (ws *WshServer) ResolveIdsCommand(ctx context.Context, data wshrpc.CommandResolveIdsData) (wshrpc.CommandResolveIdsRtnData, error) {
173172
rtn := wshrpc.CommandResolveIdsRtnData{}
174173
rtn.ResolvedIds = make(map[string]waveobj.ORef)
@@ -829,6 +828,48 @@ func (ws WshServer) SendTelemetryCommand(ctx context.Context) error {
829828
return wcloud.SendAllTelemetry(ctx, client.OID)
830829
}
831830

831+
func (ws *WshServer) WaveAIEnableTelemetryCommand(ctx context.Context) error {
832+
// Enable telemetry in config
833+
meta := waveobj.MetaMapType{
834+
wconfig.ConfigKey_TelemetryEnabled: true,
835+
}
836+
err := wconfig.SetBaseConfigValue(meta)
837+
if err != nil {
838+
return fmt.Errorf("error setting telemetry enabled: %w", err)
839+
}
840+
841+
// Get client for telemetry operations
842+
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
843+
if err != nil {
844+
return fmt.Errorf("getting client data for telemetry: %v", err)
845+
}
846+
847+
// Send no-telemetry update to cloud (async)
848+
go func() {
849+
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
850+
defer cancelFn()
851+
err := wcloud.SendNoTelemetryUpdate(ctx, client.OID, false) // false means telemetry is enabled
852+
if err != nil {
853+
log.Printf("error sending no-telemetry update: %v", err)
854+
}
855+
}()
856+
857+
// Record the telemetry event
858+
event := telemetrydata.MakeTEvent("waveai:enabletelemetry", telemetrydata.TEventProps{})
859+
err = telemetry.RecordTEvent(ctx, event)
860+
if err != nil {
861+
log.Printf("error recording waveai:enabletelemetry event: %v", err)
862+
}
863+
864+
// Immediately send telemetry to cloud
865+
err = wcloud.SendAllTelemetry(ctx, client.OID)
866+
if err != nil {
867+
log.Printf("error sending telemetry after enabling: %v", err)
868+
}
869+
870+
return nil
871+
}
872+
832873
var wshActivityRe = regexp.MustCompile(`^[a-z:#]+$`)
833874

834875
func (ws *WshServer) WshActivityCommand(ctx context.Context, data map[string]int) error {

0 commit comments

Comments
 (0)