mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-09-27 00:53:37 +00:00
The settings gain a data backup panel. It writes the chats of the chosen workspaces, and optionally the temporary chats, into a single archive file with the extension .aistudio-chats, and it imports such archives again. The archive mirrors the chat storage, so restoring needs no conversion. Documents the user attached are never copied into the archive. The chats keep the absolute path of each document, which keeps them usable wherever the documents live and avoids a second copy. Files AI Studio created itself, such as transcripts, exist nowhere else; when the selection holds any, the user decides whether they travel with the archive. On import, chats which already exist, in any workspace, are either kept or imported as additional chats with a new ID. Workspaces are merged by their identity. An imported chat gets its own copy of every transcript the archive did not carry, as a copied chat does, so deleting it never removes the transcripts of the chat it came from. An existing archive is only replaced once the new one is complete. Chats which could not be read are reported instead of missing silently. Both directions can be stopped at any time and report what was written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.8 KiB
C#
68 lines
2.8 KiB
C#
namespace AIStudio.Tools.ChatArchive;
|
|
|
|
/// <summary>
|
|
/// Finds the files AI Studio keeps inside a chat directory itself, such as transcripts.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Documents the user attaches are never copied into a chat directory: they stay where the
|
|
/// user put them and the chat only stores their path. Everything below a chat directory was
|
|
/// therefore written by AI Studio, which makes the directory contents a reliable definition
|
|
/// of "the app's own files" — minus the two files the chat storage owns itself.
|
|
/// </remarks>
|
|
public static class ChatArchiveChatFiles
|
|
{
|
|
/// <summary>
|
|
/// Collects the app-owned files of one chat.
|
|
/// </summary>
|
|
/// <param name="chatDirectory">The directory of the chat.</param>
|
|
/// <returns>The absolute paths of the files, keyed by their chat-relative path.</returns>
|
|
public static Dictionary<string, string> Collect(string chatDirectory)
|
|
{
|
|
var files = new Dictionary<string, string>(PathTools.COMPARER);
|
|
if (!Directory.Exists(chatDirectory))
|
|
return files;
|
|
|
|
foreach (var filePath in Directory.EnumerateFiles(chatDirectory, "*", SearchOption.AllDirectories))
|
|
{
|
|
// Skip the temporary files the chat storage writes while saving a chat:
|
|
var fileName = Path.GetFileName(filePath);
|
|
if (fileName.StartsWith('.') && fileName.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
|
|
var chatRelativePath = Path.GetRelativePath(chatDirectory, filePath).Replace(Path.DirectorySeparatorChar, ChatArchiveFormat.ENTRY_SEPARATOR);
|
|
|
|
// The chat itself is always exported and is not one of its own attachments:
|
|
if (chatRelativePath.Equals(ChatArchiveFormat.THREAD_FILE_NAME, StringComparison.OrdinalIgnoreCase) ||
|
|
chatRelativePath.Equals(ChatArchiveFormat.NAME_FILE_NAME, StringComparison.OrdinalIgnoreCase))
|
|
continue;
|
|
|
|
files[chatRelativePath] = filePath;
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether at least one of the given chats owns a file.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This stops at the first file found, so that asking the question stays cheap even for
|
|
/// a large chat storage.
|
|
/// </remarks>
|
|
/// <param name="chatDirectories">The directories of the chats to check.</param>
|
|
/// <param name="token">Cancels the search.</param>
|
|
/// <returns>Whether any app-owned file exists.</returns>
|
|
public static bool HasAny(IEnumerable<string> chatDirectories, CancellationToken token)
|
|
{
|
|
foreach (var chatDirectory in chatDirectories)
|
|
{
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
if (Collect(chatDirectory).Count > 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|