allow meeting organizer to see info about embeddings, and add match to pages
This commit is contained in:
@@ -27,7 +27,7 @@ Large Components (>500 lines): 5 components (12.5%)
|
|||||||
├── GiftedDialog.vue (670 lines) ⚠️ HIGH PRIORITY
|
├── GiftedDialog.vue (670 lines) ⚠️ HIGH PRIORITY
|
||||||
├── PhotoDialog.vue (669 lines) ⚠️ HIGH PRIORITY
|
├── PhotoDialog.vue (669 lines) ⚠️ HIGH PRIORITY
|
||||||
├── PushNotificationPermission.vue (660 lines) ⚠️ HIGH PRIORITY
|
├── PushNotificationPermission.vue (660 lines) ⚠️ HIGH PRIORITY
|
||||||
└── MembersList.vue (550 lines) ⚠️ MODERATE PRIORITY
|
└── MeetingMembersList.vue (550 lines) ⚠️ MODERATE PRIORITY
|
||||||
|
|
||||||
Medium Components (200-500 lines): 12 components (30%)
|
Medium Components (200-500 lines): 12 components (30%)
|
||||||
├── GiftDetailsStep.vue (450 lines)
|
├── GiftDetailsStep.vue (450 lines)
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ echo "=============================="
|
|||||||
|
|
||||||
# Analyze critical files identified in the assessment
|
# Analyze critical files identified in the assessment
|
||||||
critical_files=(
|
critical_files=(
|
||||||
src/components/MembersList.vue"
|
src/components/MeetingMembersList.vue"
|
||||||
"src/views/ContactsView.vue"
|
"src/views/ContactsView.vue"
|
||||||
src/views/OnboardMeetingSetupView.vue"
|
src/views/OnboardMeetingSetupView.vue"
|
||||||
src/db/databaseUtil.ts"
|
src/db/databaseUtil.ts"
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ echo "=========================="
|
|||||||
|
|
||||||
# Critical files from our assessment
|
# Critical files from our assessment
|
||||||
files=(
|
files=(
|
||||||
src/components/MembersList.vue"
|
src/components/MeetingMembersList.vue"
|
||||||
"src/views/ContactsView.vue"
|
"src/views/ContactsView.vue"
|
||||||
src/views/OnboardMeetingSetupView.vue"
|
src/views/OnboardMeetingSetupView.vue"
|
||||||
src/db/databaseUtil.ts"
|
src/db/databaseUtil.ts"
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ echo "=========================="
|
|||||||
|
|
||||||
# Critical files from our assessment
|
# Critical files from our assessment
|
||||||
files=(
|
files=(
|
||||||
src/components/MembersList.vue"
|
src/components/MeetingMembersList.vue"
|
||||||
"src/views/ContactsView.vue"
|
"src/views/ContactsView.vue"
|
||||||
src/views/OnboardMeetingSetupView.vue"
|
src/views/OnboardMeetingSetupView.vue"
|
||||||
src/db/databaseUtil.ts"
|
src/db/databaseUtil.ts"
|
||||||
|
|||||||
245
src/components/MeetingMemberMatch.vue
Normal file
245
src/components/MeetingMemberMatch.vue
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<div class="group-onboard-match-display">
|
||||||
|
<!-- Loading -->
|
||||||
|
<div
|
||||||
|
v-if="isLoading"
|
||||||
|
class="flex items-center justify-center gap-2 py-6 text-slate-600"
|
||||||
|
>
|
||||||
|
<font-awesome icon="spinner" class="fa-spin-pulse" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error -->
|
||||||
|
<div
|
||||||
|
v-else-if="errorMessage"
|
||||||
|
class="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-red-700"
|
||||||
|
>
|
||||||
|
Inform the organizer that there was an error. {{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Matched person -->
|
||||||
|
<div
|
||||||
|
v-else-if="matchedPerson"
|
||||||
|
class="rounded-lg border border-slate-200 bg-white p-4 shadow-sm"
|
||||||
|
>
|
||||||
|
<h2 class="mb-3 font-bold text-slate-700">Your Current Match</h2>
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<EntityIcon
|
||||||
|
:contact="matchedPersonContact"
|
||||||
|
class="!size-14 shrink-0 overflow-hidden rounded-full border border-slate-300 bg-white"
|
||||||
|
/>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p class="font-medium text-slate-900">
|
||||||
|
{{ matchedPerson.name || "(No name)" }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-0.5 truncate text-xs text-slate-500">
|
||||||
|
{{ matchedPerson.did }}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
v-if="matchedPerson.description"
|
||||||
|
class="mt-2 line-clamp-3 text-sm text-slate-600"
|
||||||
|
>
|
||||||
|
{{ matchedPerson.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue, Prop, Watch } from "vue-facing-decorator";
|
||||||
|
import {
|
||||||
|
errorStringForLog,
|
||||||
|
getHeaders,
|
||||||
|
serverMessageForUser,
|
||||||
|
} from "@/libs/endorserServer";
|
||||||
|
import { decryptMessage } from "@/libs/crypto";
|
||||||
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
|
import EntityIcon from "./EntityIcon.vue";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
import { AxiosErrorResponse } from "@/interfaces";
|
||||||
|
|
||||||
|
/** Participant from GET /api/partner/groupOnboardMatch pair */
|
||||||
|
interface MatchPairParticipant {
|
||||||
|
issuerDid: string;
|
||||||
|
content: string;
|
||||||
|
description?: string;
|
||||||
|
decryptedContentObject?: {
|
||||||
|
name: string;
|
||||||
|
did: string;
|
||||||
|
isRegistered: boolean;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MatchPair {
|
||||||
|
pairNumber: number;
|
||||||
|
similarity: number;
|
||||||
|
participants: MatchPairParticipant[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Normalized matched person for display */
|
||||||
|
interface MatchedPersonData {
|
||||||
|
name: string;
|
||||||
|
did: string;
|
||||||
|
isRegistered: boolean;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
EntityIcon,
|
||||||
|
},
|
||||||
|
mixins: [PlatformServiceMixin],
|
||||||
|
})
|
||||||
|
export default class GroupOnboardMatchDisplay extends Vue {
|
||||||
|
@Prop({ required: true })
|
||||||
|
meetingPassword!: string;
|
||||||
|
|
||||||
|
/** When provided, used to determine this person's match instead of calling groupOnboardMatch */
|
||||||
|
@Prop()
|
||||||
|
matchPairs?: MatchPair[] | null;
|
||||||
|
|
||||||
|
activeDid = "";
|
||||||
|
apiServer = "";
|
||||||
|
errorMessage = "";
|
||||||
|
isLoading = true;
|
||||||
|
matchedPerson: MatchedPersonData | null = null;
|
||||||
|
/** Pair that contains the current user (for similarity display if needed) */
|
||||||
|
myPair: MatchPair | null = null;
|
||||||
|
|
||||||
|
/** Contact-like object for EntityIcon from matched person */
|
||||||
|
get matchedPersonContact(): Contact | undefined {
|
||||||
|
if (!this.matchedPerson) return undefined;
|
||||||
|
return {
|
||||||
|
did: this.matchedPerson.did,
|
||||||
|
name: this.matchedPerson.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async created() {
|
||||||
|
const settings = await this.$accountSettings();
|
||||||
|
this.apiServer = settings?.apiServer || "";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const activeIdentity = await (this as any).$getActiveIdentity();
|
||||||
|
this.activeDid = activeIdentity?.activeDid || "";
|
||||||
|
await this.fetchMatches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch("meetingPassword")
|
||||||
|
async onPasswordChange() {
|
||||||
|
if (
|
||||||
|
this.meetingPassword &&
|
||||||
|
(this.matchPairs != null || (this.apiServer && this.activeDid))
|
||||||
|
) {
|
||||||
|
await this.fetchMatches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch("matchPairs")
|
||||||
|
async onMatchPairsChange() {
|
||||||
|
if (this.activeDid && this.meetingPassword) {
|
||||||
|
await this.fetchMatches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchMatches(): Promise<void> {
|
||||||
|
const usePropPairs =
|
||||||
|
this.matchPairs != null &&
|
||||||
|
Array.isArray(this.matchPairs) &&
|
||||||
|
this.matchPairs.length > 0;
|
||||||
|
const needApi = !usePropPairs;
|
||||||
|
|
||||||
|
if (
|
||||||
|
needApi &&
|
||||||
|
(!this.meetingPassword?.trim() || !this.apiServer || !this.activeDid)
|
||||||
|
) {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.matchedPerson = null;
|
||||||
|
this.myPair = null;
|
||||||
|
this.errorMessage = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (usePropPairs && (!this.meetingPassword?.trim() || !this.activeDid)) {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.matchedPerson = null;
|
||||||
|
this.myPair = null;
|
||||||
|
this.errorMessage = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.errorMessage = "";
|
||||||
|
this.matchedPerson = null;
|
||||||
|
this.myPair = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let pairs: MatchPair[] | null = null;
|
||||||
|
if (usePropPairs) {
|
||||||
|
// Shallow-copy so we can set decryptedContentObject without mutating the prop
|
||||||
|
pairs = (this.matchPairs ?? []).map((p) => ({
|
||||||
|
...p,
|
||||||
|
participants: p.participants.map((part) => ({ ...part })),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
const headers = await getHeaders(this.activeDid);
|
||||||
|
const response = await this.axios.get(
|
||||||
|
`${this.apiServer}/api/partner/groupOnboardMatch`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
pairs = response?.data?.data?.pairs ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(pairs) || pairs.length === 0) {
|
||||||
|
this.isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt each participant's content and find the pair containing this user
|
||||||
|
for (const pair of pairs) {
|
||||||
|
if (!pair.participants || pair.participants.length !== 2) continue;
|
||||||
|
|
||||||
|
for (const participant of pair.participants) {
|
||||||
|
try {
|
||||||
|
const decrypted = await decryptMessage(
|
||||||
|
participant.content,
|
||||||
|
this.meetingPassword,
|
||||||
|
);
|
||||||
|
participant.decryptedContentObject = JSON.parse(decrypted);
|
||||||
|
} catch {
|
||||||
|
participant.decryptedContentObject = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const myIndex = pair.participants.findIndex(
|
||||||
|
(p) => p.issuerDid === this.activeDid,
|
||||||
|
);
|
||||||
|
if (myIndex === -1) continue;
|
||||||
|
|
||||||
|
this.myPair = pair;
|
||||||
|
const other = pair.participants[1 - myIndex];
|
||||||
|
const obj = other.decryptedContentObject;
|
||||||
|
this.matchedPerson = {
|
||||||
|
name: obj?.name ?? "",
|
||||||
|
did: obj?.did ?? other.issuerDid,
|
||||||
|
isRegistered: !!obj?.isRegistered,
|
||||||
|
description: other.description,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = false;
|
||||||
|
} catch (error) {
|
||||||
|
this.$logAndConsole(
|
||||||
|
"Error fetching group onboard match: " + errorStringForLog(error),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
this.errorMessage =
|
||||||
|
serverMessageForUser(error as unknown as AxiosErrorResponse) ||
|
||||||
|
"Failed to load your match.";
|
||||||
|
this.matchedPerson = null;
|
||||||
|
this.myPair = null;
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -273,7 +273,7 @@ interface DecryptedMember {
|
|||||||
},
|
},
|
||||||
mixins: [PlatformServiceMixin],
|
mixins: [PlatformServiceMixin],
|
||||||
})
|
})
|
||||||
export default class MembersList extends Vue {
|
export default class MeetingMembersList extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
@@ -448,8 +448,8 @@ export const NOTIFY_UNCONFIRMED_HOURS_DYNAMIC = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Complex modal constants (for raw $notify calls with advanced features)
|
// Complex modal constants (for raw $notify calls with advanced features)
|
||||||
// MembersList.vue complex modals
|
// MeetingMembersList.vue complex modals
|
||||||
// Used in: MembersList.vue (complex modal for adding contacts)
|
// Used in: MeetingMembersList.vue (complex modal for adding contacts)
|
||||||
export const NOTIFY_ADD_CONTACT_FIRST = {
|
export const NOTIFY_ADD_CONTACT_FIRST = {
|
||||||
title: "Add as Contact First?",
|
title: "Add as Contact First?",
|
||||||
text: "This person is not in your contacts. Would you like to add them as a contact first?",
|
text: "This person is not in your contacts. Would you like to add them as a contact first?",
|
||||||
@@ -457,7 +457,7 @@ export const NOTIFY_ADD_CONTACT_FIRST = {
|
|||||||
noText: "Skip Adding Contact",
|
noText: "Skip Adding Contact",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used in: MembersList.vue (complex modal for continuing without adding)
|
// Used in: MeetingMembersList.vue (complex modal for continuing without adding)
|
||||||
export const NOTIFY_CONTINUE_WITHOUT_ADDING = {
|
export const NOTIFY_CONTINUE_WITHOUT_ADDING = {
|
||||||
title: "Continue Without Adding?",
|
title: "Continue Without Adding?",
|
||||||
text: "Are you sure you want to proceed with admission? If they are not a contact, you will not know their name after this meeting.",
|
text: "Are you sure you want to proceed with admission? If they are not a contact, you will not know their name after this meeting.",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export interface UserProfile {
|
export interface UserProfile {
|
||||||
description: string;
|
description: string;
|
||||||
|
generateEmbedding?: boolean;
|
||||||
|
embeddingIsForEmptyString?: boolean;
|
||||||
locLat?: number;
|
locLat?: number;
|
||||||
locLon?: number;
|
locLon?: number;
|
||||||
locLat2?: number;
|
locLat2?: number;
|
||||||
|
|||||||
@@ -164,7 +164,9 @@
|
|||||||
>
|
>
|
||||||
{{ userProfileData.description }}
|
{{ userProfileData.description }}
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="text-slate-500 italic">No description.</p>
|
<p v-else class="text-slate-500 italic">
|
||||||
|
This person has no profile description or it's not visible to you.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -319,31 +321,54 @@
|
|||||||
class="mt-4 pt-4 border-t border-slate-300"
|
class="mt-4 pt-4 border-t border-slate-300"
|
||||||
data-testid="generateEmbeddingSection"
|
data-testid="generateEmbeddingSection"
|
||||||
>
|
>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
<div class="flex items-center gap-2">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2 mt-2">
|
||||||
Always generate embedding
|
Always generate embedding
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="switch"
|
role="switch"
|
||||||
:aria-checked="generateEmbedding"
|
:aria-checked="
|
||||||
:disabled="generateEmbeddingSaving || generateEmbeddingLoading"
|
generateEmbedding ?? userProfileData?.generateEmbedding
|
||||||
|
"
|
||||||
|
:disabled="generateEmbeddingSaving || userProfileLoading"
|
||||||
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
:class="generateEmbedding ? 'bg-blue-600' : 'bg-gray-200'"
|
:class="
|
||||||
|
(generateEmbedding ?? userProfileData?.generateEmbedding)
|
||||||
|
? 'bg-blue-600'
|
||||||
|
: 'bg-gray-200'
|
||||||
|
"
|
||||||
@click="toggleGenerateEmbedding"
|
@click="toggleGenerateEmbedding"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition"
|
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition"
|
||||||
:class="generateEmbedding ? 'translate-x-5' : 'translate-x-1'"
|
:class="
|
||||||
|
(generateEmbedding ?? userProfileData?.generateEmbedding)
|
||||||
|
? 'translate-x-5'
|
||||||
|
: 'translate-x-1'
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<span class="text-sm text-gray-600">
|
<span class="text-sm text-gray-600">
|
||||||
{{ generateEmbedding ? "On" : "Off" }}
|
{{
|
||||||
<span v-if="generateEmbeddingLoading" class="ml-1">(loading…)</span>
|
(generateEmbedding ?? userProfileData?.generateEmbedding)
|
||||||
|
? "On"
|
||||||
|
: "Off"
|
||||||
|
}}
|
||||||
|
<span v-if="userProfileLoading" class="ml-1">(loading…)</span>
|
||||||
<span v-else-if="generateEmbeddingSaving" class="ml-1"
|
<span v-else-if="generateEmbeddingSaving" class="ml-1"
|
||||||
>(saving…)</span
|
>(saving…)</span
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="text-sm text-gray-600">
|
||||||
|
{{
|
||||||
|
userProfileData?.embeddingIsForEmptyString == null
|
||||||
|
? ""
|
||||||
|
: userProfileData?.embeddingIsForEmptyString
|
||||||
|
? "- Embedding is for blank description"
|
||||||
|
: "- Embedding is for non-blank description"
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -494,7 +519,7 @@ export default class DIDView extends Vue {
|
|||||||
contactLabels: string[] = [];
|
contactLabels: string[] = [];
|
||||||
|
|
||||||
contactYaml = "";
|
contactYaml = "";
|
||||||
generateEmbedding: boolean | null = null;
|
generateEmbedding: boolean | null = null; // used when there is no profile
|
||||||
generateEmbeddingLoading = false;
|
generateEmbeddingLoading = false;
|
||||||
generateEmbeddingSaving = false;
|
generateEmbeddingSaving = false;
|
||||||
hitEnd = false;
|
hitEnd = false;
|
||||||
@@ -509,7 +534,6 @@ export default class DIDView extends Vue {
|
|||||||
showUserProfile = false;
|
showUserProfile = false;
|
||||||
userProfileData: UserProfile | null = null;
|
userProfileData: UserProfile | null = null;
|
||||||
userProfileError: string | null = null;
|
userProfileError: string | null = null;
|
||||||
userProfileFetched = false;
|
|
||||||
userProfileLoading = false;
|
userProfileLoading = false;
|
||||||
viewingDid?: string;
|
viewingDid?: string;
|
||||||
|
|
||||||
@@ -543,7 +567,7 @@ export default class DIDView extends Vue {
|
|||||||
await this.loadClaimsAbout();
|
await this.loadClaimsAbout();
|
||||||
await this.checkIfOwnDID();
|
await this.checkIfOwnDID();
|
||||||
if (this.showGeneralAdvanced && this.activeDid) {
|
if (this.showGeneralAdvanced && this.activeDid) {
|
||||||
await this.loadGenerateEmbeddingState();
|
await this.loadUserProfile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -623,48 +647,36 @@ export default class DIDView extends Vue {
|
|||||||
this.isMyDid = allAccountDids.includes(this.viewingDid);
|
this.isMyDid = allAccountDids.includes(this.viewingDid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads partner profile generateEmbedding state for the viewed DID (when showGeneralAdvanced).
|
|
||||||
*/
|
|
||||||
private async loadGenerateEmbeddingState() {
|
|
||||||
if (!this.viewingDid || !this.activeDid) return;
|
|
||||||
this.generateEmbeddingLoading = true;
|
|
||||||
try {
|
|
||||||
const headers = await getHeaders(this.activeDid);
|
|
||||||
const url = `${this.partnerApiServer}/api/partner/userProfileForIssuer/${encodeURIComponent(this.viewingDid)}`;
|
|
||||||
const response = await this.axios.get(url, { headers });
|
|
||||||
const data = response.data?.data;
|
|
||||||
this.generateEmbedding =
|
|
||||||
data && typeof data.generateEmbedding === "boolean"
|
|
||||||
? data.generateEmbedding
|
|
||||||
: false;
|
|
||||||
} catch {
|
|
||||||
this.generateEmbedding = false;
|
|
||||||
} finally {
|
|
||||||
this.generateEmbeddingLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the "always generate embedding" flag for the viewed DID on the partner API.
|
* Toggles the "always generate embedding" flag for the viewed DID on the partner API.
|
||||||
* Only permissioned (admin) users can change this; API returns 403 otherwise.
|
* Only permissioned (admin) users can change this; API returns 403 otherwise.
|
||||||
*/
|
*/
|
||||||
async toggleGenerateEmbedding() {
|
async toggleGenerateEmbedding() {
|
||||||
if (
|
if (!this.viewingDid || !this.activeDid) {
|
||||||
!this.viewingDid ||
|
|
||||||
!this.activeDid ||
|
|
||||||
this.generateEmbeddingSaving ||
|
|
||||||
this.generateEmbeddingLoading
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newValue = !this.generateEmbedding;
|
const newValue = !(this.userProfileData
|
||||||
|
? this.userProfileData.generateEmbedding
|
||||||
|
: this.generateEmbedding);
|
||||||
this.generateEmbeddingSaving = true;
|
this.generateEmbeddingSaving = true;
|
||||||
try {
|
try {
|
||||||
const headers = await getHeaders(this.activeDid);
|
const headers = await getHeaders(this.activeDid);
|
||||||
const url = `${this.partnerApiServer}/api/partner/userProfileGenerateEmbedding/${encodeURIComponent(this.viewingDid)}`;
|
const url = `${this.partnerApiServer}/api/partner/userProfileGenerateEmbedding/${encodeURIComponent(this.viewingDid)}`;
|
||||||
await this.axios.put(url, { generateEmbedding: newValue }, { headers });
|
await this.axios.put(url, { generateEmbedding: newValue }, { headers });
|
||||||
|
if (this.userProfileData) {
|
||||||
|
this.userProfileData.generateEmbedding = newValue;
|
||||||
|
this.userProfileData.embeddingIsForEmptyString = newValue; // the server should have generated it or erased it
|
||||||
|
} else {
|
||||||
this.generateEmbedding = newValue;
|
this.generateEmbedding = newValue;
|
||||||
|
if (newValue) {
|
||||||
|
this.userProfileData = {
|
||||||
|
description: "",
|
||||||
|
issuerDid: this.viewingDid,
|
||||||
|
generateEmbedding: newValue,
|
||||||
|
embeddingIsForEmptyString: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
this.notify.success(
|
this.notify.success(
|
||||||
newValue
|
newValue
|
||||||
? "Contact tagged to always generate embedding."
|
? "Contact tagged to always generate embedding."
|
||||||
@@ -719,7 +731,7 @@ export default class DIDView extends Vue {
|
|||||||
*/
|
*/
|
||||||
toggleUserProfile() {
|
toggleUserProfile() {
|
||||||
this.showUserProfile = !this.showUserProfile;
|
this.showUserProfile = !this.showUserProfile;
|
||||||
if (this.showUserProfile && !this.userProfileFetched) {
|
if (this.showUserProfile && !this.userProfileData) {
|
||||||
this.loadUserProfile();
|
this.loadUserProfile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -730,7 +742,6 @@ export default class DIDView extends Vue {
|
|||||||
*/
|
*/
|
||||||
async loadUserProfile() {
|
async loadUserProfile() {
|
||||||
if (!this.viewingDid || !this.activeDid) return;
|
if (!this.viewingDid || !this.activeDid) return;
|
||||||
this.userProfileFetched = true;
|
|
||||||
this.userProfileLoading = true;
|
this.userProfileLoading = true;
|
||||||
this.userProfileError = null;
|
this.userProfileError = null;
|
||||||
this.userProfileData = null;
|
this.userProfileData = null;
|
||||||
|
|||||||
@@ -40,8 +40,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<MeetingMemberMatch
|
||||||
|
:match-pairs="matchPairs"
|
||||||
|
:meeting-password="password || ''"
|
||||||
|
class="mt-4"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Members List -->
|
<!-- Members List -->
|
||||||
<MembersList v-else :password="password" @error="handleError" />
|
<MeetingMembersList :password="password" @error="handleError" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Project Link Section -->
|
<!-- Project Link Section -->
|
||||||
<div v-if="projectLink" class="mt-8 p-4 border rounded-lg bg-white shadow">
|
<div v-if="projectLink" class="mt-8 p-4 border rounded-lg bg-white shadow">
|
||||||
@@ -67,7 +75,8 @@ import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
|||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
import MembersList from "../components/MembersList.vue";
|
import MeetingMemberMatch from "../components/MeetingMemberMatch.vue";
|
||||||
|
import MeetingMembersList from "../components/MeetingMembersList.vue";
|
||||||
import UserNameDialog from "../components/UserNameDialog.vue";
|
import UserNameDialog from "../components/UserNameDialog.vue";
|
||||||
import { encryptMessage } from "../libs/crypto";
|
import { encryptMessage } from "../libs/crypto";
|
||||||
import {
|
import {
|
||||||
@@ -84,7 +93,8 @@ import { AxiosErrorResponse } from "@/interfaces";
|
|||||||
components: {
|
components: {
|
||||||
QuickNav,
|
QuickNav,
|
||||||
TopMessage,
|
TopMessage,
|
||||||
MembersList,
|
MeetingMemberMatch,
|
||||||
|
MeetingMembersList,
|
||||||
UserNameDialog,
|
UserNameDialog,
|
||||||
},
|
},
|
||||||
mixins: [PlatformServiceMixin],
|
mixins: [PlatformServiceMixin],
|
||||||
|
|||||||
@@ -278,6 +278,13 @@
|
|||||||
@close="handleDialogClose"
|
@close="handleDialogClose"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div v-if="!!matchPairs">
|
||||||
|
<MeetingMemberMatch
|
||||||
|
:meeting-password="currentMeeting.password || ''"
|
||||||
|
class="mt-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Members Section -->
|
<!-- Members Section -->
|
||||||
<div
|
<div
|
||||||
v-if="!isLoading && currentMeeting != null && !!currentMeeting.password"
|
v-if="!isLoading && currentMeeting != null && !!currentMeeting.password"
|
||||||
@@ -307,20 +314,19 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<MembersList
|
<MeetingMembersList
|
||||||
ref="membersList"
|
ref="membersList"
|
||||||
:password="currentMeeting.password || ''"
|
:password="currentMeeting.password || ''"
|
||||||
:show-organizer-tools="true"
|
:show-organizer-tools="true"
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
@error="handleMembersError"
|
@error="handleMembersError"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8 p-4 border rounded-lg bg-white shadow">
|
||||||
<!-- Pairwise matches (organizer only: this page is organizer's meeting) -->
|
<!-- Pairwise matches (organizer only: this page is organizer's meeting) -->
|
||||||
<div class="mt-6 pt-4 border-t border-gray-200">
|
<div class="border-gray-200">
|
||||||
<h3 class="font-semibold mb-2">Pairs</h3>
|
<h3 class="font-semibold mb-2">Matching Pairs</h3>
|
||||||
<p class="text-sm text-gray-600 mb-3">
|
|
||||||
Match members by profile similarity
|
|
||||||
</p>
|
|
||||||
<div class="flex flex-wrap gap-2 mb-4">
|
<div class="flex flex-wrap gap-2 mb-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -428,7 +434,8 @@ import { RouteLocationNormalizedLoaded, Router } from "vue-router";
|
|||||||
|
|
||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
import MembersList from "../components/MembersList.vue";
|
import MeetingMembersList from "../components/MeetingMembersList.vue";
|
||||||
|
import MeetingMemberMatch from "../components/MeetingMemberMatch.vue";
|
||||||
import MeetingProjectDialog from "../components/MeetingProjectDialog.vue";
|
import MeetingProjectDialog from "../components/MeetingProjectDialog.vue";
|
||||||
import ProjectIcon from "../components/ProjectIcon.vue";
|
import ProjectIcon from "../components/ProjectIcon.vue";
|
||||||
import {
|
import {
|
||||||
@@ -473,7 +480,7 @@ interface MeetingSetupInputs {
|
|||||||
interface MatchPairParticipant {
|
interface MatchPairParticipant {
|
||||||
issuerDid: string;
|
issuerDid: string;
|
||||||
content: string;
|
content: string;
|
||||||
// there's a similar structure in MembersList.vue with extra Member info
|
// there's a similar structure in MeetingMembersList.vue with extra Member info
|
||||||
decryptedContentObject: {
|
decryptedContentObject: {
|
||||||
name: string;
|
name: string;
|
||||||
did: string;
|
did: string;
|
||||||
@@ -492,7 +499,8 @@ interface MatchPair {
|
|||||||
components: {
|
components: {
|
||||||
QuickNav,
|
QuickNav,
|
||||||
TopMessage,
|
TopMessage,
|
||||||
MembersList,
|
MeetingMembersList,
|
||||||
|
MeetingMemberMatch,
|
||||||
MeetingProjectDialog,
|
MeetingProjectDialog,
|
||||||
ProjectIcon,
|
ProjectIcon,
|
||||||
},
|
},
|
||||||
@@ -1102,7 +1110,6 @@ export default class OnboardMeetingView extends Vue {
|
|||||||
);
|
);
|
||||||
this.matchPairs = null;
|
this.matchPairs = null;
|
||||||
this.previousMatchedPairs = [];
|
this.previousMatchedPairs = [];
|
||||||
this.notify.success("Matches cleared.", TIMEOUTS.STANDARD);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$logAndConsole(
|
this.$logAndConsole(
|
||||||
"Error clearing matches: " + errorStringForLog(error),
|
"Error clearing matches: " + errorStringForLog(error),
|
||||||
@@ -1187,20 +1194,20 @@ export default class OnboardMeetingView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle dialog open event - stop auto-refresh in MembersList
|
* Handle dialog open event - stop auto-refresh in MeetingMembersList
|
||||||
*/
|
*/
|
||||||
handleDialogOpen(): void {
|
handleDialogOpen(): void {
|
||||||
const membersList = this.$refs.membersList as MembersList;
|
const membersList = this.$refs.membersList as MeetingMembersList;
|
||||||
if (membersList) {
|
if (membersList) {
|
||||||
membersList.stopAutoRefresh();
|
membersList.stopAutoRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle dialog close event - start auto-refresh in MembersList
|
* Handle dialog close event - start auto-refresh in MeetingMembersList
|
||||||
*/
|
*/
|
||||||
handleDialogClose(): void {
|
handleDialogClose(): void {
|
||||||
const membersList = this.$refs.membersList as MembersList;
|
const membersList = this.$refs.membersList as MeetingMembersList;
|
||||||
if (membersList) {
|
if (membersList) {
|
||||||
membersList.startAutoRefresh();
|
membersList.startAutoRefresh();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ export async function createBuildConfig(platform: string): Promise<UserConfig> {
|
|||||||
server: {
|
server: {
|
||||||
port: parseInt(process.env.VITE_PORT || "8080"),
|
port: parseInt(process.env.VITE_PORT || "8080"),
|
||||||
fs: { strict: false },
|
fs: { strict: false },
|
||||||
|
//allowedHosts: ['bab3-68-69-173-46.ngrok-free.app'],
|
||||||
|
//allowedHosts: ['*'],
|
||||||
|
|
||||||
// CORS headers disabled to allow images from any domain
|
// CORS headers disabled to allow images from any domain
|
||||||
// This means SharedArrayBuffer is unavailable, but absurd-sql
|
// This means SharedArrayBuffer is unavailable, but absurd-sql
|
||||||
// will automatically fall back to IndexedDB mode which still works
|
// will automatically fall back to IndexedDB mode which still works
|
||||||
|
|||||||
Reference in New Issue
Block a user