Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FAI-13624] Calculate auto-completed % #3

Merged
merged 4 commits into from
Nov 8, 2024
Merged
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "faros-vscode-extension",
"displayName": "Faros AI",
"description": "Faros AI's Visual Studio Code extension",
"version": "0.1.0",
"version": "0.1.1",
"publisher": "farosai",
"icon": "images/logo.png",
"engines": {
Expand Down
4 changes: 3 additions & 1 deletion src/panel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as vscode from "vscode";
import { calculateAutoCompletionStats, getTopRepositories } from "./stats";
import { calculateAutoCompletionStats, calculateRatios, getTopRepositories } from "./stats";

export default class FarosPanel implements vscode.WebviewViewProvider {
public static readonly viewType = "farosPanel";
Expand All @@ -11,11 +11,13 @@ export default class FarosPanel implements vscode.WebviewViewProvider {

public refresh() {
const stats = calculateAutoCompletionStats();
const ratios = calculateRatios();
const topRepositories = getTopRepositories(5);

this.webview?.postMessage({
command: "refresh",
stats,
ratios,
topRepositories,
});
}
Expand Down
12 changes: 12 additions & 0 deletions src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AutoCompletionEvent, DocumentChangeEvent, HandWrittenEvent, HourlyAggre

const AUTOCOMPLETION_EVENTS_KEY = 'autocompletionEvents';
const HOURLY_AGGREGATE_PREFIX = 'aggregate: ';
const TOTAL_AGGREGATE_KEY = 'total';

let context: vscode.ExtensionContext;

Expand All @@ -22,6 +23,14 @@ export const getHourlyAggregate = (hour: number): HourlyAggregate => {
return context.globalState.get(hourToKey(hour)) as HourlyAggregate;
};

export const setTotalAggregate = (aggregate: Summarization) => {
context.globalState.update(TOTAL_AGGREGATE_KEY, aggregate);
};

export const getTotalAggregate = (): Summarization => {
return context.globalState.get(TOTAL_AGGREGATE_KEY) as Summarization;
};

const dateToHour = (date: Date) => new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours()).getTime();

export const getHourlyAggregateForRange = (startDate: Date, endDate: Date): Array<HourlyAggregate> => {
Expand Down Expand Up @@ -105,6 +114,9 @@ const addDocumentChangeEvent = (event: DocumentChangeEvent) => {
}

setHourlyAggregate(hour, aggregate);

const totalAggregate = getTotalAggregate();
setTotalAggregate(updateSummarization(totalAggregate, change));
};

export const clearAutoCompletionEventQueue = () => {
Expand Down
3 changes: 3 additions & 0 deletions src/stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const now = new Date(2024, 9, 17);
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const startOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const startOfYear = new Date(now.getFullYear(), 1, 1);

suite('Stats Test Suite', () => {
setup(async () => {
Expand All @@ -27,6 +28,7 @@ suite('Stats Test Suite', () => {
{ timestamp: startOfWeek, autoCompletionCharCountChange: 30*CHARS_PER_MINUTE, filename: 'file2.js', extension: '.js', language: 'JavaScript', repository: 'repo2', branch: 'main' },
{ timestamp: startOfWeek, autoCompletionCharCountChange: 10*CHARS_PER_MINUTE, filename: 'file4.js', extension: '.ts', language: 'JavaScript', repository: 'repo2', branch: 'main' },
{ timestamp: startOfMonth, autoCompletionCharCountChange: 10*CHARS_PER_MINUTE, filename: 'file3.js', extension: '.py', language: 'JavaScript', repository: 'repo2', branch: 'main' },
{ timestamp: startOfYear, autoCompletionCharCountChange: 10*CHARS_PER_MINUTE, filename: 'file3.js', extension: '.py', language: 'JavaScript', repository: 'repo2', branch: 'main' },
];

events.forEach(addAutoCompletionEvent);
Expand All @@ -37,6 +39,7 @@ suite('Stats Test Suite', () => {
today: { count: 4, timeSaved: 55 },
thisWeek: { count: 6, timeSaved: 95 },
thisMonth: { count: 7, timeSaved: 105 },
total: { count: 8, timeSaved: 115 },
});


Expand Down
55 changes: 53 additions & 2 deletions src/stats.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { getHourlyAggregateForRange } from './state';
import { getHourlyAggregateForRange, getTotalAggregate } from './state';
import { HourlyAggregate } from './types';

export const CHARS_PER_MINUTE = 300;

export const calculateAutoCompletionStats = (now: Date = new Date()): {
total: { count: number, timeSaved: number },
today: { count: number, timeSaved: number },
thisWeek: { count: number, timeSaved: number },
thisMonth: { count: number, timeSaved: number }
Expand All @@ -29,7 +30,57 @@ export const calculateAutoCompletionStats = (now: Date = new Date()): {
const thisWeek = calculateStats(weekHistory);
const thisMonth = calculateStats(monthHistory);

return { today, thisWeek, thisMonth };
const calculateTotal = () => {
const totalAggregate = getTotalAggregate();
return {
count: totalAggregate.autoCompletionEventCount,
timeSaved: totalAggregate.autoCompletionCharCount / CHARS_PER_MINUTE
};
};
const total = calculateTotal();

return { total, today, thisWeek, thisMonth };
};

export const calculateRatios = (now: Date = new Date()): {
total: number,
today: number,
thisWeek: number,
thisMonth: number
} => {
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const startOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay());
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);

const todayHistory = getHourlyAggregateForRange(startOfDay, now);
const weekHistory = getHourlyAggregateForRange(startOfWeek, now);
const monthHistory = getHourlyAggregateForRange(startOfMonth, now);

const calculateRatios = (history: Array<HourlyAggregate>) => {
return history.reduce((acc, aggregate) => {
return {
autoCompletedChars: acc.autoCompletedChars + aggregate.totals.autoCompletionCharCount,
handWrittenChars: acc.handWrittenChars + aggregate.totals.handWrittenCharCount
};
}, { autoCompletedChars: 0, handWrittenChars: 0 });
};

const today = calculateRatios(todayHistory);
const thisWeek = calculateRatios(weekHistory);
const thisMonth = calculateRatios(monthHistory);

const calculateTotal = () => {
const totalAggregate = getTotalAggregate();
return totalAggregate.autoCompletionCharCount / (totalAggregate.autoCompletionCharCount + totalAggregate.handWrittenCharCount);
};
const total = calculateTotal();

return {
total,
today: today.autoCompletedChars / (today.autoCompletedChars + today.handWrittenChars),
thisWeek: thisWeek.autoCompletedChars / (thisWeek.autoCompletedChars + thisWeek.handWrittenChars),
thisMonth: thisMonth.autoCompletedChars / (thisMonth.autoCompletedChars + thisMonth.handWrittenChars)
};
};

export const getTopRepositories = (limit: number = 5, now: Date = new Date()): { repository: string; count: number }[] => {
Expand Down
73 changes: 56 additions & 17 deletions src/webview/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,76 +6,115 @@ import {
eventsCountIcon,
timeSavedIcon,
repositoryIcon,
percentageIcon,
chevronIcon,
} from "./Icons";

import { gridItemStyle, gridStyle, panelStyle, subtitleStyle, titleStyle } from "./Styles";
import {detailsCollapseButtonStyle, detailsGridStyle, gridItemStyle, overviewGridStyle, panelStyle, subtitleStyle, titleStyle } from "./Styles";

const App = () => {
const [stats, setStats] = React.useState<{
total: { count: number; timeSaved: number };
today: { count: number; timeSaved: number };
thisWeek: { count: number; timeSaved: number };
thisMonth: { count: number; timeSaved: number };
}>({
total: { count: 0, timeSaved: 0 },
today: { count: 0, timeSaved: 0 },
thisWeek: { count: 0, timeSaved: 0 },
thisMonth: { count: 0, timeSaved: 0 },
});
const [ratios, setRatios] = React.useState<{
today: number;
thisWeek: number;
thisMonth: number;
total: number;
}>({
total: 0,
today: 0,
thisWeek: 0,
thisMonth: 0,
});
const [topRepositories, setTopRepositories] = React.useState<
{
repository: string;
count: number;
}[]
>([]);
const [showDetailedBreakdown, setShowDetailedBreakdown] = React.useState(false);

React.useEffect(() => {
window.addEventListener("message", (event) => {
const message = event.data; // The json data that the extension sent
switch (message.command) {
case "startup":
setStats(message.stats);
setRatios(message.ratios);
setTopRepositories(message.topRepositories);
break;
case "refresh":
setStats(message.stats);
setRatios(message.ratios);
setTopRepositories(message.topRepositories);
break;
}
});
});

const formatTimeSaved = (timeSaved: number) => timeSaved >= 60 ? (timeSaved / 60).toFixed(2) + " hours" : timeSaved.toFixed(2) + " min";
const formatTimeSaved = (timeSaved: number) => {
const hours = Math.floor(timeSaved / 60);
const minutes = Math.round(timeSaved % 60);
if (hours === 0) {
return `${minutes}m`;
}
return `${hours}h ${minutes}m`;
};
const formatPercentage = (percentage: number) => percentage ? (percentage * 100).toFixed(0) + "%" : "N/A";

return (
<>
<div style={panelStyle}>
<div style={titleStyle}>My Stats</div>
<div style={subtitleStyle}>Time saved from auto-completion</div>
<div style={gridStyle("auto auto auto")}>
<div style={{...gridItemStyle, marginRight: "20px"}}>{calendarDayIcon} Today</div>
<div style={{...gridItemStyle, marginRight: "20px"}}>{eventsCountIcon}{stats.today.count}</div>
<div style={gridItemStyle}>{timeSavedIcon} {formatTimeSaved(stats.today.timeSaved)}</div>
<div style={{...gridItemStyle, marginRight: "20px"}}>{calendarWeekIcon} This week</div>
<div style={{...gridItemStyle, marginRight: "20px"}}>{eventsCountIcon}{stats.thisWeek.count}</div>
<div style={gridItemStyle}>{timeSavedIcon} {formatTimeSaved(stats.thisWeek.timeSaved)}</div>
<div style={{...gridItemStyle, marginRight: "20px"}}>{calendarMonthIcon} This month</div>
<div style={{...gridItemStyle, marginRight: "20px"}}>{eventsCountIcon}{stats.thisMonth.count}</div>
<div style={gridItemStyle}>{timeSavedIcon} {formatTimeSaved(stats.thisMonth.timeSaved)}</div>
<div style={subtitleStyle}>Overview of my auto-completion usage</div>
<div style={overviewGridStyle()}>
<div style={gridItemStyle()}>{eventsCountIcon}Total Auto-completions</div><div style={gridItemStyle({justifyContent: "flex-end"})}>{stats.total.count}</div>
<div style={gridItemStyle()}>{timeSavedIcon}Time saved</div><div style={gridItemStyle({justifyContent: "flex-end"})}>{formatTimeSaved(stats.total.timeSaved)}</div>
<div style={gridItemStyle()}>{percentageIcon}Auto-completed ratio</div><div style={gridItemStyle({justifyContent: "flex-end"})}>{formatPercentage(ratios.total)}</div>
</div>
<div style={{display: "flex", alignItems: "center", gap: "4px"}}>
{chevronIcon(showDetailedBreakdown)}
<a style={detailsCollapseButtonStyle} onClick={() => setShowDetailedBreakdown(!showDetailedBreakdown)}>Detailed Breakdown</a>
</div>
{showDetailedBreakdown && (
<div style={detailsGridStyle()}>
<div style={gridItemStyle({marginRight: "16px"})}>{calendarDayIcon}1d</div>
<div style={gridItemStyle({marginRight: "16px"})}>{eventsCountIcon}{stats.today.count}</div>
<div style={gridItemStyle({marginRight: "16px"})}>{timeSavedIcon}{formatTimeSaved(stats.today.timeSaved)}</div>
<div style={gridItemStyle()}>{percentageIcon}{formatPercentage(ratios.today)}</div>
<div style={gridItemStyle({marginRight: "16px"})}>{calendarWeekIcon}1w</div>
<div style={gridItemStyle({marginRight: "16px"})}>{eventsCountIcon}{stats.thisWeek.count}</div>
<div style={gridItemStyle({marginRight: "16px"})}>{timeSavedIcon}{formatTimeSaved(stats.thisWeek.timeSaved)}</div>
<div style={gridItemStyle()}>{percentageIcon}{formatPercentage(ratios.thisWeek)}</div>
<div style={gridItemStyle({marginRight: "16px"})}>{calendarMonthIcon}1m</div>
<div style={gridItemStyle({marginRight: "16px"})}>{eventsCountIcon}{stats.thisMonth.count}</div>
<div style={gridItemStyle({marginRight: "16px"})}>{timeSavedIcon}{formatTimeSaved(stats.thisMonth.timeSaved)}</div>
<div style={gridItemStyle()}>{percentageIcon}{formatPercentage(ratios.thisMonth)}</div>
</div>
)}
</div>


<div style={panelStyle}>
<div style={titleStyle}>Top Repositories</div>
<div style={subtitleStyle}>Repositories with the highest auto-completion</div>
{topRepositories.length > 0 ? (
<div style={gridStyle("auto auto")}>
<div style={overviewGridStyle()}>
{topRepositories.map((repo) => (
<>
<div style={{...gridItemStyle, marginRight: "70px"}}>
<div style={gridItemStyle()}>
{repositoryIcon(topRepositories.indexOf(repo))}
{repo.repository}
</div>
<div style={{...gridItemStyle, justifyContent: "flex-end"}}>{repo.count}</div>
<div style={gridItemStyle({justifyContent: "flex-end"})}>{repo.count}</div>
</>
))}
</div>
Expand Down
55 changes: 23 additions & 32 deletions src/webview/components/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,46 +52,27 @@ export const calendarMonthIcon = (
);

export const eventsCountIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="15"
viewBox="0 0 14 15"
fill="none"
>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="15" viewBox="0 0 14 15" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.7551 7.69021L0.6825 8.7692L0 8.09545L1.82875 6.28419L2.49375 6.29295L4.3225 8.14795L3.6575 8.81295L2.63679 7.78037C2.80245 10.0455 4.69263 11.8317 7 11.8317C8.6718 11.8317 10.1246 10.894 10.8611 9.51582L11.5727 10.0378C10.6714 11.6312 8.96128 12.7067 7 12.7067C4.17876 12.7067 1.87728 10.4813 1.7551 7.69021ZM11.3716 7.28211L10.29 6.18796L9.625 6.85296L11.4537 8.69921L12.1187 8.70796L13.9475 6.89671L13.2912 6.23171L12.2465 7.2642C12.1453 4.45387 9.83506 2.20667 7 2.20667C5.10761 2.20667 3.44907 3.2079 2.52523 4.70954L3.23368 5.22923C3.99566 3.94359 5.39717 3.08167 7 3.08167C9.35779 3.08167 11.28 4.94679 11.3716 7.28211Z" fill="#519ABA"/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1.7551 7.60366L0.6825 8.68265L0 8.0089L1.82875 6.19765L2.49375 6.2064L4.3225 8.0614L3.6575 8.7264L2.63679 7.69382C2.80245 9.95891 4.69263 11.7451 7 11.7451C8.6718 11.7451 10.1246 10.8074 10.8611 9.42927L11.5727 9.95128C10.6714 11.5447 8.96128 12.6201 7 12.6201C4.17876 12.6201 1.87728 10.3948 1.7551 7.60366ZM11.3716 7.19557L10.29 6.10141L9.625 6.76641L11.4537 8.61266L12.1187 8.62141L13.9475 6.81016L13.2912 6.14516L12.2465 7.17766C12.1453 4.36732 9.83506 2.12012 7 2.12012C5.10761 2.12012 3.44907 3.12136 2.52523 4.62299L3.23368 5.14268C3.99566 3.85704 5.39717 2.99512 7 2.99512C9.35779 2.99512 11.28 4.86024 11.3716 7.19557Z"
fill="#C5C5C5"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M9.3958 5.8326L6.61127 9.12012L6.35132 9.10835L5.25 7.5402L5.51903 7.35126L6.498 8.74519L9.14494 5.62012L9.3958 5.8326Z"
fill="#C5C5C5"
d="M9.3958 5.91914L6.61127 9.20666L6.35132 9.1949L5.25 7.62675L5.51903 7.43781L6.498 8.83174L9.14494 5.70667L9.3958 5.91914Z"
fill="#519ABA"
/>
</svg>
);

export const timeSavedIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="15"
viewBox="0 0 14 15"
fill="none"
>
<path
d="M6.5625 8.33179H8.3125V7.45679H7V5.26929H6.125V7.89429L6.5625 8.33179Z"
fill="#C5C5C5"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.8125 3.66711C3.50481 4.42355 2.625 5.83742 2.625 7.45679C2.625 9.07616 3.50481 10.49 4.8125 11.2465V13.1443L5.25 13.5818H8.75L9.1875 13.1443V11.2465C10.4952 10.49 11.375 9.07616 11.375 7.45679C11.375 5.83742 10.4952 4.42355 9.1875 3.66711V1.76929L8.75 1.33179H5.25L4.8125 1.76929V3.66711ZM10.5 7.45679C10.5 9.38976 8.93302 10.9568 7 10.9568C5.06698 10.9568 3.5 9.38976 3.5 7.45679C3.5 5.52382 5.06698 3.95679 7 3.95679C8.93302 3.95679 10.5 5.52382 10.5 7.45679Z"
fill="#C5C5C5"
/>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="15" viewBox="0 0 14 15" fill="none">
<path d="M6.5625 8.33167H8.3125V7.45667H7V5.26917H6.125V7.89417L6.5625 8.33167Z" fill="#8DC149"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.8125 3.66699C3.50481 4.42343 2.625 5.8373 2.625 7.45667C2.625 9.07603 3.50481 10.4899 4.8125 11.2463V13.1442L5.25 13.5817H8.75L9.1875 13.1442V11.2463C10.4952 10.4899 11.375 9.07603 11.375 7.45667C11.375 5.8373 10.4952 4.42343 9.1875 3.66699V1.76917L8.75 1.33167H5.25L4.8125 1.76917V3.66699ZM10.5 7.45667C10.5 9.38963 8.93302 10.9567 7 10.9567C5.06698 10.9567 3.5 9.38963 3.5 7.45667C3.5 5.5237 5.06698 3.95667 7 3.95667C8.93302 3.95667 10.5 5.5237 10.5 7.45667Z" fill="#8DC149"/>
</svg>
);

export const percentageIcon = (
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="11" viewBox="0 0 10 11" fill="none">
< path d="M4.44444 2.67889C4.44444 3.90619 3.44952 4.90111 2.22222 4.90111C0.994923 4.90111 0 3.90619 0 2.67889C0 1.45159 0.994923 0.456665 2.22222 0.456665C3.44952 0.456665 4.44444 1.45159 4.44444 2.67889ZM3.33333 2.67889C3.33333 2.06524 2.83587 1.56778 2.22222 1.56778C1.60857 1.56778 1.11111 2.06524 1.11111 2.67889C1.11111 3.29254 1.60857 3.79 2.22222 3.79C2.83587 3.79 3.33333 3.29254 3.33333 2.67889ZM10 8.23444C10 9.46174 9.00508 10.4567 7.77778 10.4567C6.55048 10.4567 5.55556 9.46174 5.55556 8.23444C5.55556 7.00714 6.55048 6.01222 7.77778 6.01222C9.00508 6.01222 10 7.00714 10 8.23444ZM8.88889 8.23444C8.88889 7.62079 8.39143 7.12333 7.77778 7.12333C7.16413 7.12333 6.66667 7.62079 6.66667 8.23444C6.66667 8.84809 7.16413 9.34555 7.77778 9.34555C8.39143 9.34555 8.88889 8.84809 8.88889 8.23444ZM8.72617 1.73049C8.94313 1.94745 8.94313 2.29921 8.72617 2.51617L2.0595 9.18284C1.84255 9.3998 1.49079 9.3998 1.27383 9.18284C1.05687 8.96588 1.05687 8.61412 1.27383 8.39716L7.9405 1.73049C8.15745 1.51354 8.50921 1.51354 8.72617 1.73049Z" fill="#E37933"/>
</svg>
);

Expand All @@ -111,3 +92,13 @@ export const repositoryIcon = (fillIndex: number) => (
/>
</svg>
);

export const chevronIcon = (collapsed: boolean) => (collapsed ? (
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708"/>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708"/>
</svg>
));
Loading