Files
coder/site/src/utils/time.ts
T
edee917d88 feat: add experimental agents support (#22290)
feat: add AI chat system with agent tools and chat UI

Introduce the chatd subsystem and Agents UI for AI-powered chat
within Coder workspaces.

- Add chatd package with chat loop, message compaction, prompt
  management, and LLM provider integration (OpenAI, Anthropic)
- Add agent tools: create workspace, list/read templates, read/write/
  edit files, execute commands
- Add chat API endpoints with streaming, message editing, and
  durable reconnection
- Add database schema and migrations for chats, chat messages, chat
  providers, and chat model configs
- Add RBAC policies and dbauthz enforcement for chat resources
- Add Agents UI pages with conversation timeline, queued messages
  list, diff viewer, and model configuration panel
- Add comprehensive test coverage including coderd integration tests,
  chatd unit tests, and Storybook stories
- Gate feature behind experiments flag

---------

Co-authored-by: Cian Johnston <cian@coder.com>
Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
Co-authored-by: Jeremy Ruppel <jeremy@coder.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 16:50:56 +00:00

183 lines
4.3 KiB
TypeScript

import dayjs, { type Dayjs } from "dayjs";
import duration from "dayjs/plugin/duration";
import relativeTimePlugin from "dayjs/plugin/relativeTime";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import humanizeDuration from "humanize-duration";
// Load required plugins
dayjs.extend(duration);
dayjs.extend(relativeTimePlugin);
dayjs.extend(utc);
dayjs.extend(timezone);
// Time conversion constants
const TIME_CONSTANTS = {
MS_PER_SECOND: 1000,
MS_PER_MINUTE: 60 * 1000,
MS_PER_HOUR: 60 * 60 * 1000,
MS_PER_DAY: 24 * 60 * 60 * 1000,
NS_PER_MS: 1e6,
SECONDS_PER_MINUTE: 60,
MINUTES_PER_HOUR: 60,
HOURS_PER_DAY: 24,
};
export type TimeUnit = "days" | "hours";
type DateTimeInput = Date | string | number | Dayjs | null | undefined;
// Standard format strings
// https://day.js.org/docs/en/display/format
export const DATE_FORMAT = {
ISO_DATE: "YYYY-MM-DD",
ISO_DATETIME: "YYYY-MM-DD HH:mm:ss",
FULL_DATE: "MMMM D, YYYY",
MEDIUM_DATE: "MMM D, YYYY",
FULL_DATETIME: "MMMM D, YYYY h:mm A",
SHORT_DATE: "MM/DD/YYYY",
TIME_24H: "HH:mm:ss",
TIME_12H: "h:mm A",
UTC_OFFSET: "Z",
} as const;
// Format functions
export function formatDateTime(
date: DateTimeInput,
format: string = DATE_FORMAT.ISO_DATETIME,
) {
return dayjs(date).format(format);
}
const defaultDateLocaleOptions: Intl.DateTimeFormatOptions = {
second: "2-digit",
minute: "2-digit",
hour: "2-digit",
day: "numeric",
month: "short",
year: "numeric",
};
export function formatDate(
date: Date,
options?: { locale?: Intl.LocalesArgument } & Intl.DateTimeFormatOptions,
) {
return date.toLocaleDateString(options?.locale, {
...defaultDateLocaleOptions,
...options,
});
}
// Duration functions
export function humanDuration(durationInMs: number) {
return humanizeDuration(durationInMs, {
conjunction: " and ",
serialComma: false,
round: true,
units: ["y", "mo", "w", "d", "h", "m", "s", "ms"],
largest: 3,
});
}
export function durationInHours(durationMs: number): number {
return durationMs / TIME_CONSTANTS.MS_PER_HOUR;
}
export function durationInDays(durationMs: number): number {
return durationMs / TIME_CONSTANTS.MS_PER_DAY;
}
export function suggestedTimeUnit(duration: number): TimeUnit {
if (duration === 0) {
return "hours";
}
return Number.isInteger(durationInDays(duration)) ? "days" : "hours";
}
// Relative time functions
export function relativeTime(date: DateTimeInput) {
return dayjs(date).fromNow();
}
/**
* Returns a compact relative time string like "now", "5m", "2h",
* "3d", "1w", "2mo", or "1y". Useful for tight UI spaces like
* sidebar timestamps.
*/
export function shortRelativeTime(date: DateTimeInput): string {
const now = dayjs();
const then = dayjs(date);
const diffSeconds = now.diff(then, "second");
if (diffSeconds < 60) {
return "now";
}
const diffMinutes = now.diff(then, "minute");
if (diffMinutes < 60) {
return `${diffMinutes}m`;
}
const diffHours = now.diff(then, "hour");
if (diffHours < 24) {
return `${diffHours}h`;
}
const diffDays = now.diff(then, "day");
if (diffDays < 7) {
return `${diffDays}d`;
}
const diffWeeks = now.diff(then, "week");
if (diffWeeks < 5) {
return `${diffWeeks}w`;
}
const diffMonths = now.diff(then, "month");
if (diffMonths < 12) {
return `${diffMonths}mo`;
}
const diffYears = now.diff(then, "year");
return `${diffYears}y`;
}
export function relativeTimeWithoutSuffix(date: DateTimeInput) {
return dayjs(date).fromNow(true);
}
export function timeFrom(
date: DateTimeInput,
referenceDate: DateTimeInput = new Date(),
) {
return dayjs(date).from(dayjs(referenceDate));
}
// Time manipulation functions
export function addTime(
date: DateTimeInput,
amount: number,
unit: dayjs.ManipulateType,
) {
return dayjs(date).add(amount, unit).toDate();
}
export function subtractTime(
date: DateTimeInput,
amount: number,
unit: dayjs.ManipulateType,
) {
return dayjs(date).subtract(amount, unit).toDate();
}
export function startOfDay(date: DateTimeInput) {
return dayjs(date).startOf("day").toDate();
}
export function startOfHour(date: DateTimeInput) {
return dayjs(date).startOf("hour").toDate();
}
export function daysAgo(count: number) {
return dayjs().subtract(count, "day").toISOString();
}
// Date comparison functions
export function isAfter(date1: DateTimeInput, date2: DateTimeInput) {
return dayjs(date1).isAfter(dayjs(date2));
}