@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.
Files changed (80) hide show
  1. package/README.md +70 -38
  2. package/dist/module.d.mts +24 -1
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +38 -29
  5. package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +3 -1
  6. package/dist/runtime/components/MeistrariChatEmbed.vue +5 -3
  7. package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +3 -1
  8. package/dist/runtime/components/chat/thinking-indicator.vue +28 -17
  9. package/dist/runtime/composables/useEmbedConfig.d.ts +5 -0
  10. package/dist/runtime/composables/useEmbedConfig.js +11 -3
  11. package/dist/runtime/embed/components/ChatConfigurationModal.vue +8 -6
  12. package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +3 -1
  13. package/dist/runtime/embed/components/ChatEmbed.vue +13 -12
  14. package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +3 -1
  15. package/dist/runtime/embed/components/ChatEmbedInner.vue +5 -1
  16. package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +7 -4
  17. package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +7 -4
  18. package/dist/runtime/server/api/conversations/[id]/usage.get.js +1 -1
  19. package/dist/runtime/server/api/conversations/generate-title.post.js +18 -5
  20. package/dist/runtime/server/api/workspace/usage.get.js +1 -1
  21. package/dist/runtime/server/db/index.d.ts +20 -3
  22. package/dist/runtime/server/db/index.js +87 -10
  23. package/dist/runtime/server/db/schema/_schema.d.ts +2 -0
  24. package/dist/runtime/server/db/schema/_schema.js +3 -0
  25. package/dist/runtime/server/db/schema/conversation-usage.d.ts +1 -1
  26. package/dist/runtime/server/db/schema/conversation-usage.js +3 -2
  27. package/dist/runtime/server/db/schema/conversations.d.ts +1 -1
  28. package/dist/runtime/server/db/schema/conversations.js +3 -2
  29. package/dist/runtime/server/db/schema/external-skills.d.ts +1 -1
  30. package/dist/runtime/server/db/schema/external-skills.js +3 -2
  31. package/dist/runtime/server/db/schema/index.d.ts +1 -0
  32. package/dist/runtime/server/db/schema/index.js +1 -0
  33. package/dist/runtime/server/db/schema/messages.d.ts +1 -1
  34. package/dist/runtime/server/db/schema/messages.js +3 -2
  35. package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
  36. package/dist/runtime/server/db/schema/workspace-settings.js +3 -2
  37. package/dist/runtime/server/plugins/init-db.d.ts +2 -0
  38. package/dist/runtime/server/plugins/init-db.js +11 -0
  39. package/dist/runtime/server/utils/billing.d.ts +26 -4
  40. package/dist/runtime/server/utils/billing.js +52 -13
  41. package/dist/runtime/server/utils/chat-workspace-settings.js +1 -1
  42. package/dist/runtime/utils/chat-loading-messages.d.ts +12 -0
  43. package/dist/runtime/utils/chat-loading-messages.js +40 -0
  44. package/dist/runtime/utils/features.d.ts +6 -0
  45. package/dist/runtime/utils/features.js +15 -0
  46. package/dist/types.d.mts +1 -1
  47. package/drizzle/0000_material_hiroim.sql +28 -0
  48. package/drizzle/0001_famous_scalphunter.sql +1 -0
  49. package/drizzle/0002_gorgeous_big_bertha.sql +1 -0
  50. package/drizzle/0003_massive_forge.sql +2 -0
  51. package/drizzle/0004_absent_black_tom.sql +2 -0
  52. package/drizzle/0005_first_kid_colt.sql +1 -0
  53. package/drizzle/0006_sloppy_mauler.sql +13 -0
  54. package/drizzle/0007_grey_rhino.sql +1 -0
  55. package/drizzle/0008_chilly_blue_blade.sql +27 -0
  56. package/drizzle/0009_cultured_maddog.sql +16 -0
  57. package/drizzle/0010_small_barracuda.sql +1 -0
  58. package/drizzle/0011_fat_lady_mastermind.sql +6 -0
  59. package/drizzle/0012_puzzling_rick_jones.sql +1 -0
  60. package/drizzle/0013_blue_gwen_stacy.sql +11 -0
  61. package/drizzle/meta/0000_snapshot.json +239 -0
  62. package/drizzle/meta/0001_snapshot.json +245 -0
  63. package/drizzle/meta/0002_snapshot.json +251 -0
  64. package/drizzle/meta/0003_snapshot.json +263 -0
  65. package/drizzle/meta/0004_snapshot.json +275 -0
  66. package/drizzle/meta/0005_snapshot.json +281 -0
  67. package/drizzle/meta/0006_snapshot.json +320 -0
  68. package/drizzle/meta/0007_snapshot.json +326 -0
  69. package/drizzle/meta/0008_snapshot.json +539 -0
  70. package/drizzle/meta/0009_snapshot.json +646 -0
  71. package/drizzle/meta/0010_snapshot.json +653 -0
  72. package/drizzle/meta/0011_snapshot.json +689 -0
  73. package/drizzle/meta/0012_snapshot.json +695 -0
  74. package/drizzle/meta/0013_snapshot.json +699 -0
  75. package/drizzle/meta/_journal.json +104 -0
  76. package/package.json +5 -3
  77. package/dist/runtime/server/tasks/send-daily-usage-report.d.ts +0 -2
  78. package/dist/runtime/server/tasks/send-daily-usage-report.js +0 -74
  79. package/dist/runtime/server/utils/auth-api.d.ts +0 -1
  80. 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
