Skip to content
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
26 changes: 24 additions & 2 deletions backend/src/features/task-decompose/task-calendar.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
TaskDecomposeRequest,
TaskDecomposeResult,
} from "./task-decompose.types";
import { normalizeTaskTimezone } from "./task-timezone";
import type {
CalendarCreatedEvent,
CalendarSyncResult,
Expand Down Expand Up @@ -51,6 +52,23 @@ function buildEventSummary(
return `[${index + 1}/${totalCount}] ${normalizedSubtask} | ${normalizedOverall}`;
}

function toOverallTaskName(
requestTask: string,
breakdown: TaskDecomposeResult,
): string {
const summary = toSingleLine(breakdown.summary ?? "");
if (summary.length > 0) {
return summary;
}

const goal = toSingleLine(breakdown.goal ?? "");
if (goal.length > 0) {
return goal;
}

return toSingleLine(requestTask);
}

function safeDate(value: string, fallback: Date): Date {
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) {
Expand Down Expand Up @@ -279,8 +297,12 @@ export async function createCalendarEvents(
input: CreateCalendarEventsInput,
): Promise<CalendarSyncResult> {
const accessToken = await getGoogleAccessToken(env, input.userId);
const timezone = input.request.timezone ?? "UTC";
const timezone = normalizeTaskTimezone(input.request.timezone);
const calendarId = PRIMARY_CALENDAR_ID;
const overallTaskName = toOverallTaskName(
input.request.task,
input.breakdown,
);

const createdEvents: CalendarCreatedEvent[] = [];
const totalCount = Math.max(input.breakdown.subtasks.length, 1);
Expand All @@ -296,7 +318,7 @@ export async function createCalendarEvents(
const eventId = await createStableEventId(input.workflowId, index);
const summary = buildEventSummary(
subtask.title,
input.request.task,
overallTaskName,
index,
totalCount,
);
Expand Down
12 changes: 2 additions & 10 deletions backend/src/features/task-decompose/task-decompose.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import type {
TaskDecomposeResult,
TaskSubtask,
} from "./task-decompose.types";
import { normalizeTaskTimezone } from "./task-timezone";

const AI_MODEL = "@cf/meta/llama-3.1-8b-instruct-fp8";
const DEFAULT_MAX_STEPS = 6;
const MIN_DURATION_MINUTES = 15;
const MAX_DURATION_MINUTES = 240;
const DEFAULT_INFERRED_DEADLINE_DAYS = 7;
const DEFAULT_PLANNING_TIMEZONE = "Asia/Tokyo";

function asStringArray(value: unknown): string[] {
if (!Array.isArray(value)) {
Expand Down Expand Up @@ -76,14 +76,6 @@ function fallbackSummary(task: string): string {
return `"${task}" を実行可能な単位へ分解した計画です。`;
}

function normalizeTimezone(timezone: string | undefined): string {
if (!timezone || timezone.trim().length === 0) {
return DEFAULT_PLANNING_TIMEZONE;
}

return timezone.trim();
}

function formatPromptDateInTimezone(date: Date, timezone: string): string {
try {
return new Intl.DateTimeFormat("en-CA", {
Expand Down Expand Up @@ -260,7 +252,7 @@ function normalizeResult(

function createPrompt(payload: TaskDecomposeRequest): string {
const maxSteps = payload.maxSteps ?? DEFAULT_MAX_STEPS;
const planningTimezone = normalizeTimezone(payload.timezone);
const planningTimezone = normalizeTaskTimezone(payload.timezone);
const now = new Date();

const deadlineGuidance = payload.deadline
Expand Down
7 changes: 7 additions & 0 deletions backend/src/features/task-decompose/task-decompose.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ export type TaskDecomposeRequest = {
maxSteps?: number;
};

export type ValidatedTaskDecomposeRequest = Omit<
TaskDecomposeRequest,
"timezone"
> & {
timezone: string;
};

export type TaskSubtask = {
title: string;
description: string;
Expand Down
18 changes: 8 additions & 10 deletions backend/src/features/task-decompose/task-decompose.validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { TaskDecomposeRequest } from "./task-decompose.types";
import type { ValidatedTaskDecomposeRequest } from "./task-decompose.types";
import { normalizeTaskTimezone } from "./task-timezone";

const MAX_STEPS_LIMIT = 12;

Expand All @@ -20,17 +21,12 @@ function parseDeadline(value: unknown): string | undefined {
return parsed.toISOString();
}

function parseTimezone(value: unknown): string | undefined {
function parseTimezone(value: unknown): string {
if (typeof value !== "string") {
return undefined;
}

const trimmed = value.trim();
if (trimmed.length === 0) {
return undefined;
return normalizeTaskTimezone(undefined);
}

return trimmed;
return normalizeTaskTimezone(value);
}
Comment thread
nenrinyear marked this conversation as resolved.

function parseMaxSteps(value: unknown): number | undefined {
Expand All @@ -46,7 +42,9 @@ function parseMaxSteps(value: unknown): number | undefined {
return Math.min(rounded, MAX_STEPS_LIMIT);
}

export function toRequestPayload(input: unknown): TaskDecomposeRequest | null {
export function toRequestPayload(
input: unknown,
): ValidatedTaskDecomposeRequest | null {
if (!input || typeof input !== "object") {
return null;
}
Expand Down
18 changes: 18 additions & 0 deletions backend/src/features/task-decompose/task-timezone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const DEFAULT_TASK_TIMEZONE = "Asia/Tokyo";

export function normalizeTaskTimezone(
value: string | null | undefined,
): string {
if (!value || value.trim().length === 0) {
return DEFAULT_TASK_TIMEZONE;
}

const candidate = value.trim();
try {
return new Intl.DateTimeFormat("en-US", {
timeZone: candidate,
}).resolvedOptions().timeZone;
} catch {
return DEFAULT_TASK_TIMEZONE;
}
}
Loading