-
Drop a card hereHavre JSON, or a character card V1, V2 or V3 (JSON or PNG)
-
or press to choose a file
+
IMPORT A CARD
+
+
+ DROP A CARD HEREHavre JSON, or a character card V1, V2 or V3 (JSON or PNG)
+ or press to choose a file
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ventures/havre/llms.txt b/ventures/havre/llms.txt
index 4242ee6..5f3be8a 100644
--- a/ventures/havre/llms.txt
+++ b/ventures/havre/llms.txt
@@ -1,6 +1,6 @@
# Havre
-> A harbor for characters: keep them in a file you own, talk to them on your own Venice key, and remix the ones you love into someone new, with credit.
+> A harbor for characters: talk to Venice's characters on your own key, keep characters in a file you own, and remix the ones you love into someone new, with credit.
Read skill.md first and follow it. card.js and venice.js are ES modules with no dependencies (Node 20+ or a browser).
@@ -9,13 +9,7 @@ Read skill.md first and follow it. card.js and venice.js are ES modules with no
- [skill.md](https://havre.yolopreneur.lol/skill.md): how to write a Havre character, export it, talk to it through Venice, and remix with credit
- [card.schema.json](https://havre.yolopreneur.lol/card.schema.json): the card format (havre: 1), as JSON Schema
- [card.js](https://havre.yolopreneur.lol/card.js): read, check and convert cards (Character Card V1/V2/V3, PNG cards, system prompts), and the similarity check
-- [venice.js](https://havre.yolopreneur.lol/venice.js): the Venice API calls the studio makes
-
-## Examples
-
-- [Maren](https://havre.yolopreneur.lol/cast/maren.havre.json): the harbormaster, a guide
-- [Taskmaster](https://havre.yolopreneur.lol/cast/taskmaster.havre.json): a coach with a flow (assess, plan, execute)
-- [Odile](https://havre.yolopreneur.lol/cast/odile.havre.json): the lighthouse keeper, a storyteller
+- [venice.js](https://havre.yolopreneur.lol/venice.js): the Venice API calls the page makes
## Coming next
diff --git a/ventures/havre/venice.js b/ventures/havre/venice.js
index 1fca866..1f35d70 100644
--- a/ventures/havre/venice.js
+++ b/ventures/havre/venice.js
@@ -59,12 +59,29 @@ export class Venice {
});
}
- /** public Venice characters, safe-for-work only */
- async characters({ search = "", limit = 24, offset = 0 } = {}) {
- const q = new URLSearchParams({ limit: String(limit), offset: String(offset) });
+ /**
+ * One page of public Venice characters, safe for work only: { list, more }. sort: featured, imports, highestRating,
+ * mostRecent (or any other sortBy Venice takes); search matches names, descriptions and #tags.
+ */
+ async characterPage({ search = "", limit = 24, offset = 0, sort = "" } = {}) {
+ const q = new URLSearchParams({ limit: String(limit), offset: String(offset), isAdult: "false" });
if (search) q.set("search", search);
- const d = await this.call("/characters?" + q);
- return (d && d.data || []).filter((c) => !c.adult).map(characterInfo);
+ if (sort) q.set("sortBy", sort);
+ let d;
+ try {
+ d = await this.call("/characters?" + q);
+ } catch (e) {
+ if (e.status !== 400) throw e;
+ q.delete("sortBy"); q.delete("isAdult"); // asked for something this API doesn't take: ask plainly
+ d = await this.call("/characters?" + q);
+ }
+ const raw = d && Array.isArray(d.data) ? d.data : [];
+ return { list: raw.filter((c) => c && !c.adult).map(characterInfo), more: raw.length >= limit };
+ }
+
+ /** public Venice characters, safe for work only */
+ async characters(opts = {}) {
+ return (await this.characterPage(opts)).list;
}
async character(slug) {
@@ -104,6 +121,6 @@ function characterInfo(c) {
slug: c.slug, name: c.name || c.slug, description: c.description || "", creator: c.author || "",
photo: /^https:\/\/outerface\.venice\.ai\//.test(c.photoUrl || "") ? c.photoUrl : "",
url: "https://venice.ai/c/" + encodeURIComponent(c.slug), model: c.modelId || "", tags: c.tags || [],
- imports: c.stats && c.stats.imports || 0, rating: c.stats && c.stats.averageRating || 0,
+ imports: c.stats && c.stats.imports || 0, rating: c.stats && c.stats.averageRating || 0, web: !!c.webEnabled,
};
}