feat: OpenClaw session import with sorted history

Backend:
- New OpenClawEndpoint with GET /api/openclaw/sessions and POST /api/openclaw/import
- Lists all OpenClaw sessions with preview messages, model, summary
- Imports user/assistant text messages (skips tool-use/tool-result)
- Cleans user messages (removes webchat prefixes, cron tags, conversation info blocks)
- Re-import support: deletes previous openclaw-import chains before importing
- New DatabaseService methods: CreateChainForImport, CreateMessageForImport, DeleteOpenClawImport
- Added SortKey to MessageRecord (computed: openclaw-import gets id-10B, else id)

History sorting:
- OpenClaw-imported messages always appear before native messages
- Sort by CASE WHEN tag='openclaw-import' THEN id-10000000000 ELSE id END
- Cursor-based paging updated from beforeId/afterId to beforeSortKey/afterSortKey
- All history queries (GetLatestActive, Before, After, CountBefore) updated

Frontend:
- Dashboard: new 'OpenClaw Import' menu item in session card dropdown
- Modal: shows OpenClaw sessions with metadata, summary, preview messages
- Imported messages display blue 'OC' badge in chat
- API functions: apiGetOpenclawSessions, apiImportOpenclawSession
- History paging updated to use sortKey cursors
This commit is contained in:
Meleki committed 2026-03-08 17:32:32 +01:00
1 parent 1597c10a3c
commit 3b7bdb7a20
10 files changed
+750 -38

No files matched your search

+10 -10
View File
@@ -43,16 +43,16 @@ public static class HistoryEndpoint
}
const int messageLimit = 500;
var beforeStr = ctx.Request.Query["beforeId"].FirstOrDefault();
var beforeId = long.TryParse(beforeStr, out var b) ? b : 0;
var beforeStr = ctx.Request.Query["beforeSortKey"].FirstOrDefault();
var beforeSortKey = long.TryParse(beforeStr, out var b) ? b : 0;
var includeDeleted = ctx.Request.Query["includeDeleted"].FirstOrDefault() == "true";
var totalCount = await db.GetActiveMessageCount(sessionId, includeDeleted);
List<MessageRecord> messages;
if (beforeId > 0)
if (beforeSortKey != 0)
{
messages = await db.GetActiveMessagesBeforeId(sessionId, beforeId, messageLimit, includeDeleted);
messages = await db.GetActiveMessagesBeforeSortKey(sessionId, beforeSortKey, messageLimit, includeDeleted);
}
else
{
@@ -60,9 +60,9 @@ public static class HistoryEndpoint
}
// Zähle wie viele Nachrichten es VOR der ältesten geladenen noch gibt
var oldestLoadedId = messages.Count > 0 ? messages[0].Id : 0;
var remainingOlder = oldestLoadedId > 0
? await db.GetActiveMessageCountBeforeId(sessionId, oldestLoadedId, includeDeleted)
var oldestLoadedSortKey = messages.Count > 0 ? messages[0].SortKey : 0;
var remainingOlder = oldestLoadedSortKey != 0
? await db.GetActiveMessageCountBeforeSortKey(sessionId, oldestLoadedSortKey, includeDeleted)
: 0;
// Nach Chain gruppieren (Reihenfolge beibehalten)
@@ -91,8 +91,8 @@ public static class HistoryEndpoint
private static async Task HandlePoll(HttpContext ctx, DatabaseService db)
{
var sessionId = ctx.Request.Query["sessionId"].FirstOrDefault();
var afterStr = ctx.Request.Query["afterId"].FirstOrDefault();
var afterId = long.TryParse(afterStr, out var a) ? a : 0;
var afterStr = ctx.Request.Query["afterSortKey"].FirstOrDefault();
var afterSortKey = long.TryParse(afterStr, out var a) ? a : 0;
var includeDeleted = ctx.Request.Query["includeDeleted"].FirstOrDefault() == "true";
if (string.IsNullOrEmpty(sessionId) || !await VerifySessionOwner(ctx, db, sessionId))
@@ -101,7 +101,7 @@ public static class HistoryEndpoint
return;
}
var messages = await db.GetActiveMessagesAfterId(sessionId, afterId, includeDeleted: includeDeleted);
var messages = await db.GetActiveMessagesAfterSortKey(sessionId, afterSortKey, includeDeleted: includeDeleted);
await ctx.Response.WriteAsJsonAsync(messages);
}