feat: parse natural language date
This commit is contained in:
parent
c72c526664
commit
c2ed44c19e
|
|
@ -53,6 +53,7 @@
|
|||
"@yornaath/batshit": "0.11.1",
|
||||
"ai": "5.0.76",
|
||||
"camelcase-keys": "10.0.0",
|
||||
"chrono-node": "2.9.0",
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import * as chrono from "chrono-node"
|
||||
import dayjs from "dayjs"
|
||||
import type { IFuseOptions } from "fuse.js"
|
||||
import Fuse from "fuse.js"
|
||||
|
|
@ -129,6 +130,74 @@ const normalizeQuery = (query: string): string => {
|
|||
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate chrono parser based on language
|
||||
*/
|
||||
const getChronoParser = (language: string) => {
|
||||
if (language === "zh-CN") {
|
||||
return chrono.zh.hans
|
||||
}
|
||||
if (language === "zh-TW") {
|
||||
return chrono.zh.hant
|
||||
}
|
||||
if (language === "ja") {
|
||||
return chrono.ja
|
||||
}
|
||||
return chrono.en
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse natural language dates using chrono-node with language-specific parser
|
||||
* Returns a DateRange if the input can be parsed, otherwise null
|
||||
*/
|
||||
const parseNaturalLanguageDate = (query: string, language: string): DateRange | null => {
|
||||
if (!query.trim()) return null
|
||||
|
||||
try {
|
||||
// Get language-specific parser
|
||||
const parser = getChronoParser(language)
|
||||
|
||||
// Try to parse the date using chrono with language-specific parser first
|
||||
let parsed = parser.parse(query)
|
||||
|
||||
// If failed and not already using English parser, try English as fallback
|
||||
if ((!parsed || parsed.length === 0) && parser !== chrono.en) {
|
||||
parsed = chrono.en.parse(query)
|
||||
}
|
||||
|
||||
if (!parsed || parsed.length === 0) return null
|
||||
|
||||
const result = parsed[0]
|
||||
if (!result) return null
|
||||
|
||||
// Get the start date
|
||||
const startDate = result.start.date()
|
||||
const start = dayjs(startDate).startOf("day")
|
||||
|
||||
// Check if there's an end date (for ranges)
|
||||
let end: dayjs.Dayjs
|
||||
if (result.end) {
|
||||
const endDate = result.end.date()
|
||||
end = dayjs(endDate).startOf("day")
|
||||
} else {
|
||||
// If no end date, use the start date as a single day
|
||||
end = start
|
||||
}
|
||||
|
||||
// Validate the dates are valid
|
||||
if (!start.isValid() || !end.isValid()) return null
|
||||
|
||||
// Ensure start is before or equal to end
|
||||
if (start.isAfter(end)) {
|
||||
return { start: end, end: start }
|
||||
}
|
||||
|
||||
return { start, end }
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const createDateMentionBuilder = (context: DateMentionBuilderContext) => {
|
||||
const candidates = buildRelativeCandidates(context)
|
||||
const fuse = new Fuse(candidates, FUSE_OPTIONS)
|
||||
|
|
@ -136,10 +205,27 @@ export const createDateMentionBuilder = (context: DateMentionBuilderContext) =>
|
|||
return (query: string): MentionData[] => {
|
||||
const normalized = normalizeQuery(query)
|
||||
const today = dayjs().startOf("day")
|
||||
const bucket = normalized ? fuse.search(normalized).map((result) => result.item) : candidates
|
||||
|
||||
const mentions: MentionData[] = []
|
||||
|
||||
// Try to parse as natural language date first
|
||||
if (normalized) {
|
||||
const naturalDateRange = parseNaturalLanguageDate(normalized, context.language)
|
||||
if (naturalDateRange) {
|
||||
// Successfully parsed a natural language date
|
||||
const chronoMention = createDateMentionData({
|
||||
range: naturalDateRange,
|
||||
translate: context.t,
|
||||
locale: context.language,
|
||||
withRangeKey: RANGE_WITH_LABEL_KEY,
|
||||
displayName: formatLocalizedRange(naturalDateRange, context.language),
|
||||
})
|
||||
mentions.push(chronoMention)
|
||||
}
|
||||
}
|
||||
|
||||
// Add predefined relative date suggestions
|
||||
const bucket = normalized ? fuse.search(normalized).map((result) => result.item) : candidates
|
||||
|
||||
bucket.forEach((candidate) => {
|
||||
const range = candidate.definition.range(today)
|
||||
if (!range) return
|
||||
|
|
|
|||
|
|
@ -563,6 +563,9 @@ importers:
|
|||
camelcase-keys:
|
||||
specifier: 10.0.0
|
||||
version: 10.0.0
|
||||
chrono-node:
|
||||
specifier: 2.9.0
|
||||
version: 2.9.0
|
||||
class-variance-authority:
|
||||
specifier: 0.7.1
|
||||
version: 0.7.1
|
||||
|
|
@ -773,7 +776,7 @@ importers:
|
|||
version: link:../../../../packages/internal/utils
|
||||
'@folo-services/ai-tools':
|
||||
specifier: 0.2.43
|
||||
version: 0.2.43(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)
|
||||
version: 0.2.43(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(kysely@0.28.8)(pg@8.16.3)
|
||||
'@types/node':
|
||||
specifier: 24.8.1
|
||||
version: 24.8.1
|
||||
|
|
@ -8415,6 +8418,10 @@ packages:
|
|||
chromium-pickle-js@0.2.0:
|
||||
resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==}
|
||||
|
||||
chrono-node@2.9.0:
|
||||
resolution: {integrity: sha512-glI4YY2Jy6JII5l3d5FN6rcrIbKSQqKPhWsIRYPK2IK8Mm4Q1ZZFdYIaDqglUNf7gNwG+kWIzTn0omzzE0VkvQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
ci-info@2.0.0:
|
||||
resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
|
||||
|
||||
|
|
@ -20685,10 +20692,10 @@ snapshots:
|
|||
- svelte
|
||||
- vue
|
||||
|
||||
'@folo-services/ai-tools@0.2.43(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(hono@4.9.8)(kysely@0.28.8)(pg@8.16.3)':
|
||||
'@folo-services/ai-tools@0.2.43(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(kysely@0.28.8)(pg@8.16.3)':
|
||||
dependencies:
|
||||
'@ai-sdk/openai': 2.0.52(zod@4.1.11)
|
||||
'@folo-services/drizzle': 0.1.29(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(hono@4.9.8)(kysely@0.28.8)
|
||||
'@folo-services/drizzle': 0.1.29(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(kysely@0.28.8)
|
||||
ai: 5.0.48(zod@4.1.11)
|
||||
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(kysely@0.28.8)(pg@8.16.3)
|
||||
zod: 4.1.11
|
||||
|
|
@ -20770,6 +20777,47 @@ snapshots:
|
|||
- sql.js
|
||||
- sqlite3
|
||||
|
||||
'@folo-services/drizzle@0.1.29(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(kysely@0.28.8)':
|
||||
dependencies:
|
||||
'@folo-services/exceptions': 0.1.16
|
||||
'@hono/zod-openapi': 1.1.2(hono@4.9.8)(zod@4.1.11)
|
||||
drizzle-orm: 0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(kysely@0.28.8)(pg@8.16.3)
|
||||
drizzle-zod: 0.8.3(drizzle-orm@0.44.6(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.0)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.0)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0))(react@19.2.0))(kysely@0.28.8)(pg@8.16.3))(zod@4.1.11)
|
||||
nanoid: 5.1.6
|
||||
pg: 8.16.3
|
||||
zod: 4.1.11
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/client-rds-data'
|
||||
- '@cloudflare/workers-types'
|
||||
- '@electric-sql/pglite'
|
||||
- '@libsql/client'
|
||||
- '@libsql/client-wasm'
|
||||
- '@neondatabase/serverless'
|
||||
- '@op-engineering/op-sqlite'
|
||||
- '@opentelemetry/api'
|
||||
- '@planetscale/database'
|
||||
- '@prisma/client'
|
||||
- '@tidbcloud/serverless'
|
||||
- '@types/better-sqlite3'
|
||||
- '@types/pg'
|
||||
- '@types/sql.js'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/postgres'
|
||||
- '@xata.io/client'
|
||||
- better-sqlite3
|
||||
- bun-types
|
||||
- expo-sqlite
|
||||
- gel
|
||||
- hono
|
||||
- knex
|
||||
- kysely
|
||||
- mysql2
|
||||
- pg-native
|
||||
- postgres
|
||||
- prisma
|
||||
- sql.js
|
||||
- sqlite3
|
||||
|
||||
'@folo-services/drizzle@0.1.29(@opentelemetry/api@1.9.0)(@types/pg@8.15.5)(better-sqlite3@12.4.1)(expo-sqlite@15.2.12(expo@53.0.12(@babel/core@7.28.4)(@expo/metro-runtime@5.0.4(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(graphql@16.8.1)(react-native-webview@13.16.0(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)(utf-8-validate@6.0.5))(react@19.2.0))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)(utf-8-validate@6.0.5))(react@19.2.0)(utf-8-validate@6.0.5))(react-native@0.79.6(@babel/core@7.28.4)(@types/react@19.2.2)(bufferutil@4.0.9)(react@19.2.0)(utf-8-validate@6.0.5))(react@19.2.0))(hono@4.9.8)(kysely@0.28.8)':
|
||||
dependencies:
|
||||
'@folo-services/exceptions': 0.1.16
|
||||
|
|
@ -23256,7 +23304,7 @@ snapshots:
|
|||
debug: 2.6.9
|
||||
invariant: 2.2.4
|
||||
metro: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
|
||||
metro-config: 0.82.4(bufferutil@4.0.9)
|
||||
metro-config: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
|
||||
metro-core: 0.82.4
|
||||
semver: 7.7.3
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -26171,6 +26219,8 @@ snapshots:
|
|||
|
||||
chromium-pickle-js@0.2.0: {}
|
||||
|
||||
chrono-node@2.9.0: {}
|
||||
|
||||
ci-info@2.0.0: {}
|
||||
|
||||
ci-info@3.9.0: {}
|
||||
|
|
@ -31200,6 +31250,22 @@ snapshots:
|
|||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
metro-config@0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5):
|
||||
dependencies:
|
||||
connect: 3.7.0
|
||||
cosmiconfig: 5.2.1
|
||||
flow-enums-runtime: 0.0.6
|
||||
jest-validate: 29.7.0
|
||||
metro: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
|
||||
metro-cache: 0.82.4
|
||||
metro-core: 0.82.4
|
||||
metro-runtime: 0.82.4
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
optional: true
|
||||
|
||||
metro-core@0.82.4:
|
||||
dependencies:
|
||||
flow-enums-runtime: 0.0.6
|
||||
|
|
@ -31385,7 +31451,7 @@ snapshots:
|
|||
metro-babel-transformer: 0.82.4
|
||||
metro-cache: 0.82.4
|
||||
metro-cache-key: 0.82.4
|
||||
metro-config: 0.82.4(bufferutil@4.0.9)
|
||||
metro-config: 0.82.4(bufferutil@4.0.9)(utf-8-validate@6.0.5)
|
||||
metro-core: 0.82.4
|
||||
metro-file-map: 0.82.4
|
||||
metro-resolver: 0.82.4
|
||||
|
|
|
|||
Loading…
Reference in New Issue