Let a bot take any colour
A bot's identity was one of seven hues, and the hue picked both its colour and its stone shape. Now its colour is free and its stone is its own: - `bots.color` holds any `#rrggbb`, `bots.stone` the flat-cut shape it's drawn on. `bots.hue` stays as the palette column they replaced, read only as a fallback; `src/scripts/move-bot-colour-to-owners.ts` backfills an install from it (safe to rerun). - `src/utils/bot-color.ts` is the only place a colour is validated or resolved (`resolveBotColor`/`resolveBotStone` carry the old hue forward, `botColorNeedsPaperFace` flips the eyes to paper on a very dark body), and `src/utils/bot-stone-props.ts` gives every row its props, so a row saved before this still renders as it did. - `BotStone` takes the colour as the body's SVG `fill`, the one documented exception to the token rule: an arbitrary hex has no token to point at. `UnreadDot` follows it. - The picker keeps the palette tiles (still shape and colour together) and adds a native swatch and a hex field, so a custom colour keeps its stone. Same place as before: the header avatar's modal and Settings > Bots, for crew managers. The coordinator stays hue-less. - A recolour reaches other open tabs through `pushBotUpdated`.
This commit is contained in:
@@ -8,7 +8,7 @@ import type { BUN_SQLITE_COBBLE_BOTS, BUN_SQLITE_COBBLE_WORKSPACES } from "@/db/
|
||||
export default async function grabCrewOptions() {
|
||||
const [bots, workspaces] = await Promise.all([
|
||||
BunSQLite.sql<BUN_SQLITE_COBBLE_BOTS>({
|
||||
sql: `SELECT id, name, role, hue, image_media_id FROM bots WHERE kind = 'worker' ORDER BY name COLLATE NOCASE ASC, id ASC`,
|
||||
sql: `SELECT id, name, role, color, stone, hue, image_media_id FROM bots WHERE kind = 'worker' ORDER BY name COLLATE NOCASE ASC, id ASC`,
|
||||
}),
|
||||
BunSQLite.sql<BUN_SQLITE_COBBLE_WORKSPACES>({
|
||||
sql: `SELECT id, name, description, image_media_id FROM workspaces WHERE archived = 0 ORDER BY name COLLATE NOCASE ASC, id ASC`,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { BUN_SQLITE_COBBLE_BOTS, BunSQLiteTables } from "@/db/types/db";
|
||||
import type { BotHue } from "@/src/dict/bot-dict";
|
||||
import type { BotStoneShape } from "@/src/dict/bot-dict";
|
||||
import assertPromptLength from "@/src/utils/assert-prompt-length";
|
||||
import assertCrewManager from "@/src/functions/backend/db/access/assert-crew-manager";
|
||||
import attachDefaultExtensions from "../extensions/attach-default-extensions";
|
||||
@@ -12,7 +12,14 @@ type Params = {
|
||||
user_id: number | string;
|
||||
name: string;
|
||||
role?: string;
|
||||
hue: BotHue;
|
||||
/**
|
||||
* Its identity colour, `#rrggbb`.
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* Which flat-cut stone it's drawn on.
|
||||
*/
|
||||
stone: BotStoneShape;
|
||||
/**
|
||||
* Its core instructions (markdown).
|
||||
*/
|
||||
@@ -26,7 +33,7 @@ type Params = {
|
||||
* computer of its own until it's given one; in a workspace it uses the
|
||||
* workspace's.
|
||||
*/
|
||||
export default async function createBot({ user_id, name, role, hue, system_prompt }: Params) {
|
||||
export default async function createBot({ user_id, name, role, color, stone, system_prompt }: Params) {
|
||||
await assertCrewManager({ user_id });
|
||||
|
||||
assertPromptLength({ label: "Instructions", text: system_prompt });
|
||||
@@ -41,7 +48,8 @@ export default async function createBot({ user_id, name, role, hue, system_promp
|
||||
user_id: Number(user_id),
|
||||
name,
|
||||
kind: "worker",
|
||||
hue,
|
||||
color,
|
||||
stone,
|
||||
role: role || undefined,
|
||||
system_prompt,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type { BotHue } from "@/src/dict/bot-dict";
|
||||
import type { BotStoneShape } from "@/src/dict/bot-dict";
|
||||
import AppError from "@/src/utils/app-error";
|
||||
import assertPromptLength from "@/src/utils/assert-prompt-length";
|
||||
import grabUserBot from "./grab-user-bot";
|
||||
@@ -10,16 +10,31 @@ type Params = {
|
||||
bot_id: number | string;
|
||||
name: string;
|
||||
role?: string;
|
||||
hue: BotHue;
|
||||
/**
|
||||
* Its identity colour, `#rrggbb`.
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* Which flat-cut stone it's drawn on.
|
||||
*/
|
||||
stone: BotStoneShape;
|
||||
system_prompt: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves one of the user's bots: name, job, hue and core instructions. The
|
||||
* coordinator keeps its name and stays hue-less, so only its instructions
|
||||
* change. Returns the saved bot.
|
||||
* Saves one of the user's bots: name, job, colour, stone and core
|
||||
* instructions. The coordinator keeps its name and stays hue-less, so only
|
||||
* its instructions change. Returns the saved bot.
|
||||
*/
|
||||
export default async function updateBot({ user_id, bot_id, name, role, hue, system_prompt }: Params) {
|
||||
export default async function updateBot({
|
||||
user_id,
|
||||
bot_id,
|
||||
name,
|
||||
role,
|
||||
color,
|
||||
stone,
|
||||
system_prompt,
|
||||
}: Params) {
|
||||
await assertCrewManager({ user_id });
|
||||
|
||||
assertPromptLength({ label: "Instructions", text: system_prompt });
|
||||
@@ -37,8 +52,8 @@ export default async function updateBot({ user_id, bot_id, name, role, hue, syst
|
||||
});
|
||||
} else {
|
||||
await BunSQLite.sql({
|
||||
sql: `UPDATE bots SET name = ?, role = ?, hue = ?, system_prompt = ?, updated_at = ? WHERE id = ?`,
|
||||
values: [name, role || null, hue, system_prompt, Date.now(), bot.id],
|
||||
sql: `UPDATE bots SET name = ?, role = ?, color = ?, stone = ?, system_prompt = ?, updated_at = ? WHERE id = ?`,
|
||||
values: [name, role || null, color, stone, system_prompt, Date.now(), bot.id],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function grabCronJobs({ user_id, bot_id, cron_job_id }: Par
|
||||
}
|
||||
|
||||
const res = await BunSQLite.sql<CronJobWithNames>({
|
||||
sql: `SELECT c.*, b.name AS bot_name, b.kind AS bot_kind, b.hue AS bot_hue, ${face.sql},
|
||||
sql: `SELECT c.*, b.name AS bot_name, b.kind AS bot_kind, b.hue AS bot_hue, b.color AS bot_color, b.stone AS bot_stone, ${face.sql},
|
||||
b.image_media_id AS bot_image_media_id, w.name AS workspace_name
|
||||
FROM cron_jobs c
|
||||
JOIN bots b ON b.id = c.bot_id
|
||||
|
||||
@@ -25,7 +25,7 @@ export default async function grabTask({ user_id, task_id }: Params): Promise<Ta
|
||||
const face = botFaceSql({ user_id, alias: "b", prefix: "bot_" });
|
||||
|
||||
const row_res = await BunSQLite.sql<TaskDetail>({
|
||||
sql: `SELECT t.*, b.name AS bot_name, b.kind AS bot_kind, b.hue AS bot_hue,
|
||||
sql: `SELECT t.*, b.name AS bot_name, b.kind AS bot_kind, b.hue AS bot_hue, b.color AS bot_color, b.stone AS bot_stone,
|
||||
b.image_media_id AS bot_image_media_id, ${face.sql}, w.name AS workspace_name
|
||||
FROM tasks t
|
||||
LEFT JOIN bots b ON b.id = t.bot_id
|
||||
|
||||
@@ -83,7 +83,7 @@ export default async function grabTasks({
|
||||
const face = botFaceSql({ user_id: user_id || 0, alias: "b", prefix: "bot_" });
|
||||
|
||||
const res = await BunSQLite.sql<TaskWithNames>({
|
||||
sql: `SELECT t.*, b.name AS bot_name, b.kind AS bot_kind, b.hue AS bot_hue,
|
||||
sql: `SELECT t.*, b.name AS bot_name, b.kind AS bot_kind, b.hue AS bot_hue, b.color AS bot_color, b.stone AS bot_stone,
|
||||
b.image_media_id AS bot_image_media_id, ${face.sql}, w.name AS workspace_name,
|
||||
(SELECT n.text FROM task_notes n WHERE n.task_id = t.id ORDER BY n.id DESC LIMIT 1) AS latest_note,
|
||||
(SELECT COUNT(*) FROM task_media tm WHERE tm.task_id = t.id) AS media_count
|
||||
|
||||
@@ -24,6 +24,8 @@ export default async function grabWorkspaceActivity({
|
||||
b.name AS bot_name,
|
||||
b.image_media_id AS bot_image_media_id,
|
||||
b.hue AS bot_hue,
|
||||
b.color AS bot_color,
|
||||
b.stone AS bot_stone,
|
||||
${face.sql},
|
||||
(SELECT COUNT(*) FROM bot_messages m WHERE m.session_id = s.id) AS message_count,
|
||||
(SELECT m.text FROM bot_messages m WHERE m.session_id = s.id ORDER BY m.created_at DESC, m.id DESC LIMIT 1) AS last_message_text
|
||||
|
||||
Reference in New Issue
Block a user