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
1 change: 1 addition & 0 deletions db/migrations-wstore/000008_aimeta.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- presets exist in config files, and should automatically prepopulate the meta in the older code versions
18 changes: 18 additions & 0 deletions db/migrations-wstore/000008_aimeta.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--- removes all ai: keys except ai:preset
UPDATE db_block
SET data = json_remove(
db_block.data,
'$.meta.ai:*',
'$.meta.ai:apitype',
'$.meta.ai:baseurl',
'$.meta.ai:apitoken',
'$.meta.ai:name',
'$.meta.ai:model',
'$.meta.ai:orgid',
'$.meta.ai:apiversion',
'$.meta.ai:maxtokens',
'$.meta.ai:timeoutms',
'$.meta.ai:fontsize',
'$.meta.ai:fixedfontsize'
)
WHERE json_extract(data, '$.meta.view') = 'waveai';
46 changes: 28 additions & 18 deletions frontend/app/view/waveai/waveai.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { RpcResponseHelper, WshClient } from "@/app/store/wshclient";
import { RpcApi } from "@/app/store/wshclientapi";
import { makeFeBlockRouteId } from "@/app/store/wshrouter";
import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil";
import { atoms, createBlock, fetchWaveFile, getApi, globalStore, useOverrideConfigAtom, WOS } from "@/store/global";
import { atoms, createBlock, fetchWaveFile, getApi, globalStore, WOS } from "@/store/global";
import { BlockService, ObjectService } from "@/store/services";
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
import { fireAndForget, isBlank, makeIconClass } from "@/util/util";
import { fireAndForget, isBlank, makeIconClass, mergeMeta } from "@/util/util";
import { atom, Atom, PrimitiveAtom, useAtomValue, WritableAtom } from "jotai";
import { splitAtom } from "jotai/utils";
import type { OverlayScrollbars } from "overlayscrollbars";
Expand Down Expand Up @@ -67,6 +67,7 @@ export class WaveAiModel implements ViewModel {
blockAtom: Atom<Block>;
presetKey: Atom<string>;
presetMap: Atom<{ [k: string]: MetaType }>;
mergedPresets: Atom<MetaType>;
aiOpts: Atom<WaveAIOptsType>;
viewIcon?: Atom<string | IconButtonDecl>;
viewName?: Atom<string>;
Expand Down Expand Up @@ -160,22 +161,32 @@ export class WaveAiModel implements ViewModel {
set(this.updateLastMessageAtom, "", false);
});

this.aiOpts = atom((get) => {
this.mergedPresets = atom((get) => {
const meta = get(this.blockAtom).meta;
let settings = get(atoms.settingsAtom);
settings = {
...settings,
...meta,
};
let presetKey = get(this.presetKey);
let presets = get(atoms.fullConfigAtom).presets;
let selectedPresets = presets?.[presetKey] ?? {};

let mergedPresets: MetaType = {};
mergedPresets = mergeMeta(settings, selectedPresets, "ai");
mergedPresets = mergeMeta(mergedPresets, meta, "ai");

return mergedPresets;
});

this.aiOpts = atom((get) => {
const mergedPresets = get(this.mergedPresets);

const opts: WaveAIOptsType = {
model: settings["ai:model"] ?? null,
apitype: settings["ai:apitype"] ?? null,
orgid: settings["ai:orgid"] ?? null,
apitoken: settings["ai:apitoken"] ?? null,
apiversion: settings["ai:apiversion"] ?? null,
maxtokens: settings["ai:maxtokens"] ?? null,
timeoutms: settings["ai:timeoutms"] ?? 60000,
baseurl: settings["ai:baseurl"] ?? null,
model: mergedPresets["ai:model"] ?? null,
apitype: mergedPresets["ai:apitype"] ?? null,
orgid: mergedPresets["ai:orgid"] ?? null,
apitoken: mergedPresets["ai:apitoken"] ?? null,
apiversion: mergedPresets["ai:apiversion"] ?? null,
maxtokens: mergedPresets["ai:maxtokens"] ?? null,
timeoutms: mergedPresets["ai:timeoutms"] ?? 60000,
baseurl: mergedPresets["ai:baseurl"] ?? null,
};
return opts;
});
Expand Down Expand Up @@ -244,7 +255,6 @@ export class WaveAiModel implements ViewModel {
onClick: () =>
fireAndForget(() =>
ObjectService.UpdateObjectMeta(WOS.makeORef("block", this.blockId), {
...preset[1],
"ai:preset": preset[0],
})
),
Expand Down Expand Up @@ -437,8 +447,8 @@ export class WaveAiModel implements ViewModel {
const ChatItem = ({ chatItemAtom, model }: ChatItemProps) => {
const chatItem = useAtomValue(chatItemAtom);
const { user, text } = chatItem;
const fontSize = useOverrideConfigAtom(model.blockId, "ai:fontsize");
const fixedFontSize = useOverrideConfigAtom(model.blockId, "ai:fixedfontsize");
const fontSize = useAtomValue(model.mergedPresets)?.["ai:fontsize"];
const fixedFontSize = useAtomValue(model.mergedPresets)?.["ai:fixedfontsize"];
const renderContent = useMemo(() => {
if (user == "error") {
return (
Expand Down
72 changes: 72 additions & 0 deletions frontend/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,77 @@ function makeNativeLabel(platform: string, isDirectory: boolean, isParent: boole
return `${fileAction} in ${managerName}`;
}

function mergeMeta(meta: MetaType, metaUpdate: MetaType, prefix?: string): MetaType {
const rtn: MetaType = {};

// Helper function to check if a key matches the prefix criteria
const shouldIncludeKey = (key: string): boolean => {
if (prefix === undefined) {
return true;
}
if (prefix === "") {
return !key.includes(":");
}
return key.startsWith(prefix + ":");
};

// Copy original meta (only keys matching prefix criteria)
for (const [k, v] of Object.entries(meta)) {
if (shouldIncludeKey(k)) {
rtn[k] = v;
}
}

// Deal with "section:*" keys (only if they match prefix criteria)
for (const k of Object.keys(metaUpdate)) {
if (!k.endsWith(":*")) {
continue;
}

if (!metaUpdate[k]) {
continue;
}

const sectionPrefix = k.slice(0, -2); // Remove ':*' suffix
if (sectionPrefix === "") {
continue;
}

// Only process if this section matches our prefix criteria
if (!shouldIncludeKey(sectionPrefix)) {
continue;
}

// Delete "[sectionPrefix]" and all keys that start with "[sectionPrefix]:"
const prefixColon = sectionPrefix + ":";
for (const k2 of Object.keys(rtn)) {
if (k2 === sectionPrefix || k2.startsWith(prefixColon)) {
delete rtn[k2];
}
}
}

// Deal with regular keys (only if they match prefix criteria)
for (const [k, v] of Object.entries(metaUpdate)) {
if (!shouldIncludeKey(k)) {
continue;
}

if (k.endsWith(":*")) {
continue;
}

if (v === null || v === undefined) {
delete rtn[k];
continue;
}

rtn[k] = v;
}

return rtn;
}

export {
atomWithDebounce,
atomWithThrottle,
Expand All @@ -349,6 +420,7 @@ export {
makeExternLink,
makeIconClass,
makeNativeLabel,
mergeMeta,
sleep,
stringToBase64,
useAtomValueSafe,
Expand Down
Loading