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
30 changes: 25 additions & 5 deletions src/application/ResourceVersionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,15 @@ namespace TUI::Application
{
/** Lock first, then check. */
LockReadLock(resourcePath, id);
auto invalidationVersion = _states.at(resourcePath).invalidationVersion;
std::weak_ptr<ResourceVersionManager<ID>> manager_ref = this->shared_from_this();
Lock lock{
[=]()
{
auto manager = manager_ref.lock();
if (manager)
{
manager->ConfirmRead(resourcePath, id);
manager->ConfirmRead(resourcePath, id, invalidationVersion);
}
},
[=]()
Expand Down Expand Up @@ -116,14 +117,15 @@ namespace TUI::Application
Lock GetWriteLock(const std::vector<std::string>& resourcePath, const ID& id)
{
LockWriteLock(resourcePath, id);
auto invalidationVersion = _states.at(resourcePath).invalidationVersion;
std::weak_ptr<ResourceVersionManager<ID>> manager_ref = this->shared_from_this();
Lock lock{
[=]()
{
auto manager = manager_ref.lock();
if (manager)
{
manager->ConfirmWrite(resourcePath, id);
manager->ConfirmWrite(resourcePath, id, invalidationVersion);
}
},
[=]()
Expand Down Expand Up @@ -170,35 +172,53 @@ namespace TUI::Application
return std::move(lock);
}

void Invalidate(const std::vector<std::string>& resourcePath)
{
auto item = _states.find(resourcePath);
if (item != _states.end())
{
item->second.upToDateSet.clear();
++item->second.invalidationVersion;
}
}

private:
struct ResourceState
{
std::unordered_set<ID> upToDateSet{};
std::unordered_set<ID> readLockHolders{};
std::optional<ID> writeLockHolder{};
size_t invalidationVersion{0};
};
/** Stores the IDs that are up to date on the resource path (the key). */
std::map<std::vector<std::string>, ResourceState> _states{};

ResourceVersionManager() = default;

void ConfirmRead(const std::vector<std::string>& resourcePath, const ID& id)
void ConfirmRead(const std::vector<std::string>& resourcePath, const ID& id, size_t invalidationVersion)
{
/** This creates the entry if it does not exist. */
auto& state = _states[resourcePath];
if (state.invalidationVersion != invalidationVersion)
{
return;
}
if (state.upToDateSet.find(id) != state.upToDateSet.end())
{
return;
}
state.upToDateSet.insert(id);
};

void ConfirmWrite(const std::vector<std::string>& resourcePath, const ID& id)
void ConfirmWrite(const std::vector<std::string>& resourcePath, const ID& id, size_t invalidationVersion)
{
/** This will make id the sole user that's up to date */
auto& state = _states[resourcePath];
state.upToDateSet.clear();
state.upToDateSet.insert(id);
if (state.invalidationVersion == invalidationVersion)
{
state.upToDateSet.insert(id);
}
};

/**
Expand Down
44 changes: 42 additions & 2 deletions src/application/Service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Service::Service(
{"getMetadata", std::bind(&Service::OnGetMetadataAsync, this, std::placeholders::_1, std::placeholders::_2)},
{"deleteMetadata", std::bind(&Service::OnDeleteMetadataAsync, this, std::placeholders::_1, std::placeholders::_2)},
{"getChatList", std::bind(&Service::OnGetChatListAsync, this, std::placeholders::_1, std::placeholders::_2)},
{"getPinnedChatList", std::bind(&Service::OnGetPinnedChatListAsync, this, std::placeholders::_1, std::placeholders::_2)},
{"setChatPinned", std::bind(&Service::OnSetChatPinnedAsync, this, std::placeholders::_1, std::placeholders::_2)},
{"newChat", std::bind(&Service::OnNewChatAsync, this, std::placeholders::_1, std::placeholders::_2)},
{"getChat", std::bind(&Service::OnGetChatAsync, this, std::placeholders::_1, std::placeholders::_2)},
{"deleteChat", std::bind(&Service::DeleteChatAsync, this, std::placeholders::_1, std::placeholders::_2)},
Expand Down Expand Up @@ -169,6 +171,7 @@ JS::Promise<nlohmann::json> Service::OnSetMetadataAsync(CallerId callerId, nlohm
co_await _database->SetChatMetadataAsync(
callerId.userId, chatId,
std::move(newMetadataString));
InvalidateChatLists(callerId.userId);
}
else
{
Expand Down Expand Up @@ -355,6 +358,7 @@ JS::Promise<nlohmann::json> Service::OnDeleteMetadataAsync(CallerId callerId, nl
co_await _database->SetChatMetadataAsync(
callerId.userId, chatId,
std::move(newMetadataString));
InvalidateChatLists(callerId.userId);
}
else
{
Expand Down Expand Up @@ -390,9 +394,36 @@ JS::Promise<nlohmann::json> Service::OnGetChatListAsync(CallerId callerId, nlohm
}

auto list = _database->ListChat(callerId.userId, static_cast<size_t>(start), static_cast<size_t>(quantity));
co_return static_cast<nlohmann::json>(MakeChatListResult(list, params.get_meta_data_keys()));
}

JS::Promise<nlohmann::json> Service::OnGetPinnedChatListAsync(CallerId callerId, nlohmann::json paramsJson)
{
auto params = ParseParams<Schema::IServer::GetPinnedChatListParams>(paramsJson);
auto lock = _resourceVersionManager->GetReadLock(
{"pinnedChatList", static_cast<std::string>(callerId.userId)}, callerId);
auto list = _database->ListPinnedChat(callerId.userId);
co_return static_cast<nlohmann::json>(MakeChatListResult(list, params.get_meta_data_keys()));
}

JS::Promise<nlohmann::json> Service::OnSetChatPinnedAsync(CallerId callerId, nlohmann::json paramsJson)
{
auto params = ParseParams<Schema::IServer::SetChatPinnedParams>(paramsJson);
Common::Uuid chatId{params.get_id()};
if (!(co_await _database->SetChatPinnedAsync(callerId.userId, chatId, params.get_pinned())))
{
throw Schema::Rpc::Exception(Schema::Rpc::ErrorCode::NOT_FOUND, "Chat not found");
}
_resourceVersionManager->Invalidate({"pinnedChatList", static_cast<std::string>(callerId.userId)});
co_return nlohmann::json{};
}

Schema::IServer::GetChatListResult Service::MakeChatListResult(
const std::list<Database::Database::IdMetadataPair>& list,
const std::optional<std::vector<std::string>>& metadataKeys)
{
Schema::IServer::GetChatListResult result{};
result.reserve(list.size());
auto metadataKeys = params.get_meta_data_keys();
for (const auto& item : list)
{
using EntryType = std::remove_reference<decltype(result)>::type::value_type;
Expand All @@ -407,7 +438,14 @@ JS::Promise<nlohmann::json> Service::OnGetChatListAsync(CallerId callerId, nlohm
result.push_back(std::move(entry));
}
result.shrink_to_fit();
co_return static_cast<nlohmann::json>(result);
return result;
}

void Service::InvalidateChatLists(const Common::Uuid& userId)
{
auto userIdString = static_cast<std::string>(userId);
_resourceVersionManager->Invalidate({"chatList", userIdString});
_resourceVersionManager->Invalidate({"pinnedChatList", userIdString});
}

/**
Expand Down Expand Up @@ -469,6 +507,7 @@ JS::Promise<nlohmann::json> Service::DeleteChatAsync(CallerId callerId, nlohmann
{"chat", static_cast<std::string>(callerId.userId), static_cast<std::string>(chatId)}, callerId);

co_await _database->DeleteChatAsync(callerId.userId, chatId);
InvalidateChatLists(callerId.userId);
/** Return null */
co_return nlohmann::json{};
}
Expand Down Expand Up @@ -675,6 +714,7 @@ JS::AsyncGenerator<nlohmann::json, nlohmann::json> Service::OnChatCompletionAsyn
callerId.userId, chatId,
std::move(allNodes[i]),
i == 0);
InvalidateChatLists(callerId.userId);
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/application/Service.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ namespace TUI::Application
JS::Promise<nlohmann::json> OnGetMetadataAsync(CallerId callerId, nlohmann::json params);
JS::Promise<nlohmann::json> OnDeleteMetadataAsync(CallerId callerId, nlohmann::json params);
JS::Promise<nlohmann::json> OnGetChatListAsync(CallerId callerId, nlohmann::json params);
JS::Promise<nlohmann::json> OnGetPinnedChatListAsync(CallerId callerId, nlohmann::json params);
JS::Promise<nlohmann::json> OnSetChatPinnedAsync(CallerId callerId, nlohmann::json params);
JS::Promise<nlohmann::json> OnNewChatAsync(CallerId callerId, nlohmann::json params);
JS::Promise<nlohmann::json> OnGetChatAsync(CallerId callerId, nlohmann::json params);
JS::Promise<nlohmann::json> DeleteChatAsync(CallerId callerId, nlohmann::json params);
Expand Down Expand Up @@ -92,6 +94,10 @@ namespace TUI::Application
}

