@meistrari/chat-nuxt 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -38
- package/dist/module.d.mts +24 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +38 -29
- package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +3 -1
- package/dist/runtime/components/MeistrariChatEmbed.vue +5 -3
- package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +3 -1
- package/dist/runtime/components/chat/thinking-indicator.vue +28 -17
- package/dist/runtime/composables/useEmbedConfig.d.ts +5 -0
- package/dist/runtime/composables/useEmbedConfig.js +11 -3
- package/dist/runtime/embed/components/ChatConfigurationModal.vue +8 -6
- package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +3 -1
- package/dist/runtime/embed/components/ChatEmbed.vue +13 -12
- package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +3 -1
- package/dist/runtime/embed/components/ChatEmbedInner.vue +5 -1
- package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +7 -4
- package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +7 -4
- package/dist/runtime/server/api/conversations/[id]/usage.get.js +1 -1
- package/dist/runtime/server/api/conversations/generate-title.post.js +18 -5
- package/dist/runtime/server/api/workspace/usage.get.js +1 -1
- package/dist/runtime/server/db/index.d.ts +20 -3
- package/dist/runtime/server/db/index.js +87 -10
- package/dist/runtime/server/db/schema/_schema.d.ts +2 -0
- package/dist/runtime/server/db/schema/_schema.js +3 -0
- package/dist/runtime/server/db/schema/conversation-usage.d.ts +1 -1
- package/dist/runtime/server/db/schema/conversation-usage.js +3 -2
- package/dist/runtime/server/db/schema/conversations.d.ts +1 -1
- package/dist/runtime/server/db/schema/conversations.js +3 -2
- package/dist/runtime/server/db/schema/external-skills.d.ts +1 -1
- package/dist/runtime/server/db/schema/external-skills.js +3 -2
- package/dist/runtime/server/db/schema/index.d.ts +1 -0
- package/dist/runtime/server/db/schema/index.js +1 -0
- package/dist/runtime/server/db/schema/messages.d.ts +1 -1
- package/dist/runtime/server/db/schema/messages.js +3 -2
- package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
- package/dist/runtime/server/db/schema/workspace-settings.js +3 -2
- package/dist/runtime/server/plugins/init-db.d.ts +2 -0
- package/dist/runtime/server/plugins/init-db.js +11 -0
- package/dist/runtime/server/utils/billing.d.ts +26 -4
- package/dist/runtime/server/utils/billing.js +52 -13
- package/dist/runtime/server/utils/chat-workspace-settings.js +1 -1
- package/dist/runtime/utils/chat-loading-messages.d.ts +12 -0
- package/dist/runtime/utils/chat-loading-messages.js +40 -0
- package/dist/runtime/utils/features.d.ts +6 -0
- package/dist/runtime/utils/features.js +15 -0
- package/dist/types.d.mts +1 -1
- package/drizzle/0000_material_hiroim.sql +28 -0
- package/drizzle/0001_famous_scalphunter.sql +1 -0
- package/drizzle/0002_gorgeous_big_bertha.sql +1 -0
- package/drizzle/0003_massive_forge.sql +2 -0
- package/drizzle/0004_absent_black_tom.sql +2 -0
- package/drizzle/0005_first_kid_colt.sql +1 -0
- package/drizzle/0006_sloppy_mauler.sql +13 -0
- package/drizzle/0007_grey_rhino.sql +1 -0
- package/drizzle/0008_chilly_blue_blade.sql +27 -0
- package/drizzle/0009_cultured_maddog.sql +16 -0
- package/drizzle/0010_small_barracuda.sql +1 -0
- package/drizzle/0011_fat_lady_mastermind.sql +6 -0
- package/drizzle/0012_puzzling_rick_jones.sql +1 -0
- package/drizzle/0013_blue_gwen_stacy.sql +11 -0
- package/drizzle/meta/0000_snapshot.json +239 -0
- package/drizzle/meta/0001_snapshot.json +245 -0
- package/drizzle/meta/0002_snapshot.json +251 -0
- package/drizzle/meta/0003_snapshot.json +263 -0
- package/drizzle/meta/0004_snapshot.json +275 -0
- package/drizzle/meta/0005_snapshot.json +281 -0
- package/drizzle/meta/0006_snapshot.json +320 -0
- package/drizzle/meta/0007_snapshot.json +326 -0
- package/drizzle/meta/0008_snapshot.json +539 -0
- package/drizzle/meta/0009_snapshot.json +646 -0
- package/drizzle/meta/0010_snapshot.json +653 -0
- package/drizzle/meta/0011_snapshot.json +689 -0
- package/drizzle/meta/0012_snapshot.json +695 -0
- package/drizzle/meta/0013_snapshot.json +699 -0
- package/drizzle/meta/_journal.json +104 -0
- package/package.json +5 -3
- package/dist/runtime/server/tasks/send-daily-usage-report.d.ts +0 -2
- package/dist/runtime/server/tasks/send-daily-usage-report.js +0 -74
- package/dist/runtime/server/utils/auth-api.d.ts +0 -1
- package/dist/runtime/server/utils/auth-api.js +0 -27
|
@@ -2,31 +2,32 @@
|
|
|
2
2
|
import { provideEmbedConfig, useEmbedConfig } from "../../composables/useEmbedConfig";
|
|
3
3
|
import ChatEmbedInner from "./ChatEmbedInner.vue";
|
|
4
4
|
const props = defineProps({
|
|
5
|
-
workspaceId: { type: String, required: true },
|
|
6
5
|
workspaceSettings: { type: null, required: false },
|
|
7
6
|
conversationId: { type: [String, null], required: false },
|
|
8
7
|
initialConversationId: { type: [String, null], required: false, default: null },
|
|
9
8
|
hideSidebar: { type: Boolean, required: false, default: true },
|
|
10
9
|
user: { type: [Object, null], required: false },
|
|
11
|
-
features: { type: Object, required: false }
|
|
10
|
+
features: { type: Object, required: false },
|
|
11
|
+
loadingMessages: { type: [Array, null], required: false },
|
|
12
|
+
loadingMessagesMode: { type: [String, null], required: false }
|
|
12
13
|
});
|
|
13
14
|
defineEmits(["update:conversationId"]);
|
|
14
15
|
const parentEmbedConfig = useEmbedConfig();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
provideEmbedConfig(props, { syncGlobal: !parentEmbedConfig });
|
|
16
|
+
const { activeOrganization } = useTelaApplicationAuth();
|
|
17
|
+
const workspaceId = computed(() => activeOrganization.value?.id?.trim() ?? "");
|
|
18
|
+
provideEmbedConfig({
|
|
19
|
+
workspaceId,
|
|
20
|
+
workspaceSettings: computed(() => props.workspaceSettings),
|
|
21
|
+
hideSidebar: computed(() => props.hideSidebar),
|
|
22
|
+
loadingMessages: computed(() => props.loadingMessages),
|
|
23
|
+
loadingMessagesMode: computed(() => props.loadingMessagesMode)
|
|
24
|
+
}, { syncGlobal: !parentEmbedConfig });
|
|
25
25
|
</script>
|
|
26
26
|
|
|
27
27
|
<template>
|
|
28
28
|
<!-- Force a clean state namespace when the workspace changes. -->
|
|
29
29
|
<ChatEmbedInner
|
|
30
|
+
v-if="workspaceId"
|
|
30
31
|
:key="workspaceId"
|
|
31
32
|
:conversation-id="conversationId"
|
|
32
33
|
:initial-conversation-id="initialConversationId"
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { DeepReadonly } from 'vue';
|
|
2
2
|
import type { WorkspaceSettings } from '../../composables/useWorkspaceSettings.js';
|
|
3
|
+
import type { ChatLoadingMessageMode } from '../../utils/chat-loading-messages.js';
|
|
3
4
|
import type { ChatActor, ChatFeatureConfig } from '../../utils/tela-chat.js';
|
|
4
5
|
type EmbedUser = ChatActor;
|
|
5
6
|
type __VLS_Props = {
|
|
6
|
-
workspaceId: string;
|
|
7
7
|
workspaceSettings?: DeepReadonly<WorkspaceSettings> | WorkspaceSettings | null;
|
|
8
8
|
conversationId?: string | null;
|
|
9
9
|
initialConversationId?: string | null;
|
|
10
10
|
hideSidebar?: boolean;
|
|
11
11
|
user?: EmbedUser | null;
|
|
12
12
|
features?: Partial<ChatFeatureConfig>;
|
|
13
|
+
loadingMessages?: readonly string[] | null;
|
|
14
|
+
loadingMessagesMode?: ChatLoadingMessageMode | null;
|
|
13
15
|
};
|
|
14
16
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
15
17
|
"update:conversationId": (value: string | null) => any;
|
|
@@ -28,7 +28,11 @@ const activeConversationId = ref(
|
|
|
28
28
|
);
|
|
29
29
|
const loadingConversationId = ref(null);
|
|
30
30
|
const isReady = ref(false);
|
|
31
|
-
const
|
|
31
|
+
const moduleFeatures = useChatNuxtFeatures();
|
|
32
|
+
const resolvedFeatures = computed(() => resolveChatFeatures({
|
|
33
|
+
showUsageTab: moduleFeatures.usage ?? props.features?.showUsageTab,
|
|
34
|
+
showDebugOption: props.features?.showDebugOption
|
|
35
|
+
}));
|
|
32
36
|
const dropZoneRef = ref(null);
|
|
33
37
|
const activeTab = ref("chat");
|
|
34
38
|
const messageInputRef = ref(null);
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
serializeResolvedWorkspaceSettings
|
|
14
14
|
} from "#chat-runtime/server/utils/chat-workspace-settings";
|
|
15
15
|
import { resolveWorkspaceCredentials } from "#chat-runtime/server/utils/workspace";
|
|
16
|
+
import { useChatNuxtFeatures } from "#chat-runtime/utils/features";
|
|
16
17
|
import { mergeFileUrls } from "#chat-runtime/utils/file";
|
|
17
18
|
import { isValidModel } from "#chat-runtime/types/chat";
|
|
18
19
|
export default defineEventHandler(async (event) => {
|
|
@@ -107,10 +108,12 @@ export default defineEventHandler(async (event) => {
|
|
|
107
108
|
try {
|
|
108
109
|
if (needsNewThread) {
|
|
109
110
|
let environmentVariables = {};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
if (useChatNuxtFeatures().credentials) {
|
|
112
|
+
try {
|
|
113
|
+
environmentVariables = await resolveWorkspaceCredentials(event, context.workspaceId);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.warn({ conversationId, messageId, error }, "Failed to resolve workspace credentials on retry, proceeding without env vars");
|
|
116
|
+
}
|
|
114
117
|
}
|
|
115
118
|
const hasEnvironmentVariables = Object.keys(environmentVariables).length > 0;
|
|
116
119
|
const result = await createChatAgentSession(token, {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
serializeResolvedWorkspaceSettings
|
|
14
14
|
} from "#chat-runtime/server/utils/chat-workspace-settings";
|
|
15
15
|
import { resolveWorkspaceCredentials } from "#chat-runtime/server/utils/workspace";
|
|
16
|
+
import { useChatNuxtFeatures } from "#chat-runtime/utils/features";
|
|
16
17
|
import { mergeFileUrls } from "#chat-runtime/utils/file";
|
|
17
18
|
import { isValidModel } from "#chat-runtime/types/chat";
|
|
18
19
|
export default defineEventHandler(async (event) => {
|
|
@@ -113,10 +114,12 @@ export default defineEventHandler(async (event) => {
|
|
|
113
114
|
const skills = externalSkillRefs.length > 0 ? externalSkillRefs : void 0;
|
|
114
115
|
if (needsNewThread) {
|
|
115
116
|
let environmentVariables = {};
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
if (useChatNuxtFeatures().credentials) {
|
|
118
|
+
try {
|
|
119
|
+
environmentVariables = await resolveWorkspaceCredentials(event, context.workspaceId);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logger.warn({ conversationId, error }, "Failed to resolve workspace credentials, proceeding without env vars");
|
|
122
|
+
}
|
|
120
123
|
}
|
|
121
124
|
const hasEnvironmentVariables = Object.keys(environmentVariables).length > 0;
|
|
122
125
|
createChatAgentSession(token, {
|
|
@@ -31,7 +31,7 @@ export default defineEventHandler(async (event) => {
|
|
|
31
31
|
eq(schema.conversationUsage.workspaceId, context.workspaceId)
|
|
32
32
|
)
|
|
33
33
|
);
|
|
34
|
-
const billing = await getWorkspaceBilling(
|
|
34
|
+
const billing = await getWorkspaceBilling(event);
|
|
35
35
|
if (!usage) {
|
|
36
36
|
return { usage: null, billing };
|
|
37
37
|
}
|
|
@@ -3,7 +3,20 @@ import { useRuntimeConfig } from "nitropack/runtime";
|
|
|
3
3
|
import { createTelaClient } from "@meistrari/tela-sdk-js";
|
|
4
4
|
import { logger } from "#chat-runtime/server/utils/logger";
|
|
5
5
|
import { requireUser } from "#chat-runtime/server/utils/auth";
|
|
6
|
+
import { useChatNuxtFeatures } from "#chat-runtime/utils/features";
|
|
6
7
|
const TITLE_CANVAS_ID = "ba06c4a0-ef05-4fb6-b4ef-5af3d63a1ba2";
|
|
8
|
+
const FALLBACK_TITLE_MAX_LENGTH = 60;
|
|
9
|
+
const FALLBACK_TITLE_DEFAULT = "New chat";
|
|
10
|
+
function buildFallbackTitle(userMessage) {
|
|
11
|
+
const normalized = userMessage.replace(/\s+/g, " ").trim();
|
|
12
|
+
if (!normalized) {
|
|
13
|
+
return FALLBACK_TITLE_DEFAULT;
|
|
14
|
+
}
|
|
15
|
+
if (normalized.length <= FALLBACK_TITLE_MAX_LENGTH) {
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
return `${normalized.slice(0, FALLBACK_TITLE_MAX_LENGTH - 1).trimEnd()}\u2026`;
|
|
19
|
+
}
|
|
7
20
|
export default defineEventHandler(async (event) => {
|
|
8
21
|
requireUser(event);
|
|
9
22
|
const { userMessage, assistantResponse } = await readBody(event);
|
|
@@ -14,11 +27,11 @@ export default defineEventHandler(async (event) => {
|
|
|
14
27
|
});
|
|
15
28
|
}
|
|
16
29
|
const { telaApiKey } = useRuntimeConfig();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
30
|
+
const features = useChatNuxtFeatures();
|
|
31
|
+
if (!features.generateTitle || !telaApiKey) {
|
|
32
|
+
const fallback = buildFallbackTitle(userMessage);
|
|
33
|
+
logger.debug({ titleLength: fallback.length, reason: !features.generateTitle ? "feature-disabled" : "no-tela-api-key" }, "Title generated via fallback");
|
|
34
|
+
return { title: fallback };
|
|
22
35
|
}
|
|
23
36
|
try {
|
|
24
37
|
const tela = createTelaClient({
|
|
@@ -54,7 +54,7 @@ export default defineEventHandler(async (event) => {
|
|
|
54
54
|
}).from(schema.conversationUsage).where(and(...conditions)).orderBy(desc(schema.conversationUsage.createdAt)).limit(limit + 1);
|
|
55
55
|
const hasMore = records.length > limit;
|
|
56
56
|
const paginatedRecords = records.slice(0, limit);
|
|
57
|
-
const billing = await getWorkspaceBilling(
|
|
57
|
+
const billing = await getWorkspaceBilling(event);
|
|
58
58
|
const formattedRecords = paginatedRecords.map((r) => {
|
|
59
59
|
const transformedCosts = transformCostForBilling({
|
|
60
60
|
promptCost: Number(r.promptCost),
|
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
import { drizzle as drizzlePostgres } from 'drizzle-orm/postgres-js';
|
|
2
|
+
import type { PgliteDatabase } from 'drizzle-orm/pglite';
|
|
1
3
|
import * as schema from './schema.js';
|
|
2
4
|
export type { ContextFile, ExternalSkill } from './schema.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
type PostgresDb = ReturnType<typeof drizzlePostgres<typeof schema>>;
|
|
6
|
+
type PgliteDb = PgliteDatabase<typeof schema>;
|
|
7
|
+
type AnyDb = PostgresDb | PgliteDb;
|
|
8
|
+
export declare function isPgliteUrl(url: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Eagerly initializes the database during Nitro startup. Idempotent. Both
|
|
11
|
+
* Postgres and pglite paths now run migrations on boot, so the chat-nuxt
|
|
12
|
+
* tables in the `chat` Postgres schema are guaranteed to exist before the
|
|
13
|
+
* first request hits a route that calls useDb().
|
|
14
|
+
*/
|
|
15
|
+
export declare function initDb(): Promise<AnyDb>;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the initialized database instance. initDb() must have already run
|
|
18
|
+
* via the boot plugin so migrations have been applied — bare useDb() now
|
|
19
|
+
* throws if called before init, regardless of driver, instead of silently
|
|
20
|
+
* skipping migrations on the Postgres path.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useDb(): AnyDb;
|
|
6
23
|
export { schema };
|
|
@@ -1,20 +1,97 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
1
3
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
4
|
import postgres from "postgres";
|
|
3
|
-
import { drizzle } from "drizzle-orm/postgres-js";
|
|
5
|
+
import { drizzle as drizzlePostgres } from "drizzle-orm/postgres-js";
|
|
6
|
+
import { migrate as migratePostgres } from "drizzle-orm/postgres-js/migrator";
|
|
4
7
|
import * as schema from "./schema/index.js";
|
|
5
|
-
import {
|
|
8
|
+
import { CHAT_SCHEMA_NAME } from "./schema/_schema.js";
|
|
9
|
+
import { logger } from "../utils/logger.js";
|
|
6
10
|
let dbInstance = null;
|
|
7
|
-
|
|
11
|
+
let dbInitPromise = null;
|
|
12
|
+
export function isPgliteUrl(url) {
|
|
13
|
+
return url.startsWith("pglite:") || url.startsWith("file:");
|
|
14
|
+
}
|
|
15
|
+
function resolvePgliteDataDir(url) {
|
|
16
|
+
if (url.startsWith("pglite:")) {
|
|
17
|
+
return url.slice("pglite:".length);
|
|
18
|
+
}
|
|
19
|
+
if (url.startsWith("file://")) {
|
|
20
|
+
return url.slice("file://".length);
|
|
21
|
+
}
|
|
22
|
+
if (url.startsWith("file:")) {
|
|
23
|
+
return url.slice("file:".length);
|
|
24
|
+
}
|
|
25
|
+
return url;
|
|
26
|
+
}
|
|
27
|
+
const IGNORED_NOTICE_CODES = /* @__PURE__ */ new Set(["42P06", "42P07"]);
|
|
28
|
+
async function initPostgresAsync(databaseUrl, migrationsDir) {
|
|
29
|
+
const client = postgres(databaseUrl, {
|
|
30
|
+
max: 10,
|
|
31
|
+
onnotice: (notice) => {
|
|
32
|
+
if (notice.code && IGNORED_NOTICE_CODES.has(notice.code))
|
|
33
|
+
return;
|
|
34
|
+
logger.info({ notice }, "Postgres notice");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const db = drizzlePostgres(client, { schema });
|
|
38
|
+
if (migrationsDir) {
|
|
39
|
+
await migratePostgres(db, { migrationsFolder: migrationsDir, migrationsSchema: CHAT_SCHEMA_NAME });
|
|
40
|
+
logger.info({ migrationsSchema: CHAT_SCHEMA_NAME }, "Postgres connection pool initialized and migrations applied");
|
|
41
|
+
} else {
|
|
42
|
+
logger.warn("Postgres connection pool initialized without migrations directory \u2014 schema may be out of date");
|
|
43
|
+
}
|
|
44
|
+
return db;
|
|
45
|
+
}
|
|
46
|
+
async function initPgliteAsync(databaseUrl, migrationsDir) {
|
|
47
|
+
const dataDir = resolvePgliteDataDir(databaseUrl);
|
|
48
|
+
await mkdir(dirname(dataDir), { recursive: true });
|
|
49
|
+
const [{ PGlite }, { drizzle }, { migrate }] = await Promise.all([
|
|
50
|
+
import("@electric-sql/pglite"),
|
|
51
|
+
import("drizzle-orm/pglite"),
|
|
52
|
+
import("drizzle-orm/pglite/migrator")
|
|
53
|
+
]);
|
|
54
|
+
const client = new PGlite(dataDir);
|
|
55
|
+
const db = drizzle(client, { schema });
|
|
56
|
+
if (migrationsDir) {
|
|
57
|
+
await migrate(db, { migrationsFolder: migrationsDir, migrationsSchema: CHAT_SCHEMA_NAME });
|
|
58
|
+
logger.info({ dataDir, migrationsSchema: CHAT_SCHEMA_NAME }, "pglite database initialized and migrated");
|
|
59
|
+
} else {
|
|
60
|
+
logger.warn({ dataDir }, "pglite database initialized without migrations directory \u2014 schema may be empty");
|
|
61
|
+
}
|
|
62
|
+
return db;
|
|
63
|
+
}
|
|
64
|
+
export async function initDb() {
|
|
8
65
|
if (dbInstance) {
|
|
9
66
|
return dbInstance;
|
|
10
67
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
68
|
+
if (dbInitPromise) {
|
|
69
|
+
return dbInitPromise;
|
|
70
|
+
}
|
|
71
|
+
dbInitPromise = (async () => {
|
|
72
|
+
const config = useRuntimeConfig();
|
|
73
|
+
const databaseUrl = config.databaseUrl;
|
|
74
|
+
const migrationsDir = config.chatNuxt?.migrationsDir ?? "";
|
|
75
|
+
if (!databaseUrl) {
|
|
76
|
+
throw new Error("DATABASE_URL is not configured and no pglite fallback path was set");
|
|
77
|
+
}
|
|
78
|
+
const db = isPgliteUrl(databaseUrl) ? await initPgliteAsync(databaseUrl, migrationsDir) : await initPostgresAsync(databaseUrl, migrationsDir);
|
|
79
|
+
dbInstance = db;
|
|
80
|
+
return db;
|
|
81
|
+
})();
|
|
82
|
+
try {
|
|
83
|
+
return await dbInitPromise;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
dbInitPromise = null;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function useDb() {
|
|
90
|
+
if (dbInstance) {
|
|
91
|
+
return dbInstance;
|
|
14
92
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return dbInstance;
|
|
93
|
+
throw new Error(
|
|
94
|
+
"chat-nuxt database is not initialized. The chat-nuxt boot plugin should have called initDb() before any request \u2014 check that the module registered server/plugins/init-db.ts."
|
|
95
|
+
);
|
|
19
96
|
}
|
|
20
97
|
export { schema };
|
|
@@ -19,7 +19,7 @@ export type ModelUsageEntry = {
|
|
|
19
19
|
};
|
|
20
20
|
export declare const conversationUsage: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
21
21
|
name: "conversation_usage";
|
|
22
|
-
schema:
|
|
22
|
+
schema: "chat";
|
|
23
23
|
columns: {
|
|
24
24
|
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
25
25
|
name: "id";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { uuid, text, timestamp, index, jsonb, integer, numeric, unique } from "drizzle-orm/pg-core";
|
|
2
|
+
import { chatSchema } from "./_schema.js";
|
|
2
3
|
import { conversations } from "./conversations.js";
|
|
3
|
-
export const conversationUsage =
|
|
4
|
+
export const conversationUsage = chatSchema.table("conversation_usage", {
|
|
4
5
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
5
6
|
workspaceId: text("workspace_id").notNull(),
|
|
6
7
|
conversationId: uuid("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ResolvedWorkspaceSettings } from './workspace-settings.js';
|
|
2
2
|
export declare const conversations: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
3
3
|
name: "conversations";
|
|
4
|
-
schema:
|
|
4
|
+
schema: "chat";
|
|
5
5
|
columns: {
|
|
6
6
|
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
7
7
|
name: "id";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { uuid, text, timestamp, index, jsonb } from "drizzle-orm/pg-core";
|
|
2
|
+
import { chatSchema } from "./_schema.js";
|
|
3
|
+
export const conversations = chatSchema.table("conversations", {
|
|
3
4
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
4
5
|
workspaceId: text("workspace_id").notNull(),
|
|
5
6
|
userId: text("user_id").notNull(),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { text, timestamp, jsonb, boolean, serial, unique } from "drizzle-orm/pg-core";
|
|
2
2
|
import { relations } from "drizzle-orm";
|
|
3
|
+
import { chatSchema } from "./_schema.js";
|
|
3
4
|
import { workspaceSettings } from "./workspace-settings.js";
|
|
4
|
-
export const externalSkills =
|
|
5
|
+
export const externalSkills = chatSchema.table("external_skills", {
|
|
5
6
|
id: serial("id").primaryKey(),
|
|
6
7
|
workspaceId: text("workspace_id").notNull(),
|
|
7
8
|
ref: text("ref").notNull(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { CHAT_SCHEMA_NAME, chatSchema } from './_schema.js';
|
|
1
2
|
export { conversationUsage, type ConversationUsageMetadata, type ModelUsageEntry } from './conversation-usage.js';
|
|
2
3
|
export { conversations } from './conversations.js';
|
|
3
4
|
export { type ExternalSkillInsert, type ExternalSkillRow, externalSkills, externalSkillsRelations } from './external-skills.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { uuid, text, timestamp, index, jsonb } from "drizzle-orm/pg-core";
|
|
2
|
+
import { chatSchema } from "./_schema.js";
|
|
2
3
|
import { conversations } from "./conversations.js";
|
|
3
|
-
export const messages =
|
|
4
|
+
export const messages = chatSchema.table("messages", {
|
|
4
5
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
5
6
|
conversationId: uuid("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
|
|
6
7
|
role: text("role", { enum: ["user", "assistant"] }).notNull(),
|
|
@@ -29,7 +29,7 @@ export type ResolvedWorkspaceSettings = {
|
|
|
29
29
|
};
|
|
30
30
|
export declare const workspaceSettings: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
31
31
|
name: "workspace_settings";
|
|
32
|
-
schema:
|
|
32
|
+
schema: "chat";
|
|
33
33
|
columns: {
|
|
34
34
|
workspaceId: import("drizzle-orm/pg-core").PgColumn<{
|
|
35
35
|
name: "workspace_id";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { text, timestamp, jsonb } from "drizzle-orm/pg-core";
|
|
2
2
|
import { relations } from "drizzle-orm";
|
|
3
|
+
import { chatSchema } from "./_schema.js";
|
|
3
4
|
import { externalSkills } from "./external-skills.js";
|
|
4
|
-
export const workspaceSettings =
|
|
5
|
+
export const workspaceSettings = chatSchema.table("workspace_settings", {
|
|
5
6
|
workspaceId: text("workspace_id").primaryKey(),
|
|
6
7
|
systemMessage: text("system_message"),
|
|
7
8
|
contextFiles: jsonb("context_files").$type(),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineNitroPlugin } from "nitropack/runtime";
|
|
2
|
+
import { initDb } from "../db/index.js";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
export default defineNitroPlugin(async () => {
|
|
5
|
+
try {
|
|
6
|
+
await initDb();
|
|
7
|
+
} catch (error) {
|
|
8
|
+
logger.error({ error }, "Failed to initialize chat-nuxt database during Nitro boot");
|
|
9
|
+
throw error;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
1
2
|
export type BillingConfig = {
|
|
2
3
|
mode: 'plan';
|
|
3
4
|
cvp: number;
|
|
@@ -9,13 +10,17 @@ export type WorkspaceInfo = {
|
|
|
9
10
|
name: string;
|
|
10
11
|
};
|
|
11
12
|
/**
|
|
12
|
-
* Fetch workspace info
|
|
13
|
+
* Fetch workspace info via the authenticated user's session.
|
|
14
|
+
*
|
|
15
|
+
* Calls auth-api's `whoAmI` with the user's `tela-access-token`, which returns
|
|
16
|
+
* the active organization (including the `settings` JSONB blob and `name`).
|
|
17
|
+
* No internal API key is required.
|
|
13
18
|
*/
|
|
14
|
-
export declare function getWorkspaceInfo(
|
|
19
|
+
export declare function getWorkspaceInfo(event: H3Event): Promise<WorkspaceInfo>;
|
|
15
20
|
/**
|
|
16
|
-
* Fetch workspace billing settings
|
|
21
|
+
* Fetch workspace billing settings via the authenticated user's session.
|
|
17
22
|
*/
|
|
18
|
-
export declare function getWorkspaceBilling(
|
|
23
|
+
export declare function getWorkspaceBilling(event: H3Event): Promise<BillingConfig>;
|
|
19
24
|
export type UsageData = {
|
|
20
25
|
promptCost: number;
|
|
21
26
|
completionCost: number;
|
|
@@ -35,3 +40,20 @@ export type TransformedCosts = {
|
|
|
35
40
|
* For 'credits' mode: calculates credits based on costs and token amounts
|
|
36
41
|
*/
|
|
37
42
|
export declare function transformCostForBilling(usage: UsageData, billing: BillingConfig): TransformedCosts;
|
|
43
|
+
export type DailyUsageRow = {
|
|
44
|
+
workspaceId: string;
|
|
45
|
+
totalTokens: number;
|
|
46
|
+
totalCost: number;
|
|
47
|
+
promptTokens: number;
|
|
48
|
+
completionTokens: number;
|
|
49
|
+
promptCost: number;
|
|
50
|
+
completionCost: number;
|
|
51
|
+
conversationCount: number;
|
|
52
|
+
userCount: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Aggregate per-workspace conversation usage between the given timestamps.
|
|
56
|
+
* Returns plain numeric rows so consumers can avoid pulling drizzle types
|
|
57
|
+
* across package boundaries.
|
|
58
|
+
*/
|
|
59
|
+
export declare function loadDailyUsageStats(start: Date, end: Date): Promise<DailyUsageRow[]>;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getCookie } from "h3";
|
|
2
|
+
import { and, gte, lte, sql } from "drizzle-orm";
|
|
3
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
4
|
+
import { AuthClient } from "@meistrari/auth-nuxt/core";
|
|
5
|
+
import { useDb, schema } from "#chat-runtime/server/db";
|
|
2
6
|
import { logger } from "#chat-runtime/server/utils/logger";
|
|
3
7
|
const DEFAULT_CVP = 1.5;
|
|
4
8
|
function calculateCreditsUsed(inputCost, outputCost, inputAmount, outputAmount) {
|
|
@@ -13,27 +17,42 @@ function calculateCreditsUsed(inputCost, outputCost, inputAmount, outputAmount)
|
|
|
13
17
|
);
|
|
14
18
|
return credits;
|
|
15
19
|
}
|
|
16
|
-
|
|
20
|
+
function defaultWorkspaceInfo(name) {
|
|
21
|
+
return {
|
|
22
|
+
billing: { mode: "plan", cvp: DEFAULT_CVP },
|
|
23
|
+
name: name ?? "workspace"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export async function getWorkspaceInfo(event) {
|
|
27
|
+
const fallbackName = event.context.auth?.workspace?.id;
|
|
28
|
+
const accessToken = event.context.auth?.token ?? getCookie(event, "tela-access-token");
|
|
29
|
+
if (!accessToken) {
|
|
30
|
+
return defaultWorkspaceInfo(fallbackName);
|
|
31
|
+
}
|
|
17
32
|
try {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
33
|
+
const config = useRuntimeConfig();
|
|
34
|
+
const authConfig = config.public.telaAuth;
|
|
35
|
+
const apiUrl = authConfig?.apiUrl;
|
|
36
|
+
if (!apiUrl) {
|
|
37
|
+
logger.warn("telaAuth.apiUrl not configured; falling back to default workspace info");
|
|
38
|
+
return defaultWorkspaceInfo(fallbackName);
|
|
39
|
+
}
|
|
40
|
+
const authClient = new AuthClient(apiUrl);
|
|
41
|
+
const { organization } = await authClient.application.whoAmI(accessToken, {});
|
|
42
|
+
const settings = organization?.settings;
|
|
21
43
|
const billingSettings = settings?.billing;
|
|
22
44
|
const billing = billingSettings?.mode === "credits" ? { mode: "credits" } : { mode: "plan", cvp: billingSettings?.cvp ?? DEFAULT_CVP };
|
|
23
45
|
return {
|
|
24
46
|
billing,
|
|
25
|
-
name:
|
|
47
|
+
name: organization?.name ?? fallbackName ?? "workspace"
|
|
26
48
|
};
|
|
27
49
|
} catch (error) {
|
|
28
|
-
logger.error(error, "Failed to fetch workspace info");
|
|
29
|
-
return
|
|
30
|
-
billing: { mode: "plan", cvp: DEFAULT_CVP },
|
|
31
|
-
name: workspaceId
|
|
32
|
-
};
|
|
50
|
+
logger.error(error, "Failed to fetch workspace info via whoAmI");
|
|
51
|
+
return defaultWorkspaceInfo(fallbackName);
|
|
33
52
|
}
|
|
34
53
|
}
|
|
35
|
-
export async function getWorkspaceBilling(
|
|
36
|
-
const info = await getWorkspaceInfo(
|
|
54
|
+
export async function getWorkspaceBilling(event) {
|
|
55
|
+
const info = await getWorkspaceInfo(event);
|
|
37
56
|
return info.billing;
|
|
38
57
|
}
|
|
39
58
|
export function transformCostForBilling(usage, billing) {
|
|
@@ -66,3 +85,23 @@ export function transformCostForBilling(usage, billing) {
|
|
|
66
85
|
totalCost: totalCredits
|
|
67
86
|
};
|
|
68
87
|
}
|
|
88
|
+
export async function loadDailyUsageStats(start, end) {
|
|
89
|
+
const db = useDb();
|
|
90
|
+
const rows = await db.select({
|
|
91
|
+
workspaceId: schema.conversationUsage.workspaceId,
|
|
92
|
+
totalTokens: sql`SUM(${schema.conversationUsage.totalTokens})::int`,
|
|
93
|
+
totalCost: sql`SUM(${schema.conversationUsage.totalCost}::numeric)::float`,
|
|
94
|
+
promptTokens: sql`SUM(${schema.conversationUsage.promptTokens})::int`,
|
|
95
|
+
completionTokens: sql`SUM(${schema.conversationUsage.completionTokens})::int`,
|
|
96
|
+
promptCost: sql`SUM(${schema.conversationUsage.promptCost}::numeric)::float`,
|
|
97
|
+
completionCost: sql`SUM(${schema.conversationUsage.completionCost}::numeric)::float`,
|
|
98
|
+
conversationCount: sql`COUNT(DISTINCT ${schema.conversationUsage.conversationId})::int`,
|
|
99
|
+
userCount: sql`COUNT(DISTINCT ${schema.conversationUsage.userId})::int`
|
|
100
|
+
}).from(schema.conversationUsage).where(
|
|
101
|
+
and(
|
|
102
|
+
gte(schema.conversationUsage.createdAt, start),
|
|
103
|
+
lte(schema.conversationUsage.createdAt, end)
|
|
104
|
+
)
|
|
105
|
+
).groupBy(schema.conversationUsage.workspaceId).orderBy(sql`SUM(${schema.conversationUsage.totalCost}::numeric) DESC`);
|
|
106
|
+
return rows;
|
|
107
|
+
}
|
|
@@ -15,7 +15,7 @@ const contextFileSchema = z.object({
|
|
|
15
15
|
const resolvedWorkspaceSettingsSchema = z.object({
|
|
16
16
|
systemMessage: z.string().nullable().optional(),
|
|
17
17
|
contextFiles: z.array(contextFileSchema).nullable().optional(),
|
|
18
|
-
canvasTools: z.array(z.object({
|
|
18
|
+
canvasTools: z.array(z.object({ canvasId: z.string() }).passthrough()).nullable().optional(),
|
|
19
19
|
knowledgeSources: z.array(z.object({ id: z.string() }).passthrough()).nullable().optional(),
|
|
20
20
|
externalSkills: z.array(z.object({ ref: z.string(), name: z.string() }).passthrough()).nullable().optional(),
|
|
21
21
|
updatedAt: z.union([z.string(), z.date()]).optional()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const DEFAULT_CHAT_LOADING_MESSAGES: readonly ["Analisando sua pergunta", "Processando informações", "Consultando base de conhecimento", "Organizando ideias", "Elaborando resposta", "Refinando detalhes", "Verificando contexto", "Conectando conceitos", "Preparando resultado", "Quase lá", "Finalizando análise", "Estruturando resposta"];
|
|
2
|
+
export type ChatLoadingMessageMode = 'ordered' | 'random';
|
|
3
|
+
export type ChatLoadingMessagesInput = {
|
|
4
|
+
loadingMessages?: readonly string[] | null;
|
|
5
|
+
loadingMessagesMode?: ChatLoadingMessageMode | null;
|
|
6
|
+
};
|
|
7
|
+
export type ResolvedChatLoadingMessages = {
|
|
8
|
+
mode: ChatLoadingMessageMode;
|
|
9
|
+
messages: readonly string[];
|
|
10
|
+
};
|
|
11
|
+
export declare function resolveChatLoadingMessages(input?: ChatLoadingMessagesInput | null): ResolvedChatLoadingMessages;
|
|
12
|
+
export declare function getNextChatLoadingMessageIndex(mode: ChatLoadingMessageMode, messageCount: number, currentIndex: number, random?: () => number): number;
|