-
Notifications
You must be signed in to change notification settings - Fork 504
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
985ec06
commit 8285f08
Showing
7 changed files
with
967 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
|
||
|
||
```bash | ||
pnpm i | ||
export OPENAI_API_KEY="<your key>" | ||
npx tsx screenpipe.ts | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"appUsage": { | ||
"window server": 0.03777777777777778, | ||
"arc": 0.7944444444444444, | ||
"obsidian": 0.04055555555555555, | ||
"control centre": 0.017222222222222222, | ||
"cursor": 0.5783333333333334, | ||
"dock": 0.010555555555555556, | ||
"emoji & symbols": 0.0016666666666666668, | ||
"screenshot": 0.0022222222222222222, | ||
"screencapture": 0.0011111111111111111 | ||
}, | ||
"activities": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
import { z } from "zod"; | ||
import { openai } from "@ai-sdk/openai"; | ||
import { generateObject, generateText, streamText } from "ai"; | ||
import { queryScreenpipe, ContentType } from "./screenpipe"; | ||
import * as fs from "fs/promises"; | ||
console.log("Starting daily tracker application"); | ||
|
||
const CHUNK_SIZE = 5 * 60; // 5 minutes in seconds | ||
console.log(`CHUNK_SIZE set to ${CHUNK_SIZE} seconds`); | ||
|
||
const ActivitySchema = z.object({ | ||
category: z.string(), | ||
description: z.string(), | ||
duration: z.number(), | ||
}); | ||
|
||
type Activity = z.infer<typeof ActivitySchema>; | ||
|
||
interface Usage { | ||
[key: string]: number; | ||
} | ||
|
||
interface AnalysisResult { | ||
appUsage: Usage; | ||
activities: Activity[]; | ||
} | ||
|
||
const analyzeTimeRangeSchema = z.object({ | ||
startTime: z.string(), | ||
endTime: z.string(), | ||
}); | ||
|
||
const generateSummarySchema = z.object({ | ||
analysisResult: z.any(), | ||
}); | ||
|
||
const streamResponseSchema = z.object({ | ||
response: z.string(), | ||
}); | ||
|
||
async function timeTracker(userInput: string) { | ||
console.log(`Starting timeTracker with user input: "${userInput}"`); | ||
try { | ||
const text = await generateText({ | ||
model: openai("gpt-4o"), | ||
tools: { | ||
analyze_time_range: { | ||
description: | ||
"Analyze app usage and activities within a given time range", | ||
parameters: analyzeTimeRangeSchema, | ||
execute: async ({ startTime, endTime }) => { | ||
console.log(`Analyzing time range: ${startTime} to ${endTime}`); | ||
let appUsage: Usage = {}; | ||
let activities: Activity[] = []; | ||
|
||
// Use the current local date | ||
const now = new Date(); | ||
const startOfDay = new Date(now.setHours(0, 0, 0, 0)); | ||
const endOfDay = new Date(now.setHours(23, 59, 59, 999)); | ||
|
||
let currentTime = startOfDay; | ||
const endTimeDate = endOfDay; | ||
|
||
while (currentTime < endTimeDate) { | ||
const chunkEnd = new Date( | ||
Math.min( | ||
currentTime.getTime() + CHUNK_SIZE * 1000, | ||
endTimeDate.getTime() | ||
) | ||
); | ||
console.log( | ||
`Querying Screenpipe for chunk: ${currentTime.toISOString()} to ${chunkEnd.toISOString()}` | ||
); | ||
const chunkData = await queryScreenpipe({ | ||
start_time: currentTime.toISOString(), | ||
end_time: chunkEnd.toISOString(), | ||
offset: 0, | ||
limit: 1000, | ||
content_type: "all" as ContentType, | ||
}); | ||
|
||
console.log( | ||
`Received ${chunkData.data.length} entries from Screenpipe` | ||
); | ||
for (const entry of chunkData.data) { | ||
if (entry.type === "OCR") { | ||
const app = entry.content.app_name.toLowerCase(); | ||
appUsage[app] = (appUsage[app] || 0) + 1; | ||
} | ||
} | ||
currentTime = chunkEnd; | ||
} | ||
|
||
appUsage = Object.fromEntries( | ||
Object.entries(appUsage).map(([app, seconds]) => [ | ||
app, | ||
seconds / 3600, | ||
]) | ||
); | ||
|
||
console.log("App usage analysis completed"); | ||
console.log("App usage summary:", appUsage); | ||
|
||
const analysisResult: AnalysisResult = { appUsage, activities }; | ||
await fs.writeFile( | ||
"analysis_result.json", | ||
JSON.stringify(analysisResult, null, 2) | ||
); | ||
console.log("Analysis result saved to analysis_result.json"); | ||
return analysisResult; | ||
}, | ||
}, | ||
generate_summary: { | ||
description: | ||
"Generate a summary of time usage based on the analysis result", | ||
parameters: generateSummarySchema, | ||
execute: async ({ analysisResult }) => { | ||
console.log("Generating summary from analysis result"); | ||
const { appUsage, activities } = analysisResult; | ||
const topApps = Object.entries(appUsage) | ||
.sort((a, b) => b[1] - a[1]) | ||
.slice(0, 5); | ||
|
||
const categorySummary = activities.reduce((acc, activity) => { | ||
acc[activity.category] = | ||
(acc[activity.category] || 0) + activity.duration; | ||
return acc; | ||
}, {} as Usage); | ||
|
||
const topCategories = Object.entries(categorySummary) | ||
.sort((a, b) => b[1] - a[1]) | ||
.slice(0, 5); | ||
|
||
console.log("Top 5 apps:", topApps); | ||
console.log("Top 5 categories:", topCategories); | ||
|
||
const prompt = ` | ||
Based on the following data: | ||
Top 5 apps used (in hours, need to multiply by 10): | ||
${JSON.stringify(topApps)} | ||
Top 5 activity categories (in seconds): | ||
${JSON.stringify(topCategories)} | ||
Provide a summary of how the user spent their time this week. | ||
Focus on answering the question: "What's the thing I spend the most time on this week?" | ||
Include insights about both app usage and activity categories. | ||
Be concise but informative. | ||
`; | ||
|
||
console.log("Sending prompt to GPT-4 for summary generation"); | ||
return generateText({ | ||
model: openai("gpt-4o"), | ||
messages: [{ role: "user", content: prompt }], | ||
}); | ||
}, | ||
}, | ||
stream_response: { | ||
description: "Stream the final response to the user", | ||
parameters: streamResponseSchema, | ||
execute: async ({ response }) => { | ||
console.log("Streaming response to user"); | ||
const { textStream } = await streamText({ | ||
model: openai("gpt-4o"), | ||
messages: [ | ||
{ | ||
role: "user", | ||
content: response, | ||
}, | ||
], | ||
}); | ||
|
||
let buffer = ""; | ||
const flushBuffer = () => { | ||
if (buffer) { | ||
process.stdout.write(buffer); | ||
buffer = ""; | ||
} | ||
}; | ||
|
||
for await (const chunk of textStream) { | ||
buffer += chunk; | ||
if (buffer.includes(" ") && buffer.length > 20) { | ||
flushBuffer(); | ||
} | ||
} | ||
flushBuffer(); // Flush any remaining content | ||
console.log("\nFinished streaming response"); | ||
throw new Error("STREAM_COMPLETE"); | ||
}, | ||
}, | ||
}, | ||
toolChoice: "required", | ||
messages: [ | ||
{ | ||
role: "system", | ||
content: `You are a helpful assistant that analyzes time usage data. | ||
The user wants to know what they spent the most time on this week. | ||
Use the provided tools to analyze the data and generate a summary. | ||
Maintain and update the analysis result in the JSON file throughout the process. | ||
Always use the stream_response tool to output the final result to the user. Stop after streaming your final response. | ||
Rules: | ||
- Do not use Markdown as the answer will be streamed in a terminal`, | ||
}, | ||
{ | ||
role: "user", | ||
content: userInput, | ||
}, | ||
], | ||
maxToolRoundtrips: 5, | ||
}); | ||
|
||
console.log("Final response from GPT-4:", text); | ||
} catch (error) { | ||
if (error instanceof Error && error.message === "STREAM_COMPLETE") { | ||
console.log("Streaming completed, exiting timeTracker"); | ||
return; | ||
} | ||
throw error; // Re-throw other errors | ||
} | ||
} | ||
|
||
const main = async () => { | ||
// Example usage | ||
const startTime = new Date(); | ||
console.log(`Starting example usage at ${startTime.toISOString()}`); | ||
await timeTracker("What did I spend the most time on this week?"); | ||
console.log("Example usage completed"); | ||
const endTime = new Date(); | ||
console.log(`Example usage completed at ${endTime.toISOString()}`); | ||
console.log( | ||
`Example usage took ${endTime.getTime() - startTime.getTime()}ms` | ||
); | ||
}; | ||
|
||
main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"dependencies": { | ||
"@ai-sdk/openai": "^0.0.40", | ||
"ai": "^3.3.0", | ||
"readline": "^1.3.0", | ||
"zod": "^3.23.8" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^22.1.0" | ||
} | ||
} |
Oops, something went wrong.