- if (import.meta.dev && parentEmbedConfig) {
16
- watchEffect(() => {
17
- const parentWorkspaceId = parentEmbedConfig.workspaceId.value.trim();
18
- const propWorkspaceId = props.workspaceId.trim();
19
- if (parentWorkspaceId && propWorkspaceId && parentWorkspaceId !== propWorkspaceId) {
20
- console.warn(`[MeistrariChatEmbed] workspaceId prop (${propWorkspaceId}) differs from parent embed config (${parentWorkspaceId}); using prop value for this embed.`);
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 resolvedFeatures = computed(() => resolveChatFeatures(props.features));
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
- try {
111
- environmentVariables = await resolveWorkspaceCredentials(event, context.workspaceId);
112
- } catch (error) {
113
- logger.warn({ conversationId, messageId, error }, "Failed to resolve workspace credentials on retry, proceeding without env vars");
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
- try {
117
- environmentVariables = await resolveWorkspaceCredentials(event, context.workspaceId);
118
- } catch (error) {
119
- logger.warn({ conversationId, error }, "Failed to resolve workspace credentials, proceeding without env vars");
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(context.workspaceId);
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
- if (!telaApiKey) {
18
- throw createError({
19
- statusCode: 500,
20
- statusMessage: "Tela API key not configured"
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(session.workspace.id);
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
- export declare function useDb(): import("drizzle-orm/postgres-js").PostgresJsDatabase<Record<string, unknown>> & {
4
- $client: import("postgres").Sql<{}>;
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 { logger } from "#chat-runtime/server/utils/logger";
8
+ import { CHAT_SCHEMA_NAME } from "./schema/_schema.js";
9
+ import { logger } from "../utils/logger.js";
6
10
  let dbInstance = null;
7
- export function useDb() {
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
- const config = useRuntimeConfig();
12
- if (!config.databaseUrl) {
13
- throw new Error("DATABASE_URL is not configured");
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
- const client = postgres(config.databaseUrl, { max: 10 });
16
- dbInstance = drizzle(client, { schema });
17
- logger.info("Database connection pool initialized");
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 };
@@ -0,0 +1,2 @@
1
+ export declare const CHAT_SCHEMA_NAME = "chat";
2
+ export declare const chatSchema: import("drizzle-orm/pg-core").PgSchema<"chat">;
@@ -0,0 +1,3 @@
1
+ import { pgSchema } from "drizzle-orm/pg-core";
2
+ export const CHAT_SCHEMA_NAME = "chat";
3
+ export const chatSchema = pgSchema(CHAT_SCHEMA_NAME);
@@ -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: undefined;
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 { pgTable, uuid, text, timestamp, index, jsonb, integer, numeric, unique } from "drizzle-orm/pg-core";
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 = pgTable("conversation_usage", {
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: undefined;
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 { pgTable, uuid, text, timestamp, index, jsonb } from "drizzle-orm/pg-core";
2
- export const conversations = pgTable("conversations", {
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,6 +1,6 @@
1
1
  export declare const externalSkills: import("drizzle-orm/pg-core").PgTableWithColumns<{
2
2
  name: "external_skills";
3
- schema: undefined;
3
+ schema: "chat";
4
4
  columns: {
5
5
  id: import("drizzle-orm/pg-core").PgColumn<{
6
6
  name: "id";
@@ -1,7 +1,8 @@
1
- import { pgTable, text, timestamp, jsonb, boolean, serial, unique } from "drizzle-orm/pg-core";
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 = pgTable("external_skills", {
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,3 +1,4 @@
1
+ export { CHAT_SCHEMA_NAME, chatSchema } from "./_schema.js";
1
2
  export { conversationUsage } from "./conversation-usage.js";
2
3
  export { conversations } from "./conversations.js";
3
4
  export { externalSkills, externalSkillsRelations } from "./external-skills.js";
@@ -8,7 +8,7 @@ export type MessageFile = {
8
8
  };
9
9
  export declare const messages: import("drizzle-orm/pg-core").PgTableWithColumns<{
10
10
  name: "messages";
11
- schema: undefined;
11
+ schema: "chat";
12
12
  columns: {
13
13
  id: import("drizzle-orm/pg-core").PgColumn<{
14
14
  name: "id";
@@ -1,6 +1,7 @@
1
- import { pgTable, uuid, text, timestamp, index, jsonb } from "drizzle-orm/pg-core";
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 = pgTable("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: undefined;
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 { pgTable, text, timestamp, jsonb } from "drizzle-orm/pg-core";
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 = pgTable("workspace_settings", {
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,2 @@
1
+ declare const _default: import("nitropack").NitroAppPlugin;
2
+ export default _default;
@@ -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 including billing settings from the auth API
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(workspaceId: string): Promise<WorkspaceInfo>;
19
+ export declare function getWorkspaceInfo(event: H3Event): Promise<WorkspaceInfo>;
15
20
  /**
16
- * Fetch workspace billing settings from the auth API
21
+ * Fetch workspace billing settings via the authenticated user's session.
17
22
  */
18
- export declare function getWorkspaceBilling(workspaceId: string): Promise<BillingConfig>;
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 { authApiFetch } from "#chat-runtime/server/utils/auth-api";
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
- export async function getWorkspaceInfo(workspaceId) {
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 response = await authApiFetch(`/organizations/${workspaceId}`);
19
- const workspace = response.data;
20
- const settings = workspace?.settings;
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: workspace?.title ?? workspaceId
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(workspaceId) {
36
- const info = await getWorkspaceInfo(workspaceId);
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({ id: z.string() }).passthrough()).nullable().optional(),
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;