chore: sync protocol outputs
This commit is contained in:
@@ -473,6 +473,7 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
public let replychannel: String?
|
public let replychannel: String?
|
||||||
public let accountid: String?
|
public let accountid: String?
|
||||||
public let replyaccountid: String?
|
public let replyaccountid: String?
|
||||||
|
public let threadid: String?
|
||||||
public let timeout: Int?
|
public let timeout: Int?
|
||||||
public let lane: String?
|
public let lane: String?
|
||||||
public let extrasystemprompt: String?
|
public let extrasystemprompt: String?
|
||||||
@@ -494,6 +495,7 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
replychannel: String?,
|
replychannel: String?,
|
||||||
accountid: String?,
|
accountid: String?,
|
||||||
replyaccountid: String?,
|
replyaccountid: String?,
|
||||||
|
threadid: String?,
|
||||||
timeout: Int?,
|
timeout: Int?,
|
||||||
lane: String?,
|
lane: String?,
|
||||||
extrasystemprompt: String?,
|
extrasystemprompt: String?,
|
||||||
@@ -514,6 +516,7 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
self.replychannel = replychannel
|
self.replychannel = replychannel
|
||||||
self.accountid = accountid
|
self.accountid = accountid
|
||||||
self.replyaccountid = replyaccountid
|
self.replyaccountid = replyaccountid
|
||||||
|
self.threadid = threadid
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.lane = lane
|
self.lane = lane
|
||||||
self.extrasystemprompt = extrasystemprompt
|
self.extrasystemprompt = extrasystemprompt
|
||||||
@@ -535,6 +538,7 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
case replychannel = "replyChannel"
|
case replychannel = "replyChannel"
|
||||||
case accountid = "accountId"
|
case accountid = "accountId"
|
||||||
case replyaccountid = "replyAccountId"
|
case replyaccountid = "replyAccountId"
|
||||||
|
case threadid = "threadId"
|
||||||
case timeout
|
case timeout
|
||||||
case lane
|
case lane
|
||||||
case extrasystemprompt = "extraSystemPrompt"
|
case extrasystemprompt = "extraSystemPrompt"
|
||||||
@@ -835,35 +839,47 @@ public struct SessionsListParams: Codable, Sendable {
|
|||||||
public let activeminutes: Int?
|
public let activeminutes: Int?
|
||||||
public let includeglobal: Bool?
|
public let includeglobal: Bool?
|
||||||
public let includeunknown: Bool?
|
public let includeunknown: Bool?
|
||||||
|
public let includederivedtitles: Bool?
|
||||||
|
public let includelastmessage: Bool?
|
||||||
public let label: String?
|
public let label: String?
|
||||||
public let spawnedby: String?
|
public let spawnedby: String?
|
||||||
public let agentid: String?
|
public let agentid: String?
|
||||||
|
public let search: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
limit: Int?,
|
limit: Int?,
|
||||||
activeminutes: Int?,
|
activeminutes: Int?,
|
||||||
includeglobal: Bool?,
|
includeglobal: Bool?,
|
||||||
includeunknown: Bool?,
|
includeunknown: Bool?,
|
||||||
|
includederivedtitles: Bool?,
|
||||||
|
includelastmessage: Bool?,
|
||||||
label: String?,
|
label: String?,
|
||||||
spawnedby: String?,
|
spawnedby: String?,
|
||||||
agentid: String?
|
agentid: String?,
|
||||||
|
search: String?
|
||||||
) {
|
) {
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.activeminutes = activeminutes
|
self.activeminutes = activeminutes
|
||||||
self.includeglobal = includeglobal
|
self.includeglobal = includeglobal
|
||||||
self.includeunknown = includeunknown
|
self.includeunknown = includeunknown
|
||||||
|
self.includederivedtitles = includederivedtitles
|
||||||
|
self.includelastmessage = includelastmessage
|
||||||
self.label = label
|
self.label = label
|
||||||
self.spawnedby = spawnedby
|
self.spawnedby = spawnedby
|
||||||
self.agentid = agentid
|
self.agentid = agentid
|
||||||
|
self.search = search
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case limit
|
case limit
|
||||||
case activeminutes = "activeMinutes"
|
case activeminutes = "activeMinutes"
|
||||||
case includeglobal = "includeGlobal"
|
case includeglobal = "includeGlobal"
|
||||||
case includeunknown = "includeUnknown"
|
case includeunknown = "includeUnknown"
|
||||||
|
case includederivedtitles = "includeDerivedTitles"
|
||||||
|
case includelastmessage = "includeLastMessage"
|
||||||
case label
|
case label
|
||||||
case spawnedby = "spawnedBy"
|
case spawnedby = "spawnedBy"
|
||||||
case agentid = "agentId"
|
case agentid = "agentId"
|
||||||
|
case search
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -473,6 +473,7 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
public let replychannel: String?
|
public let replychannel: String?
|
||||||
public let accountid: String?
|
public let accountid: String?
|
||||||
public let replyaccountid: String?
|
public let replyaccountid: String?
|
||||||
|
public let threadid: String?
|
||||||
public let timeout: Int?
|
public let timeout: Int?
|
||||||
public let lane: String?
|
public let lane: String?
|
||||||
public let extrasystemprompt: String?
|
public let extrasystemprompt: String?
|
||||||
@@ -494,6 +495,7 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
replychannel: String?,
|
replychannel: String?,
|
||||||
accountid: String?,
|
accountid: String?,
|
||||||
replyaccountid: String?,
|
replyaccountid: String?,
|
||||||
|
threadid: String?,
|
||||||
timeout: Int?,
|
timeout: Int?,
|
||||||
lane: String?,
|
lane: String?,
|
||||||
extrasystemprompt: String?,
|
extrasystemprompt: String?,
|
||||||
@@ -514,6 +516,7 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
self.replychannel = replychannel
|
self.replychannel = replychannel
|
||||||
self.accountid = accountid
|
self.accountid = accountid
|
||||||
self.replyaccountid = replyaccountid
|
self.replyaccountid = replyaccountid
|
||||||
|
self.threadid = threadid
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.lane = lane
|
self.lane = lane
|
||||||
self.extrasystemprompt = extrasystemprompt
|
self.extrasystemprompt = extrasystemprompt
|
||||||
@@ -535,6 +538,7 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
case replychannel = "replyChannel"
|
case replychannel = "replyChannel"
|
||||||
case accountid = "accountId"
|
case accountid = "accountId"
|
||||||
case replyaccountid = "replyAccountId"
|
case replyaccountid = "replyAccountId"
|
||||||
|
case threadid = "threadId"
|
||||||
case timeout
|
case timeout
|
||||||
case lane
|
case lane
|
||||||
case extrasystemprompt = "extraSystemPrompt"
|
case extrasystemprompt = "extraSystemPrompt"
|
||||||
@@ -835,35 +839,47 @@ public struct SessionsListParams: Codable, Sendable {
|
|||||||
public let activeminutes: Int?
|
public let activeminutes: Int?
|
||||||
public let includeglobal: Bool?
|
public let includeglobal: Bool?
|
||||||
public let includeunknown: Bool?
|
public let includeunknown: Bool?
|
||||||
|
public let includederivedtitles: Bool?
|
||||||
|
public let includelastmessage: Bool?
|
||||||
public let label: String?
|
public let label: String?
|
||||||
public let spawnedby: String?
|
public let spawnedby: String?
|
||||||
public let agentid: String?
|
public let agentid: String?
|
||||||
|
public let search: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
limit: Int?,
|
limit: Int?,
|
||||||
activeminutes: Int?,
|
activeminutes: Int?,
|
||||||
includeglobal: Bool?,
|
includeglobal: Bool?,
|
||||||
includeunknown: Bool?,
|
includeunknown: Bool?,
|
||||||
|
includederivedtitles: Bool?,
|
||||||
|
includelastmessage: Bool?,
|
||||||
label: String?,
|
label: String?,
|
||||||
spawnedby: String?,
|
spawnedby: String?,
|
||||||
agentid: String?
|
agentid: String?,
|
||||||
|
search: String?
|
||||||
) {
|
) {
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.activeminutes = activeminutes
|
self.activeminutes = activeminutes
|
||||||
self.includeglobal = includeglobal
|
self.includeglobal = includeglobal
|
||||||
self.includeunknown = includeunknown
|
self.includeunknown = includeunknown
|
||||||
|
self.includederivedtitles = includederivedtitles
|
||||||
|
self.includelastmessage = includelastmessage
|
||||||
self.label = label
|
self.label = label
|
||||||
self.spawnedby = spawnedby
|
self.spawnedby = spawnedby
|
||||||
self.agentid = agentid
|
self.agentid = agentid
|
||||||
|
self.search = search
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case limit
|
case limit
|
||||||
case activeminutes = "activeMinutes"
|
case activeminutes = "activeMinutes"
|
||||||
case includeglobal = "includeGlobal"
|
case includeglobal = "includeGlobal"
|
||||||
case includeunknown = "includeUnknown"
|
case includeunknown = "includeUnknown"
|
||||||
|
case includederivedtitles = "includeDerivedTitles"
|
||||||
|
case includelastmessage = "includeLastMessage"
|
||||||
case label
|
case label
|
||||||
case spawnedby = "spawnedBy"
|
case spawnedby = "spawnedBy"
|
||||||
case agentid = "agentId"
|
case agentid = "agentId"
|
||||||
|
case search
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1324,6 +1340,9 @@ public struct ChannelsStatusResult: Codable, Sendable {
|
|||||||
public let ts: Int
|
public let ts: Int
|
||||||
public let channelorder: [String]
|
public let channelorder: [String]
|
||||||
public let channellabels: [String: AnyCodable]
|
public let channellabels: [String: AnyCodable]
|
||||||
|
public let channeldetaillabels: [String: AnyCodable]?
|
||||||
|
public let channelsystemimages: [String: AnyCodable]?
|
||||||
|
public let channelmeta: [[String: AnyCodable]]?
|
||||||
public let channels: [String: AnyCodable]
|
public let channels: [String: AnyCodable]
|
||||||
public let channelaccounts: [String: AnyCodable]
|
public let channelaccounts: [String: AnyCodable]
|
||||||
public let channeldefaultaccountid: [String: AnyCodable]
|
public let channeldefaultaccountid: [String: AnyCodable]
|
||||||
@@ -1332,6 +1351,9 @@ public struct ChannelsStatusResult: Codable, Sendable {
|
|||||||
ts: Int,
|
ts: Int,
|
||||||
channelorder: [String],
|
channelorder: [String],
|
||||||
channellabels: [String: AnyCodable],
|
channellabels: [String: AnyCodable],
|
||||||
|
channeldetaillabels: [String: AnyCodable]?,
|
||||||
|
channelsystemimages: [String: AnyCodable]?,
|
||||||
|
channelmeta: [[String: AnyCodable]]?,
|
||||||
channels: [String: AnyCodable],
|
channels: [String: AnyCodable],
|
||||||
channelaccounts: [String: AnyCodable],
|
channelaccounts: [String: AnyCodable],
|
||||||
channeldefaultaccountid: [String: AnyCodable]
|
channeldefaultaccountid: [String: AnyCodable]
|
||||||
@@ -1339,6 +1361,9 @@ public struct ChannelsStatusResult: Codable, Sendable {
|
|||||||
self.ts = ts
|
self.ts = ts
|
||||||
self.channelorder = channelorder
|
self.channelorder = channelorder
|
||||||
self.channellabels = channellabels
|
self.channellabels = channellabels
|
||||||
|
self.channeldetaillabels = channeldetaillabels
|
||||||
|
self.channelsystemimages = channelsystemimages
|
||||||
|
self.channelmeta = channelmeta
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.channelaccounts = channelaccounts
|
self.channelaccounts = channelaccounts
|
||||||
self.channeldefaultaccountid = channeldefaultaccountid
|
self.channeldefaultaccountid = channeldefaultaccountid
|
||||||
@@ -1347,6 +1372,9 @@ public struct ChannelsStatusResult: Codable, Sendable {
|
|||||||
case ts
|
case ts
|
||||||
case channelorder = "channelOrder"
|
case channelorder = "channelOrder"
|
||||||
case channellabels = "channelLabels"
|
case channellabels = "channelLabels"
|
||||||
|
case channeldetaillabels = "channelDetailLabels"
|
||||||
|
case channelsystemimages = "channelSystemImages"
|
||||||
|
case channelmeta = "channelMeta"
|
||||||
case channels
|
case channels
|
||||||
case channelaccounts = "channelAccounts"
|
case channelaccounts = "channelAccounts"
|
||||||
case channeldefaultaccountid = "channelDefaultAccountId"
|
case channeldefaultaccountid = "channelDefaultAccountId"
|
||||||
|
|||||||
@@ -3,92 +3,92 @@ import { Type } from "@sinclair/typebox";
|
|||||||
import { NonEmptyString, SessionLabelString } from "./primitives.js";
|
import { NonEmptyString, SessionLabelString } from "./primitives.js";
|
||||||
|
|
||||||
export const SessionsListParamsSchema = Type.Object(
|
export const SessionsListParamsSchema = Type.Object(
|
||||||
{
|
{
|
||||||
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
||||||
activeMinutes: Type.Optional(Type.Integer({ minimum: 1 })),
|
activeMinutes: Type.Optional(Type.Integer({ minimum: 1 })),
|
||||||
includeGlobal: Type.Optional(Type.Boolean()),
|
includeGlobal: Type.Optional(Type.Boolean()),
|
||||||
includeUnknown: Type.Optional(Type.Boolean()),
|
includeUnknown: Type.Optional(Type.Boolean()),
|
||||||
/**
|
/**
|
||||||
* Read first 8KB of each session transcript to derive title from first user message.
|
* Read first 8KB of each session transcript to derive title from first user message.
|
||||||
* Performs a file read per session - use `limit` to bound result set on large stores.
|
* Performs a file read per session - use `limit` to bound result set on large stores.
|
||||||
*/
|
*/
|
||||||
includeDerivedTitles: Type.Optional(Type.Boolean()),
|
includeDerivedTitles: Type.Optional(Type.Boolean()),
|
||||||
/**
|
/**
|
||||||
* Read last 16KB of each session transcript to extract most recent message preview.
|
* Read last 16KB of each session transcript to extract most recent message preview.
|
||||||
* Performs a file read per session - use `limit` to bound result set on large stores.
|
* Performs a file read per session - use `limit` to bound result set on large stores.
|
||||||
*/
|
*/
|
||||||
includeLastMessage: Type.Optional(Type.Boolean()),
|
includeLastMessage: Type.Optional(Type.Boolean()),
|
||||||
label: Type.Optional(SessionLabelString),
|
label: Type.Optional(SessionLabelString),
|
||||||
spawnedBy: Type.Optional(NonEmptyString),
|
spawnedBy: Type.Optional(NonEmptyString),
|
||||||
agentId: Type.Optional(NonEmptyString),
|
agentId: Type.Optional(NonEmptyString),
|
||||||
search: Type.Optional(Type.String()),
|
search: Type.Optional(Type.String()),
|
||||||
},
|
},
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SessionsResolveParamsSchema = Type.Object(
|
export const SessionsResolveParamsSchema = Type.Object(
|
||||||
{
|
{
|
||||||
key: Type.Optional(NonEmptyString),
|
key: Type.Optional(NonEmptyString),
|
||||||
label: Type.Optional(SessionLabelString),
|
label: Type.Optional(SessionLabelString),
|
||||||
agentId: Type.Optional(NonEmptyString),
|
agentId: Type.Optional(NonEmptyString),
|
||||||
spawnedBy: Type.Optional(NonEmptyString),
|
spawnedBy: Type.Optional(NonEmptyString),
|
||||||
includeGlobal: Type.Optional(Type.Boolean()),
|
includeGlobal: Type.Optional(Type.Boolean()),
|
||||||
includeUnknown: Type.Optional(Type.Boolean()),
|
includeUnknown: Type.Optional(Type.Boolean()),
|
||||||
},
|
},
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SessionsPatchParamsSchema = Type.Object(
|
export const SessionsPatchParamsSchema = Type.Object(
|
||||||
{
|
{
|
||||||
key: NonEmptyString,
|
key: NonEmptyString,
|
||||||
label: Type.Optional(Type.Union([SessionLabelString, Type.Null()])),
|
label: Type.Optional(Type.Union([SessionLabelString, Type.Null()])),
|
||||||
thinkingLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
thinkingLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
verboseLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
verboseLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
reasoningLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
reasoningLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
responseUsage: Type.Optional(
|
responseUsage: Type.Optional(
|
||||||
Type.Union([
|
Type.Union([
|
||||||
Type.Literal("off"),
|
Type.Literal("off"),
|
||||||
Type.Literal("tokens"),
|
Type.Literal("tokens"),
|
||||||
Type.Literal("full"),
|
Type.Literal("full"),
|
||||||
// Backward compat with older clients/stores.
|
// Backward compat with older clients/stores.
|
||||||
Type.Literal("on"),
|
Type.Literal("on"),
|
||||||
Type.Null(),
|
Type.Null(),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
elevatedLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
elevatedLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
execHost: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
execHost: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
execSecurity: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
execSecurity: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
execAsk: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
execAsk: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
execNode: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
execNode: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
model: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
model: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
spawnedBy: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
spawnedBy: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
||||||
sendPolicy: Type.Optional(
|
sendPolicy: Type.Optional(
|
||||||
Type.Union([Type.Literal("allow"), Type.Literal("deny"), Type.Null()]),
|
Type.Union([Type.Literal("allow"), Type.Literal("deny"), Type.Null()]),
|
||||||
),
|
),
|
||||||
groupActivation: Type.Optional(
|
groupActivation: Type.Optional(
|
||||||
Type.Union([Type.Literal("mention"), Type.Literal("always"), Type.Null()]),
|
Type.Union([Type.Literal("mention"), Type.Literal("always"), Type.Null()]),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SessionsResetParamsSchema = Type.Object(
|
export const SessionsResetParamsSchema = Type.Object(
|
||||||
{ key: NonEmptyString },
|
{ key: NonEmptyString },
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SessionsDeleteParamsSchema = Type.Object(
|
export const SessionsDeleteParamsSchema = Type.Object(
|
||||||
{
|
{
|
||||||
key: NonEmptyString,
|
key: NonEmptyString,
|
||||||
deleteTranscript: Type.Optional(Type.Boolean()),
|
deleteTranscript: Type.Optional(Type.Boolean()),
|
||||||
},
|
},
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SessionsCompactParamsSchema = Type.Object(
|
export const SessionsCompactParamsSchema = Type.Object(
|
||||||
{
|
{
|
||||||
key: NonEmptyString,
|
key: NonEmptyString,
|
||||||
maxLines: Type.Optional(Type.Integer({ minimum: 1 })),
|
maxLines: Type.Optional(Type.Integer({ minimum: 1 })),
|
||||||
},
|
},
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { Editor, type EditorOptions, type EditorTheme, type TUI, Key, matchesKey } from "@mariozechner/pi-tui";
|
import {
|
||||||
|
Editor,
|
||||||
|
type EditorOptions,
|
||||||
|
type EditorTheme,
|
||||||
|
type TUI,
|
||||||
|
Key,
|
||||||
|
matchesKey,
|
||||||
|
} from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
export class CustomEditor extends Editor {
|
export class CustomEditor extends Editor {
|
||||||
onEscape?: () => void;
|
onEscape?: () => void;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const WORD_BOUNDARY_CHARS = /[\s\-_./:#@]/;
|
|||||||
* Check if position is at a word boundary.
|
* Check if position is at a word boundary.
|
||||||
*/
|
*/
|
||||||
export function isWordBoundary(text: string, index: number): boolean {
|
export function isWordBoundary(text: string, index: number): boolean {
|
||||||
return index === 0 || WORD_BOUNDARY_CHARS.test(text[index - 1] ?? "");
|
return index === 0 || WORD_BOUNDARY_CHARS.test(text[index - 1] ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,17 +19,17 @@ export function isWordBoundary(text: string, index: number): boolean {
|
|||||||
* Returns null if no match.
|
* Returns null if no match.
|
||||||
*/
|
*/
|
||||||
export function findWordBoundaryIndex(text: string, query: string): number | null {
|
export function findWordBoundaryIndex(text: string, query: string): number | null {
|
||||||
if (!query) return null;
|
if (!query) return null;
|
||||||
const textLower = text.toLowerCase();
|
const textLower = text.toLowerCase();
|
||||||
const queryLower = query.toLowerCase();
|
const queryLower = query.toLowerCase();
|
||||||
const maxIndex = textLower.length - queryLower.length;
|
const maxIndex = textLower.length - queryLower.length;
|
||||||
if (maxIndex < 0) return null;
|
if (maxIndex < 0) return null;
|
||||||
for (let i = 0; i <= maxIndex; i++) {
|
for (let i = 0; i <= maxIndex; i++) {
|
||||||
if (textLower.startsWith(queryLower, i) && isWordBoundary(textLower, i)) {
|
if (textLower.startsWith(queryLower, i) && isWordBoundary(textLower, i)) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,31 +37,31 @@ export function findWordBoundaryIndex(text: string, query: string): number | nul
|
|||||||
* Returns score (lower = better) or null if no match.
|
* Returns score (lower = better) or null if no match.
|
||||||
*/
|
*/
|
||||||
export function fuzzyMatchLower(queryLower: string, textLower: string): number | null {
|
export function fuzzyMatchLower(queryLower: string, textLower: string): number | null {
|
||||||
if (queryLower.length === 0) return 0;
|
if (queryLower.length === 0) return 0;
|
||||||
if (queryLower.length > textLower.length) return null;
|
if (queryLower.length > textLower.length) return null;
|
||||||
|
|
||||||
let queryIndex = 0;
|
let queryIndex = 0;
|
||||||
let score = 0;
|
let score = 0;
|
||||||
let lastMatchIndex = -1;
|
let lastMatchIndex = -1;
|
||||||
let consecutiveMatches = 0;
|
let consecutiveMatches = 0;
|
||||||
|
|
||||||
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
|
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
|
||||||
if (textLower[i] === queryLower[queryIndex]) {
|
if (textLower[i] === queryLower[queryIndex]) {
|
||||||
const isAtWordBoundary = isWordBoundary(textLower, i);
|
const isAtWordBoundary = isWordBoundary(textLower, i);
|
||||||
if (lastMatchIndex === i - 1) {
|
if (lastMatchIndex === i - 1) {
|
||||||
consecutiveMatches++;
|
consecutiveMatches++;
|
||||||
score -= consecutiveMatches * 5; // Reward consecutive matches
|
score -= consecutiveMatches * 5; // Reward consecutive matches
|
||||||
} else {
|
} else {
|
||||||
consecutiveMatches = 0;
|
consecutiveMatches = 0;
|
||||||
if (lastMatchIndex >= 0) score += (i - lastMatchIndex - 1) * 2; // Penalize gaps
|
if (lastMatchIndex >= 0) score += (i - lastMatchIndex - 1) * 2; // Penalize gaps
|
||||||
}
|
}
|
||||||
if (isAtWordBoundary) score -= 10; // Reward word boundary matches
|
if (isAtWordBoundary) score -= 10; // Reward word boundary matches
|
||||||
score += i * 0.1; // Slight penalty for later matches
|
score += i * 0.1; // Slight penalty for later matches
|
||||||
lastMatchIndex = i;
|
lastMatchIndex = i;
|
||||||
queryIndex++;
|
queryIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return queryIndex < queryLower.length ? null : score;
|
return queryIndex < queryLower.length ? null : score;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,46 +69,46 @@ export function fuzzyMatchLower(queryLower: string, textLower: string): number |
|
|||||||
* Supports space-separated tokens (all must match).
|
* Supports space-separated tokens (all must match).
|
||||||
*/
|
*/
|
||||||
export function fuzzyFilterLower<T extends { searchTextLower?: string }>(
|
export function fuzzyFilterLower<T extends { searchTextLower?: string }>(
|
||||||
items: T[],
|
items: T[],
|
||||||
queryLower: string,
|
queryLower: string,
|
||||||
): T[] {
|
): T[] {
|
||||||
const trimmed = queryLower.trim();
|
const trimmed = queryLower.trim();
|
||||||
if (!trimmed) return items;
|
if (!trimmed) return items;
|
||||||
|
|
||||||
const tokens = trimmed.split(/\s+/).filter((t) => t.length > 0);
|
const tokens = trimmed.split(/\s+/).filter((t) => t.length > 0);
|
||||||
if (tokens.length === 0) return items;
|
if (tokens.length === 0) return items;
|
||||||
|
|
||||||
const results: { item: T; score: number }[] = [];
|
const results: { item: T; score: number }[] = [];
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const text = item.searchTextLower ?? "";
|
const text = item.searchTextLower ?? "";
|
||||||
let totalScore = 0;
|
let totalScore = 0;
|
||||||
let allMatch = true;
|
let allMatch = true;
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
const score = fuzzyMatchLower(token, text);
|
const score = fuzzyMatchLower(token, text);
|
||||||
if (score !== null) {
|
if (score !== null) {
|
||||||
totalScore += score;
|
totalScore += score;
|
||||||
} else {
|
} else {
|
||||||
allMatch = false;
|
allMatch = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (allMatch) results.push({ item, score: totalScore });
|
if (allMatch) results.push({ item, score: totalScore });
|
||||||
}
|
}
|
||||||
results.sort((a, b) => a.score - b.score);
|
results.sort((a, b) => a.score - b.score);
|
||||||
return results.map((r) => r.item);
|
return results.map((r) => r.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare items for fuzzy filtering by pre-computing lowercase search text.
|
* Prepare items for fuzzy filtering by pre-computing lowercase search text.
|
||||||
*/
|
*/
|
||||||
export function prepareSearchItems<T extends { label?: string; description?: string; searchText?: string }>(
|
export function prepareSearchItems<
|
||||||
items: T[],
|
T extends { label?: string; description?: string; searchText?: string },
|
||||||
): (T & { searchTextLower: string })[] {
|
>(items: T[]): (T & { searchTextLower: string })[] {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (item.label) parts.push(item.label);
|
if (item.label) parts.push(item.label);
|
||||||
if (item.description) parts.push(item.description);
|
if (item.description) parts.push(item.description);
|
||||||
if (item.searchText) parts.push(item.searchText);
|
if (item.searchText) parts.push(item.searchText);
|
||||||
return { ...item, searchTextLower: parts.join(" ").toLowerCase() };
|
return { ...item, searchTextLower: parts.join(" ").toLowerCase() };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import {
|
|||||||
selectListTheme,
|
selectListTheme,
|
||||||
settingsListTheme,
|
settingsListTheme,
|
||||||
} from "../theme/theme.js";
|
} from "../theme/theme.js";
|
||||||
import {
|
import { FilterableSelectList, type FilterableSelectItem } from "./filterable-select-list.js";
|
||||||
FilterableSelectList,
|
|
||||||
type FilterableSelectItem,
|
|
||||||
} from "./filterable-select-list.js";
|
|
||||||
import { SearchableSelectList } from "./searchable-select-list.js";
|
import { SearchableSelectList } from "./searchable-select-list.js";
|
||||||
|
|
||||||
export function createSelectList(items: SelectItem[], maxVisible = 7) {
|
export function createSelectList(items: SelectItem[], maxVisible = 7) {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
export function formatRelativeTime(timestamp: number): string {
|
export function formatRelativeTime(timestamp: number): string {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const diff = now - timestamp;
|
const diff = now - timestamp;
|
||||||
const seconds = Math.floor(diff / 1000);
|
const seconds = Math.floor(diff / 1000);
|
||||||
const minutes = Math.floor(seconds / 60);
|
const minutes = Math.floor(seconds / 60);
|
||||||
const hours = Math.floor(minutes / 60);
|
const hours = Math.floor(minutes / 60);
|
||||||
const days = Math.floor(hours / 24);
|
const days = Math.floor(hours / 24);
|
||||||
|
|
||||||
if (seconds < 60) return "just now";
|
if (seconds < 60) return "just now";
|
||||||
if (minutes < 60) return `${minutes}m ago`;
|
if (minutes < 60) return `${minutes}m ago`;
|
||||||
if (hours < 24) return `${hours}h ago`;
|
if (hours < 24) return `${hours}h ago`;
|
||||||
if (days === 1) return "Yesterday";
|
if (days === 1) return "Yesterday";
|
||||||
if (days < 7) return `${days}d ago`;
|
if (days < 7) return `${days}d ago`;
|
||||||
return new Date(timestamp).toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
return new Date(timestamp).toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user