std::shared_ptr<ApiProvider::IProvider> GetProvider(const Common::Uuid& providerId);
Schema::IServer::GetChatListResult MakeChatListResult(
const std::list<Database::Database::IdMetadataPair>& list,
const std::optional<std::vector<std::string>>& metadataKeys);
void InvalidateChatLists(const Common::Uuid& userId);
std::map<std::string, nlohmann::json> TryGetMetadata(const std::vector<std::string>& keys, const std::string& metadataString);
std::string TryMergeMetadata(const std::string& base, std::map<std::string, nlohmann::json>& changes);
std::string TryDeleteMetadata(const std::string& base, const std::vector<std::string>& keys);
Expand Down
41 changes: 38 additions & 3 deletions src/database/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ JS::Promise<std::shared_ptr<Database>> Database::CreateAsync(
"user_id TEXT, "
"id TEXT, "
"metadata TEXT, "
"pinned INTEGER NOT NULL DEFAULT 0 CHECK (pinned IN (0, 1)), "
"PRIMARY KEY (user_id, id));");
co_await db->_db->ExecAsync(
"CREATE TABLE IF NOT EXISTS chat_content ("
Expand All @@ -66,6 +67,21 @@ JS::Promise<std::shared_ptr<Database>> Database::CreateAsync(
"message TEXT, "
"timestamp INTEGER, "
"PRIMARY KEY (user_id, chat_id, id));");
if (db->_db->Exec("SELECT name FROM pragma_table_info('chat') WHERE name = 'pinned';").empty())
{
co_await db->_db->ExecAsync("BEGIN IMMEDIATE;");
co_await db->_db->ExecAsync(
"ALTER TABLE chat ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0 CHECK (pinned IN (0, 1));");
co_await db->_db->ExecAsync(
"UPDATE chat SET timestamp = COALESCE("
"(SELECT MAX(timestamp) FROM chat_content WHERE user_id = chat.user_id AND chat_id = chat.id), "
"timestamp);");
co_await db->_db->ExecAsync("COMMIT;");
}
co_await db->_db->ExecAsync(
"CREATE INDEX IF NOT EXISTS chat_activity ON chat (user_id, timestamp DESC, id DESC);");
co_await db->_db->ExecAsync(
"CREATE INDEX IF NOT EXISTS chat_pinned_activity ON chat (user_id, timestamp DESC, id DESC) WHERE pinned = 1;");
co_await db->_db->ExecAsync(
"CREATE TABLE IF NOT EXISTS file_meta ("
"user_id TEXT, "
Expand Down Expand Up @@ -372,12 +388,28 @@ std::list<Database::IdMetadataPair> Database::ListChat(
const Uuid& userId, size_t from , size_t limit)
{
auto sql = std::format(
"SELECT id, metadata FROM chat WHERE user_id = ? ORDER BY timestamp DESC LIMIT {} OFFSET {}",
"SELECT id, metadata FROM chat WHERE user_id = ? ORDER BY timestamp DESC, id DESC LIMIT {} OFFSET {}",
limit, from);
auto result = _db->Exec(sql, static_cast<std::string>(userId));
return ParseListTableIdWithMetadataResult(result);
}

std::list<Database::IdMetadataPair> Database::ListPinnedChat(const Uuid& userId)
{
auto result = _db->Exec(
"SELECT id, metadata FROM chat WHERE user_id = ? AND pinned = 1 ORDER BY timestamp DESC, id DESC;",
static_cast<std::string>(userId));
return ParseListTableIdWithMetadataResult(result);
}

JS::Promise<bool> Database::SetChatPinnedAsync(const Uuid& userId, const Uuid& chatId, bool pinned)
{
auto result = co_await _db->ExecAsync(
"UPDATE chat SET pinned = ? WHERE user_id = ? AND id = ? RETURNING id;",
static_cast<int64_t>(pinned), static_cast<std::string>(userId), static_cast<std::string>(chatId));
co_return !result.empty();
}

JS::Promise<void> Database::SetChatMetadataAsync(const Uuid& userId, const Uuid& id, std::string metadata)
{
return SetStringToChatAsync(userId, id, "metadata", metadata);
Expand Down Expand Up @@ -444,6 +476,10 @@ JS::Promise<void> Database::AppendChatHistoryAsync(
(static_cast<nlohmann::json>(node.get_children())).dump(),
(static_cast<nlohmann::json>(node.get_message())).dump(),
static_cast<int64_t>(node.get_timestamp()));
co_await _db->ExecAsync(
"UPDATE chat SET timestamp = ? WHERE user_id = ? AND id = ?;",
Timestamp::GetWallClock(),
static_cast<std::string>(userId), static_cast<std::string>(chatId));
}

IServer::TreeHistory Database::GetChatHistory(const Uuid& userId, const Uuid& id)
Expand Down Expand Up @@ -602,12 +638,11 @@ JS::Promise<void> Database::SetStringToChatAsync(
const Uuid& userId, const Uuid& id, const std::string& name, std::string value)
{
auto sql = std::format(
"UPDATE chat SET {} = ?, timestamp = ? WHERE user_id = ? AND id = ?;",
"UPDATE chat SET {} = ? WHERE user_id = ? AND id = ?;",
name);
co_await _db->ExecAsync(
sql,
std::move(value),
Timestamp::GetWallClock(),
static_cast<std::string>(userId),
static_cast<std::string>(id));
}
Expand Down
3 changes: 3 additions & 0 deletions src/database/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ namespace TUI::Database
size_t GetChatCount(const Common::Uuid& userId);
std::list<IdMetadataPair> ListChat(
const Common::Uuid& userId, size_t from = 0, size_t limit = 50);
std::list<IdMetadataPair> ListPinnedChat(const Common::Uuid& userId);
JS::Promise<bool> SetChatPinnedAsync(
const Common::Uuid& userId, const Common::Uuid& chatId, bool pinned);
JS::Promise<void> SetChatMetadataAsync(
const Common::Uuid& userId, const Common::Uuid& chatId, std::string metadata);
std::string GetChatMetadata(const Common::Uuid& userId, const Common::Uuid& chatId);
Expand Down
60 changes: 60 additions & 0 deletions src/schema/IServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,37 @@ namespace IServer {
void set_metadata(std::optional<std::map<std::string, nlohmann::json>> value) { this->metadata = value; }
};

class GetPinnedChatListParams {
public:
GetPinnedChatListParams() = default;
virtual ~GetPinnedChatListParams() = default;

private:
std::optional<std::vector<std::string>> meta_data_keys;

public:
std::optional<std::vector<std::string>> get_meta_data_keys() const { return meta_data_keys; }
void set_meta_data_keys(std::optional<std::vector<std::string>> value) { this->meta_data_keys = value; }
};

class SetChatPinnedParams {
public:
SetChatPinnedParams() = default;
virtual ~SetChatPinnedParams() = default;

private:
std::string id;
bool pinned{false};

public:
const std::string & get_id() const { return id; }
std::string & get_mutable_id() { return id; }
void set_id(const std::string & value) { this->id = value; }

bool get_pinned() const { return pinned; }
void set_pinned(bool value) { this->pinned = value; }
};

class Tool {
public:
Tool() = default;
Expand Down Expand Up @@ -945,6 +976,7 @@ namespace IServer {
using GetMetadataResult = std::map<std::string, nlohmann::json>;
using LinearHistory = std::vector<Message>;
using GetChatListResult = std::vector<GetChatListResultElement>;
using GetPinnedChatListResult = GetChatListResult;
using GetModelListResult = std::vector<GetModelListResultElement>;
using GetUserListResult = std::vector<GetUserListResultElement>;
using ListFileResult = std::vector<ListFileResultElement>;
Expand Down Expand Up @@ -988,6 +1020,12 @@ void to_json(json & j, const GetChatListParams & x);
void from_json(const json & j, GetChatListResultElement & x);
void to_json(json & j, const GetChatListResultElement & x);

void from_json(const json & jsonValue, GetPinnedChatListParams & value);
void to_json(json & jsonValue, const GetPinnedChatListParams & value);

void from_json(const json & jsonValue, SetChatPinnedParams & value);
void to_json(json & jsonValue, const SetChatPinnedParams & value);

void from_json(const json & j, Tool & x);
void to_json(json & j, const Tool & x);

Expand Down Expand Up @@ -1248,6 +1286,28 @@ namespace IServer {
}
}

inline void from_json(const json & jsonValue, GetPinnedChatListParams & value) {
value.set_meta_data_keys(get_stack_optional<std::vector<std::string>>(jsonValue, "metaDataKeys"));
}

inline void to_json(json & jsonValue, const GetPinnedChatListParams & value) {
jsonValue = json::object();
if (value.get_meta_data_keys()) {
jsonValue["metaDataKeys"] = value.get_meta_data_keys();
}
}

inline void from_json(const json & jsonValue, SetChatPinnedParams & value) {
value.set_id(jsonValue.at("id").get<std::string>());
value.set_pinned(jsonValue.at("pinned").get<bool>());
}

inline void to_json(json & jsonValue, const SetChatPinnedParams & value) {
jsonValue = json::object();
jsonValue["id"] = value.get_id();
jsonValue["pinned"] = value.get_pinned();
}

inline void from_json(const json & j, Tool& x) {
x.set_description(j.at("description").get<std::string>());
x.set_name(j.at("name").get<std::string>());
Expand Down
2 changes: 1 addition & 1 deletion src/schema/src/types
Submodule types updated 1 files
+16 −2 IServer.ts
Loading
Loading